Files
automations/deployments/pocket-id
57_WolveandClaude Opus 5 947c89931d fix(deployments): stop interpolating .env values into a sed script
The set_env/env_set helper escaped its value with
`esc=${val//\/\\}; esc=${esc//|/\|}; esc=${esc//&/\&}` and then
interpolated it into `sed -i -e "s|^KEY=.*|KEY=${esc}|"`. The escaping does
not do what it looks like. Tested on bash 5.2:

  set_env K 'a&b'   ->  K=aK=seedb        (sed expanded & to the whole match)
  set_env K 'a|b'   ->  sed: unknown option to `s'   (rc!=0, aborts under set -e)

So any value containing & is silently corrupted and any value containing the
s||| delimiter kills the run. That is reachable: headscale writes
OIDC_CLIENT_SECRET through this, pocket-id writes REDIRECT_URL, copyparty
writes DATA_DIR. A generated secret or a URL query string hits both cases.

The copies in headscale and pocket-id were additionally mangled when they
were introduced -- `${val//\/\}` (pattern `\/`, a literal SLASH) and a raw
newline inside `printf '%s=%s\n'`. The mangled form is a no-op rather than a
corrupter, so the practical failure mode was the same as the original.

Replace all of them with an awk rewrite that passes the key and value through
the ENVIRONMENT, so the value is never parsed as part of a script and needs no
escaping at all. ENVIRON and index() are POSIX, so busybox awk handles them.
Output goes to a temp file and is copied back with `cat >`, which preserves the
original mode and owner -- a .env holding secrets stays 0600. If awk fails,
set -e aborts before .env is touched, which `sed -i` could not promise.

Verified against plain, a&b, a|b, a\b, p@ss&w|rd\x, R&D, a URL with a query
string, s/foo/bar/, a trailing space and the empty string; plus the
append-when-key-absent path, the file-does-not-exist path, non-target lines
left intact, no line-count drift, and mode preservation.

copyparty/update.sh and the rebuilt copyparty payload are included because
update.sh is embedded; regenerated with build.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 14:21:07 -05:00
..

pocket-id

Pocket-ID OIDC provider behind Caddy, with an optional Anubis proof-of-work anti-bot gate. The identity provider for the other stacks (headscale, beszel, webfinger).

Required .env values

Variable Notes
POCKETID_DOMAIN Public hostname (e.g. id.example.com).
ACME_EMAIL Let's Encrypt registration email.
ENCRYPTION_KEY Generated on first deploy (openssl rand -base64 32). Losing it is unrecoverable.
ANUBIS_PID_KEY Generated on first deploy (openssl rand -hex 32).

MAXMIND_LICENSE_KEY (audit-log geolocation) is optional. See .env.example for the full list.

Optional WebFinger: set BASE_DOMAIN (and REDIRECT_URL) to have this Caddy also serve /.well-known/webfinger at the apex — useful when the base domain has no other web server. Leave both blank to run pocket-id only and use the dedicated webfinger deployment instead.

Deploy

From the repo root via the launcher:

./automations.sh        # Deploy on this host → deploy: pocket-id

Or build the self-contained artifact and run it on the host:

./build.sh                                  # embeds files into deploy.sh
scp deploy.sh root@host:                     # copy to the target
ssh root@host 'bash deploy.sh'               # interactive
# non-interactive:
#   POCKETID_DOMAIN=id.example.com ACME_EMAIL=me@example.com SKIP_PROMPTS=1 bash deploy.sh

Unattended provisioning: cloud-init.yml.

DNS for POCKETID_DOMAIN must resolve to the host and ports 80/443 be reachable before deploy, or the LE cert request fails.