Changeset 2dd1dc4 in Klonkt for scripts/install.sh


Ignore:
Timestamp:
07/31/2026 12:48:10 PM (6 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
ccaa530
Parents:
e2c3d09
Message:

Scheid gebruikersdata van code voor self-hosters

Een instance bewaarde zijn database, uploads en .env binnen de checkout. Daardoor
bevatte de codemap levende gebruikersdata: opruimen bij een deploy kon uploads
raken, een back-up moest de data tussen de code vandaan vissen, en een tweede
site vroeg een tweede kopie van alles, inclusief node_modules, die apart
bijgewerkt moest worden.

Nu staat de code in /opt/klonkt en de data per instance in /var/lib/klonkt/<slug>.
De checkout is daarmee wegwerpbaar: weggooien en opnieuw klonen laat elke
instance intact. Een site toevoegen is een map plus een .env, zonder tweede
kopie van de code, en klonkt-update brengt ze in een keer allemaal naar de
nieuwe versie.

De systemd-template draait als de klonkt-gebruiker met ProtectSystem=strict en
ReadWritePaths op alleen de eigen datamap. Een instance kan dus niet in de code
schrijven en niet bij de data van een andere instance, ook niet als er in de app
iets misgaat.

Changed files:
scripts/install.sh

  • KLONKT_DATA_ROOT en KLONKT_SLUG toegevoegd, slug afgeleid van het domein
  • verse installatie zet data in /var/lib/klonkt/<slug> en start klonkt@<slug>
  • bestaande installaties met een .env in de checkout blijven ongemoeid, opnieuw draaien mag nooit een levende database verplaatsen
  • weigert bij een database in de checkout zonder .env, te dubbelzinnig
  • klonkt-update herstart voortaan elke instance, niet alleen klonkt.service

deploy/DEPLOY.md

  • sectie 9b: meerdere zelfstandige Klonkts naast elkaar, met verwijzing
  • onderscheid verduidelijkt met de bestaande multi-tenant sectie, die gaat over sites binnen een instance

New file:
deploy/klonkt@.service

  • systemd template-unit, een service per instance, gedeelde code

scripts/klonkt-migrate-data.sh

  • zet een bestaande installatie om, met --dry-run en een rollback-pad
  • weigert op code zonder src/config/paths.js, anders schrijft de app alsnog naast zijn eigen code

scripts/klonkt-add-instance.sh

  • nieuwe instance: datamap, .env met verse SESSION_SECRET en vrije poort, service en Caddy-blok

deploy/MULTI-INSTANCE.md

  • indeling, eigenaarschap en rechten, migratie, instances toevoegen, updaten, back-up en verwijderen

Nog te doen: dit is getest op de fleet en met een lokale rooktest, maar de
verse-installatiestap zelf is nog niet op een schone VPS gedraaid.

-robo
Co-Authored-By: Claude Opus 5 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • scripts/install.sh

    re2c3d09 r2dd1dc4  
    2828KLONKT_BRANCH="${KLONKT_BRANCH:-stable}"
    2929KLONKT_DIR="${KLONKT_DIR:-/opt/klonkt}"
     30# Where instance data lives, one directory per slug. The code in KLONKT_DIR is
     31# shared; everything an instance writes stays under here.
     32KLONKT_DATA_ROOT="${KLONKT_DATA_ROOT:-/var/lib/klonkt}"
     33# Short name for this instance: its directory under the data root and its
     34# systemd unit (klonkt@<slug>). Derived from the domain when left empty.
     35KLONKT_SLUG="${KLONKT_SLUG:-}"
    3036KLONKT_USER="${KLONKT_USER:-klonkt}"
    3137KLONKT_PORT="${KLONKT_PORT:-3000}"
     
    164170  git clone --depth 1 --branch "$KLONKT_BRANCH" "$KLONKT_REPO" "$KLONKT_DIR"
    165171fi
    166 mkdir -p "$KLONKT_DIR/storage/media" "$KLONKT_DIR/storage/audio"
    167172chown -R "$KLONKT_USER:$KLONKT_USER" "$KLONKT_DIR"
    168173ok "code in $KLONKT_DIR"
     174
     175# --- where this instance keeps its data -------------------------------------
     176# New installs put data in /var/lib/klonkt/<slug> so the checkout stays free of
     177# user data and can be shared by more instances later. An install that already
     178# has its .env inside the checkout is left exactly as it is: re-running the
     179# installer must never move a live database. Convert those deliberately with
     180# scripts/klonkt-migrate-data.sh.
     181if [ -z "$KLONKT_SLUG" ]; then
     182  KLONKT_SLUG="$(printf '%s' "${KLONKT_DOMAIN:-default}" | sed 's/^www\.//' | cut -d. -f1 \
     183                 | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9._-')"
     184  [ -n "$KLONKT_SLUG" ] || KLONKT_SLUG=default
     185fi
     186# A database in the checkout but no .env is too ambiguous to guess at: refuse,
     187# rather than start a fresh empty instance beside data nobody is reading.
     188if [ ! -f "$KLONKT_DIR/.env" ] && [ -f "$KLONKT_DIR/storage/database.sqlite" ]; then
     189  die "found $KLONKT_DIR/storage/database.sqlite but no .env next to it.
     190   Put the .env back and re-run, or move the old storage/ aside first."
     191fi
     192if [ -f "$KLONKT_DIR/.env" ]; then
     193  LAYOUT=legacy
     194  ENV="$KLONKT_DIR/.env"
     195  DATA_DIR="$KLONKT_DIR/storage"
     196  SERVICE="klonkt"
     197  mkdir -p "$DATA_DIR/media" "$DATA_DIR/audio"
     198  chown -R "$KLONKT_USER:$KLONKT_USER" "$DATA_DIR"
     199  ok "existing layout kept (data inside $KLONKT_DIR; split it with scripts/klonkt-migrate-data.sh)"
     200else
     201  LAYOUT=split
     202  DATA_DIR="$KLONKT_DATA_ROOT/$KLONKT_SLUG"
     203  ENV="$DATA_DIR/.env"
     204  SERVICE="klonkt@${KLONKT_SLUG}"
     205  mkdir -p "$DATA_DIR/media" "$DATA_DIR/audio"
     206  chown -R "$KLONKT_USER:$KLONKT_USER" "$DATA_DIR"
     207  chmod 750 "$DATA_DIR"
     208  ok "data in $DATA_DIR (instance '$KLONKT_SLUG')"
     209fi
    169210
    170211log "Installing dependencies (npm ci)…"
     
    173214
    174215log ".env…"
    175 ENV="$KLONKT_DIR/.env"
    176216if [ ! -f "$ENV" ]; then
    177217  SECRET="$(openssl rand -hex 32)"
     
    183223    echo "HOST=127.0.0.1"
    184224    echo "SESSION_SECRET=${SECRET}"
    185     echo "DATABASE_PATH=./storage/database.sqlite"
    186     echo "MEDIA_PATH=./storage/media"
    187     echo "AUDIO_PATH=./storage/audio"
     225    # Absolute, so the app does not depend on its working directory and the
     226    # data can sit outside the checkout. Media subdirectories (avatars,
     227    # post-images, ...) follow MEDIA_PATH by themselves.
     228    echo "DATABASE_PATH=${DATA_DIR}/database.sqlite"
     229    echo "MEDIA_PATH=${DATA_DIR}/media"
     230    echo "AUDIO_PATH=${DATA_DIR}/audio"
    188231    echo "PUBLIC_BASE_URL=https://${KLONKT_DOMAIN}"
    189232    [ -n "$KLONKT_LANG" ] && echo "KLONKT_DEFAULT_LANG=${KLONKT_LANG}"
     
    201244log "systemd service…"
    202245NODE_BIN="$(command -v node)"
    203 cat > /etc/systemd/system/klonkt.service <<EOF
     246if [ "$LAYOUT" = split ]; then
     247  # One template, one service per instance. Adding a site later is a data
     248  # directory plus an .env, with no second copy of the code.
     249  sed -e "s#^User=klonkt\$#User=${KLONKT_USER}#" \
     250      -e "s#^Group=klonkt\$#Group=${KLONKT_USER}#" \
     251      -e "s#^WorkingDirectory=/opt/klonkt\$#WorkingDirectory=${KLONKT_DIR}#" \
     252      -e "s#^EnvironmentFile=/var/lib/klonkt/%i/.env\$#EnvironmentFile=${KLONKT_DATA_ROOT}/%i/.env#" \
     253      -e "s#^ReadWritePaths=/var/lib/klonkt/%i\$#ReadWritePaths=${KLONKT_DATA_ROOT}/%i#" \
     254      -e "s#^ExecStart=/usr/bin/node src/server.js\$#ExecStart=${NODE_BIN} src/server.js#" \
     255      "$KLONKT_DIR/deploy/klonkt@.service" > /etc/systemd/system/klonkt@.service
     256  chmod 0644 /etc/systemd/system/klonkt@.service
     257  systemctl daemon-reload
     258  systemctl enable --now "klonkt@${KLONKT_SLUG}"
     259  ok "klonkt@${KLONKT_SLUG} running on 127.0.0.1:${KLONKT_PORT}"
     260else
     261  cat > /etc/systemd/system/klonkt.service <<EOF
    204262[Unit]
    205263Description=Klonkt
     
    222280WantedBy=multi-user.target
    223281EOF
    224 systemctl daemon-reload
    225 systemctl enable --now klonkt
    226 ok "klonkt.service running on 127.0.0.1:${KLONKT_PORT}"
     282  systemctl daemon-reload
     283  systemctl enable --now klonkt
     284  ok "klonkt.service running on 127.0.0.1:${KLONKT_PORT}"
     285fi
    227286
    228287if [ -z "$NO_CADDY" ]; then
     
    261320  runuser -u ${KLONKT_USER} -- env HOME="\$D" bash -c "cd '\$D' && npm ci --omit=dev"
    262321fi
    263 systemctl restart klonkt
    264 echo "Klonkt updated (\$A) + restarted."
     322# Restart every instance. Each directory under the data root with an .env is one
     323# instance sharing this checkout. An install that has not been split yet has no
     324# such directories and still runs the single klonkt.service.
     325N=0
     326for d in ${KLONKT_DATA_ROOT}/*/; do
     327  [ -f "\$d/.env" ] || continue
     328  s=\$(basename "\$d")
     329  systemctl restart "klonkt@\$s" && N=\$((N+1))
     330done
     331if [ "\$N" = 0 ]; then
     332  systemctl restart klonkt
     333  echo "Klonkt updated (\$A) + restarted."
     334else
     335  echo "Klonkt updated (\$A) + restarted \$N instance(s)."
     336fi
    265337EOF
    266338chmod +x /usr/local/bin/klonkt-update
     
    286358echo "  • First run:       go to /auth/register and create your admin account."
    287359echo
    288 echo "  Manage:  systemctl status klonkt · journalctl -u klonkt -f · klonkt-update"
     360echo "  Manage:  systemctl status ${SERVICE} · journalctl -u ${SERVICE} -f · klonkt-update"
    289361echo "  Lost password: cd ${KLONKT_DIR} && runuser -u ${KLONKT_USER} -- env HOME=${KLONKT_DIR} npm run reset-admin"
     362if [ "$LAYOUT" = split ]; then
     363  echo
     364  echo "  Code:  ${KLONKT_DIR}          shared, nothing of yours lives here"
     365  echo "  Data:  ${DATA_DIR}   database, uploads and .env — back up this one"
     366  echo "  Another site on this server, sharing the same code:"
     367  echo "      sudo bash ${KLONKT_DIR}/scripts/klonkt-add-instance.sh <slug> <domain>"
     368fi
    290369echo
    291370echo "  DNS: make sure A + AAAA of ${KLONKT_DOMAIN} point to this server."
Note: See TracChangeset for help on using the changeset viewer.