diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 111 |
1 files changed, 109 insertions, 2 deletions
@@ -17,13 +17,23 @@ needs to provide](#what-an-app-needs-to-provide). - FreeBSD 15, ZFS root - `nginx` and `git` - neither ships in base; install with `pkg install nginx git-lite` +- `certbot` - only needed if you'll route real domains via `skiff domains`; + install with `pkg install py312-certbot` (package name tracks the default + Python version, so this may drift) - `jail_enable="YES"` in `/etc/rc.conf` (`sysrc jail_enable=YES`) - `service jail start`/`restart` refuse to run without it - Whatever toolchain your apps' Makefiles invoke to build - `go`, `python`, `rustc`, `node`, etc. - baked into the base jail image (see [One-time host setup](#one-time-host-setup)); builds run inside the jail via `jexec`, not on the host itself. `make` itself is already in FreeBSD base (bmake), so - it's the one build tool Skiff never asks you to install. + it's the one build tool Skiff never asks you to install. Watch for + versioned port names (`go125`, `python312`, ...) - FreeBSD installs the + literal versioned binary, not a bare `go`/`python3`, so symlink a default + into the base image's `/usr/local/bin` if a Makefile invokes the + unversioned name. +- The build step needs real outbound network access from inside the jail + (module/package downloads) - see the `--ip` note under [First + deploy](#first-deploy). ## One-time host setup @@ -33,6 +43,42 @@ needs to provide](#what-an-app-needs-to-provide). # zfs create zroot/skiff # zfs create -o mountpoint=/skiff/base zroot/skiff/base # bsdinstall jail /skiff/base +``` + +`bsdinstall jail` only lays down the bare base system - it ships `pkg(8)`'s +bootstrap stub but never actually runs it, so a jail cloned from this alone +has no working `pkg` of its own (files can still be `pkg -c`-installed into +it from the host, but nothing *inside* the jail can manage them, and +`jexec <app> pkg ...` fails with "the package management tool is not yet +installed"). Bootstrap pkg into the image itself, before snapshotting: + +``` +# mount -t devfs devfs /skiff/base/dev +# env ASSUME_ALWAYS_YES=yes pkg -c /skiff/base bootstrap -f +``` + +(`-f` because the stub already counts as "pkg is present" otherwise; devfs +is needed for pkg's own network/TLS calls inside a bare chroot - `fetch(1)` +wants `/dev/null` and `/dev/random`.) Now install whatever your apps' +Makefiles actually need to build: + +``` +# pkg -c /skiff/base install -y go125 +``` + +Watch for versioned port names - FreeBSD installs `go125` as literally +`go125`, not `go`, so a Makefile invoking the bare command needs a default +symlinked in: + +``` +# ln -sf go125 /skiff/base/usr/local/bin/go +# ln -sf gofmt125 /skiff/base/usr/local/bin/gofmt +``` + +Unmount devfs and snapshot once the image can actually build and run apps: + +``` +# umount /skiff/base/dev # zfs snapshot zroot/skiff/base@ready # zfs create -o mountpoint=none zroot/skiff/appbase # zfs create -o mountpoint=none zroot/skiff/appdata @@ -92,6 +138,32 @@ include /usr/local/etc/nginx/skiff.d/*.conf; Skiff only ever writes files under `skiff.d/` and reloads - it never touches `nginx.conf` itself. +### TLS (Let's Encrypt) + +Skiff issues certificates itself via `certbot certonly` (webroot validation, +never certbot's own `--nginx` plugin - Skiff owns `skiff.d/*.conf` and would +fight any other writer of it). Register the certbot account once: + +``` +# certbot register --agree-tos -m you@example.com +``` + +Renewal rides on FreeBSD's own `periodic(8)`, same as any other certbot +install - Skiff runs no daemon or scheduler of its own for this: + +``` +# sysrc -f /etc/periodic.conf weekly_certbot_enable="YES" +``` + +`certonly` mode doesn't reload nginx after a renewal the way certbot's +installer plugins do, so add that as a deploy hook once: + +``` +# mkdir -p /usr/local/etc/letsencrypt/renewal-hooks/deploy +# printf '#!/bin/sh\nnginx -s reload\n' > /usr/local/etc/letsencrypt/renewal-hooks/deploy/reload-nginx +# chmod +x /usr/local/etc/letsencrypt/renewal-hooks/deploy/reload-nginx +``` + ### Install skiff ``` @@ -104,7 +176,7 @@ $ GOOS=freebsd GOARCH=amd64 go build -o skiff ./cmd/skiff ### First deploy ``` -# skiff new-app --name myapp --ip 10.0.0.5 --port 8080 --server-name myapp.example.com +# skiff new-app --name myapp --ip 192.168.1.50 --port 8080 ``` Clones the base dataset, writes `/etc/jail.conf.d/myapp.conf` and the jail's @@ -112,6 +184,38 @@ own rc.d service, creates the bare repo at `/skiff/repos/myapp.git` with its `post-receive` hook installed, and starts the (empty) jail. Prints the remote URL to add locally. +`--ip` must actually route on the host's `--iface` interface - in practice, +an address in the same subnet as the host's own primary address on that +interface, not an arbitrary block like `10.0.0.0/8`. Classic (non-VNET) +jails share the host's routing table, so an out-of-subnet address can still +send packets - they just may never get a response, silently, if whatever +sits upstream (a NAT gateway, a virtualized bridge) only answers its own +subnet. This didn't matter before builds moved inside the jail; now every +deploy's build step needs real outbound network access from that exact +address (module downloads, `pkg install`, etc). + +### Domains & TLS + +`new-app` only provisions the jail - nothing is routed until you add a +domain: + +``` +# skiff domains add --app myapp --primary myapp.example.com +# skiff domains add --app myapp www.myapp.example.com +# skiff domains list --app myapp +# skiff domains remove --app myapp www.myapp.example.com +``` + +`add` routes the hostname over plain HTTP immediately, then requests or +expands a single certificate covering every domain currently registered for +the app (named after whichever is primary). If DNS isn't pointed at the +app's `--ip` yet, issuance fails and the command exits non-zero - the app +stays reachable over HTTP, and `add` is safe to re-run once DNS is fixed, +same as retrying a failed deploy. The first domain added becomes primary +automatically; every other domain 301-redirects to it, over HTTP and HTTPS +alike. `remove` only updates routing - it never calls out to certbot, so a +DNS or network problem can't make it fail. + ### Every deploy after ``` @@ -209,3 +313,6 @@ the Makefile handles. symlink flip and are safe to clean up by hand. - Single host only. Scaling to a second host is a `zfs send`/`receive` of an app's dataset plus the same jail.conf/nginx steps - not yet automated here. +- `skiff domains remove` doesn't shrink the shared certificate's SAN - a + removed hostname may still be listed on it until the next `add` re-syncs + the domain set. Harmless, since nothing routes to it anymore. |
