Changeset 96a99bf in Klonkt for scripts


Ignore:
Timestamp:
08/20/2026 10:45:25 AM (3 weeks ago)
Author:
Robin <roboburr@…>
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.
Message:

Merge remote-tracking branch 'pruts/main'

File:
1 edited

Legend:

Unmodified
Added
Removed
  • scripts/klonkt-add-instance.sh

    r77019fa r96a99bf  
    3838[[ "$SLUG" =~ ^[a-z0-9][a-z0-9._-]*$ ]] || die "slug must be lowercase letters, digits, dot, dash or underscore."
    3939
     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.
     51to_ascii() {
     52  if LC_ALL=C printf '%s' "$1" | grep -q '[^ -~]'; then
     53    python3 -c 'import codecs,sys
     54print(".".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.
     63stripped_ascii() {
     64  python3 -c 'import codecs,sys
     65d = sys.argv[1]
     66s = d.replace("‍", "").replace("️", "")
     67if s == d: raise SystemExit(0)
     68print(".".join(l if l.isascii() else "xn--" + codecs.encode(l, "punycode").decode()
     69               for l in s.split(".")))' "$1"
     70}
     71
     72HOST_ASCII="$(to_ascii "$DOMAIN")"
     73
    4074DATA_DIR="$DATA_ROOT/$SLUG"
    4175ENV_FILE="$DATA_DIR/.env"
     
    4882id -u "$KLONKT_USER" >/dev/null 2>&1 || die "user $KLONKT_USER does not exist"
    4983[ -e "$DATA_DIR" ] && die "$DATA_DIR already exists. Pick another slug."
     84
     85if [ "$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
     108fi
    50109[ -f /etc/systemd/system/klonkt@.service ] || {
    51110  [ -f "$KLONKT_DIR/deploy/klonkt@.service" ] || die "missing $KLONKT_DIR/deploy/klonkt@.service"
     
    77136  echo "HOST=127.0.0.1"
    78137  echo "SESSION_SECRET=${SECRET}"
    79   echo "PUBLIC_BASE_URL=https://${DOMAIN}"
     138  echo "PUBLIC_BASE_URL=https://${HOST_ASCII}"
    80139  echo "DATABASE_PATH=${DATA_DIR}/database.sqlite"
    81140  echo "MEDIA_PATH=${DATA_DIR}/media"
     
    105164  step "Caddy"
    106165  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"
    109176  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.
    112181    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}"
    116186  fi
    117187fi
Note: See TracChangeset for help on using the changeset viewer.