- Timestamp:
- 08/20/2026 10:45:25 AM (3 weeks ago)
- Branches:
- main
- Children:
- d9f60f4
- Parents:
- 77019fa (diff), bc9ef3b (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)links above to see all the changes relative to each parent. - File:
-
- 1 edited
-
scripts/klonkt-add-instance.sh (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
scripts/klonkt-add-instance.sh
r77019fa r96a99bf 38 38 [[ "$SLUG" =~ ^[a-z0-9][a-z0-9._-]*$ ]] || die "slug must be lowercase letters, digits, dot, dash or underscore." 39 39 40 # An internationalised domain is written into the Caddy block, the filename and 41 # .env as ASCII. Not because Caddy needs it — it copes — but because an emoji 42 # name can carry a zero-width joiner and variation selectors, and those are 43 # INVISIBLE. An editor, a paste or a well-meant tidy-up drops one and the vhost 44 # stops matching with nothing on screen to explain why. 45 # 46 # Per-label punycode, and deliberately not an IDNA library. Python's built-in 47 # `idna` codec STRIPS the joiner and yields a different name (one that has no 48 # certificate); the strict IDNA2008 tools reject emoji outright. Encoding what 49 # the operator actually typed is the only thing that matches the DNS record 50 # they actually made. 51 to_ascii() { 52 if LC_ALL=C printf '%s' "$1" | grep -q '[^ -~]'; then 53 python3 -c 'import codecs,sys 54 print(".".join(l if l.isascii() else "xn--" + codecs.encode(l, "punycode").decode() 55 for l in sys.argv[1].split(".")))' "$1" 56 else 57 printf '%s\n' "$1" 58 fi 59 } 60 61 # The same name with joiner and variation selectors removed: what a client that 62 # normalises them away will ask for instead. Empty when the name has none. 63 stripped_ascii() { 64 python3 -c 'import codecs,sys 65 d = sys.argv[1] 66 s = d.replace("", "").replace("️", "") 67 if s == d: raise SystemExit(0) 68 print(".".join(l if l.isascii() else "xn--" + codecs.encode(l, "punycode").decode() 69 for l in s.split(".")))' "$1" 70 } 71 72 HOST_ASCII="$(to_ascii "$DOMAIN")" 73 40 74 DATA_DIR="$DATA_ROOT/$SLUG" 41 75 ENV_FILE="$DATA_DIR/.env" … … 48 82 id -u "$KLONKT_USER" >/dev/null 2>&1 || die "user $KLONKT_USER does not exist" 49 83 [ -e "$DATA_DIR" ] && die "$DATA_DIR already exists. Pick another slug." 84 85 if [ "$HOST_ASCII" != "$DOMAIN" ]; then 86 say "IDN: ${DOMAIN} -> ${HOST_ASCII}" 87 ALIAS_ASCII="$(stripped_ascii "$DOMAIN" || true)" 88 if [ -n "$ALIAS_ASCII" ]; then 89 die "refusing ${DOMAIN} 90 91 This name contains a zero-width joiner or a variation selector, which makes 92 it invalid under IDNA2008. Browsers reject it — two were tested — so nobody 93 could reach the site by the name you just typed. Worse, clients that quietly 94 strip those characters ask for a DIFFERENT name than the one you registered: 95 96 you typed : ${HOST_ASCII} 97 they ask : ${ALIAS_ASCII} 98 99 Nothing has been created. Use an emoji that is a single codepoint, or an 100 ordinary name. 101 102 If you want this anyway, pass the punycode form as the domain, and give the 103 stripped name its own DNS record and a redirect block so one identity keeps 104 one canonical address: 105 106 $0 $SLUG ${HOST_ASCII}" 107 fi 108 fi 50 109 [ -f /etc/systemd/system/klonkt@.service ] || { 51 110 [ -f "$KLONKT_DIR/deploy/klonkt@.service" ] || die "missing $KLONKT_DIR/deploy/klonkt@.service" … … 77 136 echo "HOST=127.0.0.1" 78 137 echo "SESSION_SECRET=${SECRET}" 79 echo "PUBLIC_BASE_URL=https://${ DOMAIN}"138 echo "PUBLIC_BASE_URL=https://${HOST_ASCII}" 80 139 echo "DATABASE_PATH=${DATA_DIR}/database.sqlite" 81 140 echo "MEDIA_PATH=${DATA_DIR}/media" … … 105 164 step "Caddy" 106 165 CADDY=/etc/caddy/Caddyfile 107 if grep -q "^${DOMAIN} {" "$CADDY" 2>/dev/null; then 108 say "a block for ${DOMAIN} already exists, left untouched" 166 CONFD=/etc/caddy/conf.d 167 # One file per site. Older machines keep every block in the single Caddyfile, 168 # so add the import if it is missing: this then works on both without moving 169 # anything that is already there. 170 mkdir -p "$CONFD" 171 grep -q '^[[:space:]]*import[[:space:]]\+conf\.d/' "$CADDY" 2>/dev/null \ 172 || printf '\nimport conf.d/*.caddyfile\n' >> "$CADDY" 173 BLOCK="$CONFD/${HOST_ASCII}.caddyfile" 174 if [ -e "$BLOCK" ]; then 175 say "a block for ${HOST_ASCII} already exists, left untouched" 109 176 else 110 cp "$CADDY" "${CADDY}.bak.$(date +%s)" 2>/dev/null || true 111 printf '\n%s {\n reverse_proxy 127.0.0.1:%s\n encode gzip zstd\n}\n' "$DOMAIN" "$PORT" >> "$CADDY" 177 printf '%s {\n reverse_proxy 127.0.0.1:%s\n encode gzip zstd\n}\n' "$HOST_ASCII" "$PORT" > "$BLOCK" 178 # Take the block away again rather than leave a config that will not load. 179 # The running Caddy is unaffected until someone reloads, and reloading is 180 # precisely what the next person to touch this machine will do. 112 181 caddy validate --config "$CADDY" --adapter caddyfile >/dev/null 2>&1 \ 113 || die "Caddy config invalid after adding ${DOMAIN} — check $CADDY (a .bak was made)" 114 systemctl reload caddy 2>/dev/null || systemctl restart caddy 115 say "serving ${DOMAIN}" 182 || { rm -f "$BLOCK"; die "Caddy config invalid for ${HOST_ASCII} — the block was removed again, nothing changed"; } 183 systemctl reload caddy \ 184 || die "caddy reload failed. NOT restarting: that would drop every site on this machine. See: journalctl -u caddy -n 30" 185 say "serving ${HOST_ASCII}" 116 186 fi 117 187 fi
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)