704923fe4b3a9fb0e23f94db763a9fe17d4601d6 — Andrew Zah 8 months ago 7e8722a
change highlights to inline-block, add post 18
M config.toml => config.toml +1 -3
@@ 3,7 3,7 @@ base_url = "andrewzah.com"
compile_sass = true

title = "Andrew Zah"
title = "AZ"
description = "Sometimes relevant thoughts by Andrew"
default_language = "en"
rss_limit = 20

@@ 19,6 19,4 @@ taxonomies = [

languagecode = "en"
subtitle = "Foremost expert on Smash Bros. Melee"
author = { name = "Andrew Zah" }

M content/posts/008_programming_korean/index.md => content/posts/008_programming_korean/index.md +6 -4
@@ 13,6 13,10 @@ keywords = "korean programming language hangeul linguistics hangeul perspective"
summary = "Korean has similarities with functional programming languages."
show_summary = true

footnotes = [
  "Note that `갔` changed to `간`. `갔` is 가다 (to go) + `~ㅆ` (past tense). But the past tense nominalization form uses `~ㄴ (것)`. Instead of 것 (thing) we swapped it for another noun 여자 (woman)."

references = [
  ["German for Programmers", "https://wickedchicken.github.io/post/german-for-programmers/"],
  ["Hangeul Grid Order ", "https://www.howtostudykorean.com/unit0/unit0lesson1/"],

@@ 211,17 215,15 @@ Note that 나가다 is a pure Korean word. To the right is 出口 (pronounced 
So, let's take 'the girl walked to school'. In English and Korean this is straightfoward enough:

  {{hlw(c="green", t="여자는")}} {{hll(c="pink",t="학교")}}{{hlrw(c="purple",t="로")}}{{hlw(c="blue",t="걸어")}} {{hlw(c="blue",t="갔어요")}} — {{hlw(c="green",t="The girl")}} {{hlw(c="blue",t="walked")}}{{hlw(c="purple",t="to")}} {{hlrw(c="pink",t="school")}}
  {{hlw(c="green", t="여자는")}} {{hll(c="pink",t="학교")}}{{hlr(c="purple",t="로")}} {{hlw(c="blue",t="걸어")}} {{hlw(c="blue",t="갔어요")}} — {{hlw(c="green",t="The girl")}} {{hlw(c="blue",t="walked")}} {{hlw(c="purple",t="to")}} {{hlrw(c="pink",t="school")}}

But what if you wanted to talk *about* that person? You could say "the girl *who* walked to school". In English, this these are known as relative causes. They can begin with `who`, `which`, `that`, `where`, etc, *following* the noun. Korean uses the `~는 것` nominalizer *before* the noun, which leads to:

  {{hll(c="pink", t="학교")}}{{hlrw(c="purple", t="로")}} {{hlw(c="blue",t="걸어")}} {{hlw(c="orange",t="간")}} {{hlrw(c="green",t="여자")}}!
  {{hll(c="pink", t="학교")}}{{hlrw(c="purple", t="로")}} {{hlw(c="blue",t="걸어")}} {{hlw(c="orange",t="간")}} {{hlrw(c="green",t="여자")}}!{{footnote(num=0)}}

Note that `갔` changed to `간`. `갔` is 가다 (to go) + `~ㅆ` (past tense). But the past tense nominalization form uses `~ㄴ (것)`. Instead of 것 (thing) we swapped it for another noun 여자 (woman).

Not that one would only say "the girl who walked to school" by itself, but we can now use the entire construct as a noun in other sentences:


A content/posts/018_zola_caddy_automatic_deploy/index.md => content/posts/018_zola_caddy_automatic_deploy/index.md +297 -0
@@ 0,0 1,297 @@
title = "Automatically deploy your blog via Git with Caddy and Docker"
slug = "automatically-deploy-your-blog-via-git-caddy-docker"
date = 2019-06-02
template = "post.html"
draft = false

tags = ["zola", "caddy", "docker"]
categories = ["programming"]

keywords = "automatic automatically deploy caddy docker zola gutenberg static site generation blog"
summary = "Using Caddy, Docker, and Zola, one can easily deploy updates to their blog with a git push."

footnotes = [
  "Philipp Offerman's fantastic blog, Writing an OS in Rust, uses Zola.",
  "Initially I made the mistake of including binary data in my site's repo. This blew up my docker alpine image from ~2mb to ~35mb before I realized. Whoops.",
  "I made this mistake when I ran the the Docker image for the first time. Hitting `ctrl-c` wouldn't kill it, I had to run `docker-compose down`... but I ran it too late. I had to wait 24 hours to deploy HTTPS for my site after that."

Over the years, I've slowly and incrementally optimized my blog. Originally I used an entire rails setup with postgres, because that was the first thing I really learned how to program. Yet that's quite the overkill for a static blog... I don't even include comments anymore.

This is where [Zola][Zola] comes in, previously named *Gutenberg*. It's a static site generator written in Rust that uses [Tera][tera] for templating. It serves as a counterpart to [Hugo][hugo], written in Golang. Both have a similar featureset, so I chose Zola since I know Rust and can contribute if needed.

However basically [any static site generation system][buildit] can work, so long as you end up with files generated to your liking.

## Zola
Feel free to skip this section if you already have your own static site generation system.

**One caveat**: Zola minifies sass by default but not javascript. I use a [Makefile][makefile] to minify the js files and bundle them.

#### Getting started

Zola has a [getting started][zola-started] guide. For inspiration, you can look at the [source code for this very blog][source] or [different sites using Zola][zola-examples].{{footnote(num=0)}}

#### My setup
This is what my site's directory looks like:

├── binary-data/
├── Caddyfile
├── config.toml
├── content/
├── docker-compose.yml
├── Dockerfile
├── Makefile
├── public/
├── sass/
├── static/
├── syntaxes/
├── templates/
└── themes/

`binary-data` is where I store all the screenshots, pdfs, and other binary data I refer to my posts. For the actual posts I upload these to an amazon S3 bucket, but I keep these as a backup, *outside* of git.{{footnote(num=1)}}

`sass/` and `static/` are pretty easy: the former gets compiled to css, the latter gets copied directly to the `public/` directory during generation.

For code, `themes` contains the syntax highlighting theme, and `syntaxes` contains sublime syntax files I added because Zola doesn't support [slim][slim] syntax highlighting yet.

[slim]: http://slim-lang.com/

This leaves us `content`, the actual posts and pages, and `templates`, for how to render them. `templates` also contains `shortcodes/`, which function much like wordpress' shortcodes.

#### Templating

[These][templates] are all the templates I've made. Naturally it can get as complex as you want. I generally have one per page or page type, such as /projects or /posts.

At a minimum, you probably want a [base.html][base] to deal with the oh-so-fun SEO stuff, and a `macros.html` for "dynamically" rendering things. I use it for the navigation bar, footnotes, references, citations, and rendering links.

With child templates, you can use blocks to inject content back to the parent:

<!-- base.html -->
  <!-- constants in base.html header -->
  <meta charset="UTF-8">

  <!-- set from child -->
  <title>{% block title %}{% endblock title %}</title>
  {% block head %}{% endblock head %}
then in the child you define the block:
<!-- zola provides objects like page/section/config, see the docs -->
{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %}

Thrilling stuff.

If you look at my source it can appear a tad complex now, but I just slowly added things as they came up– like the page title, then custom SEO attributes, etc.

#### Macros

Tera's macro system is really useful. One of my use cases was to show the tags and categories below a post:

{% macro render_tags(tags) %}
  <div class="tags">
    {% for tag in tags %}
      {% if loop.last %}
        <a href="/tags/{{ tag | slugify }}/">{{ tag | title }}</a>
      {% else %}
        <a href="/tags/{{ tag | slugify }}/">{{ tag | title }}</a> | 
      {% endif %}
    {% endfor %}
{% endmacro render_tags %}
Yes, I should probably clean them up a bit. They work good enough for now.

#### Shortcodes
Shortcodes are awesome. Two main things I use them for:

* footnotes, citations, references
* generating boilerplate for lity.js (a lightbox lib)

<!-- img.html -->
<a href="{{url}}" data-lity data-lity-desc="{{desc}}" alt="{{desc}}">
  <img class="full" async-src="{{url}}"/>
{% if t %}
  <p class="image-desc"> {{t}} </p>
{% endif %}
<!-- footnote.html -->
<a id="footnote-cite-{{num}}" href="#footnote-{{num}}">({{num}})</a>

My [Korean for Programmers][kp] post uses ~5 shortcodes to {{hl(t="highlight",c="red")}} words in different colors:

<!-- hlm.html -->
<!-- where t=text,c=css color class name-->
<span class="hl hl-middle hl-{{c}}">

Okay, okay.. Time for the real stuff.

## Static Assets Repo
Now that you have your static files, commit them to a new git repo. With Zola, I use `rsync` to move the output from `public/` to another directory–since `zola build` nukes it each time.

As stated earlier I keep binary files like images in a separate directory, and in the posts themselves I link to amazon s3. If you want to link to assets locally, you might need something like [Git LFS][lfs] from Github or or a different solution.

I keep my statically generated assets at [github.com/azah/personal-site-public][static-assets] because sourcehut doesn't support webhooks yet.

## Caddy

[Caddy][caddy] is an awesome HTTP/2 web server. It handles SSL certs for you automatically via Lets Encrypt, and it has a `git` plugin which we'll be using. The git plugin clones or updates a repo for us, so we can now push content to a git repo and have it automatically update!

Let's create the Caddyfile:

**NOTE**!! Use a port (like :2015) for local testing instead of the actual domain! If you run Caddy with this caddyfile locally without the `-disable-acme-auth`, caddy will repeatedly try to authorize, quickly **ratelimiting you from Let's Encrypt**!{{footnote(num=2)}}

# Caddyfile
andrewzah.com, andrei.kr {
  cache {
    default_max_age 10m

  git {
    hook /webhook {%SITE_WEBHOOK%}
    repo https://github.com/azah/personal-site-public.git
    branch master
    clone_args --recurse-submodules
    pull_args --recurse-submodules
    interval 86400
    hook_type github

  root /www/public

The `SITE_WEBHOOK` environment variable is set in `.env`.

Note that a webhook is optional. In fact, [all of the git directives here are optional][caddy-git] besides the repo path itself. By default the plugin clones to the root path, `/www/public` in this case.

I've set it to pull once per day as well as listen for requests on `/webhook`. Right now I use github webhooks as `sourcehut` doesn't seem to support webhooks yet.

If you're running multiple containerized services you can use caddy as a proxy as well. You can see the [source for andrewzah.com's docker script][andrewzahcom] as an example. I have an `http` docker service that proxies to my `website` service, which looks like the following:

# services/http/Caddyfile
www.andrewzah.com, andrewzah.com, andrei.blue {
  tls zah@andrewzah.com

  log / stdout {combined}
  errors stderr

  proxy /webhook http://website:1111/webhook {

  proxy / http://website:1111


## Docker

Lastly, we'll run all of this inside a docker container, so we need a `Dockerfile`:

FROM alpine:edge
LABEL caddy_version = "1.0.0" architecture="amd64"

# Caddy
RUN adduser -S caddy

ARG plugins=http.git,http.cache
ARG version=v1.0.0

RUN apk add --no-cache --virtual .build-caddy openssh-client tar curl \
  && apk add --no-cache git \
  && curl --silent --show-error --fail --location \
  --header "Accept: application/tar+gzip, application/x-zip, application/octet-stream" -p \
  "https://caddyserver.com/download/linux/amd64?version=${version}&plugins=${plugins}&license=personal&telemetry=off" \
  | tar --no-same-owner -C /usr/bin -xz caddy \
  && chmod 0755 /usr/bin/caddy \
  && apk del --purge .build-caddy

RUN /usr/bin/caddy --plugins
RUN mkdir /www \
  && chown -R caddy /www

COPY Caddyfile /etc/Caddyfile

USER caddy
ENTRYPOINT ["/usr/bin/caddy"]
CMD ["--conf", "/etc/Caddyfile", "--log", "stdout", "-agree"]

and a corresponding `docker-compose` file:
version: '3.7'

    restart: always
      context: .
    image: <your_dockerhub_username>/personal-site
      - "1111"
      - ".env"

I try to use alpine docker whenever possible. This image fetches a predefined Caddy version, v1.0.0, with the `cache` and `git` plugins.

We need to pass the `-agree` flag to agree to Let's Encrypt's Subscriber Agreement. Caddy will not run otherwise unless you use `-disable-http-challenge` (or specify http/a port), but we want HTTPS, no?

Deploying the image is just `docker push` once you've signed in via the docker cli. 


...and that's pretty much it. For your VPS, you'll want to install docker and/or docker-compose, then run the image. If you set up a corresponding docker-compose file, you can do `docker-compose pull && docker-compose up -d`.

If you're using webhooks, don't forget to configure the webhook on github/gitlab/bitbucket/etc.

If configured correctly, you should now be able to git push your static assets and automatically have the container pull them in!

[andrewzahcom]: https://git.sr.ht/~andrewzah/andrewzah.com/tree
[base]: https://git.sr.ht/~andrewzah/personal-site/tree/master/templates/base.html
[buildit]: https://git.sr.ht/~charles/cdaniels.net/tree/master/bin/buildit
[caddy-git]: https://caddyserver.com/docs/http.git
[caddy]: https://caddyserver.com/
[hugo]: https://gohugo.io/
[kp]: ../korean-for-programmers/#finally-a-sentence
[lfs]: https://git-lfs.github.com/
[macros]: https://git.sr.ht/~andrewzah/personal-site/tree/master/templates/macros.html
[makefile]: https://git.sr.ht/~andrewzah/personal-site/tree/master/Makefile
[philopp]: https://os.phil-opp.com/
[source]: https://git.sr.ht/~andrewzah/personal-site/tree
[static-assets]: https://github.com/azah/personal-site-public
[templates]: https://git.sr.ht/~andrewzah/personal-site/tree/master/templates
[tera]: https://tera.netlify.com/
[zola-examples]: https://github.com/getzola/zola/blob/master/EXAMPLES.md
[zola-started]: https://www.getzola.org/documentation/getting-started/installation/
[zola]: https://getzola.org

## Conclusion

M sass/components/_highlight.scss => sass/components/_highlight.scss +5 -3
@@ 1,11 1,13 @@
.hl {
  padding: 2px 0px 2px 2px;
  padding: 0px 5px 0px 5px;
  margin-top: 0.5px;
  border-radius: 2px;
  display: inline-block;

.hl-word {
  padding: 2px;
  margin-right: 6px;
  //padding: 2px;
  //margin-right: 6px;

.hl-left {