#!/bin/bash # The browser post editor that ships with the personal website. # # Sourced by setup_personal_website.sh; split out to keep every file under the # 250-line cap. Sourced rather than run, so it inherits the caller's strict # mode plus WEBSITE_SRC / WEBSITE_DATA_DIR from above the source line. # # Deliberately NOT fronted by Caddy. The site is public; its editor writes into # the content directory and triggers a real build, so it binds to loopback and # is reached over an SSH tunnel: # # ssh -N -L 4321:127.0.0.1:4321 # # A public route would also break the login outright: the session cookie is # Secure, which a browser honours only over TLS or on localhost. readonly ADMIN_PORT="4321" readonly ADMIN_UNIT="personal-website-admin.service" readonly ADMIN_UNIT_PATH="/etc/systemd/system/${ADMIN_UNIT}" readonly ADMIN_ENV_FILE="${WEBSITE_DATA_DIR}/admin.env" # The generated password, written once so setup needs no interactive prompt. readonly ADMIN_PASSWORD_FILE="${WEBSITE_DATA_DIR}/admin-password" # The service's own CLI, wrapped so the tests can stand in for it. admin_cli() { node "${WEBSITE_SRC}/dist-admin/admin.js" "$@" } # --- Phase A: build the service and its editor page ------------------------- build_website_admin() { log_info "Building the admin editor…" ( cd "$WEBSITE_SRC" || die "Cannot enter ${WEBSITE_SRC}." pnpm run build:admin ) [[ -f "${WEBSITE_SRC}/dist-admin/admin.js" ]] || die "Build did not produce ${WEBSITE_SRC}/dist-admin/admin.js." # Checked separately: without the page the service starts, answers, and # serves a blank document -- a failure that looks like a browser problem. [[ -f "${WEBSITE_SRC}/dist-admin/client/index.html" ]] || die "Build did not produce the editor page (dist-admin/client/index.html)." log_ok "Admin editor built to ${WEBSITE_SRC}/dist-admin." } # --- Phase B: secrets ------------------------------------------------------- # A URL-safe password from the kernel's entropy. Printed nowhere: it goes # straight into a 0600 file whose path the report names. admin_new_password() { head -c 18 /dev/urandom | base64 | tr -d '\n/+=' } admin_env_text() { cat <"$ADMIN_ENV_FILE" printf '%s\n' "$password" >"$ADMIN_PASSWORD_FILE" ) chmod 600 "$ADMIN_ENV_FILE" "$ADMIN_PASSWORD_FILE" log_ok "Generated admin credentials; the password is in ${ADMIN_PASSWORD_FILE}." } # --- Phase C: the unit ------------------------------------------------------ # systemd starts the service with a minimal PATH, and node here comes from nvm # rather than /usr/bin. The build steps shell out to `node` by name, so the # directory holding it has to be on the unit's PATH or every save fails at the # prerender step with a "node: not found" the editor reports as a failed build. admin_unit_text() { local node_bin node_dir node_bin="$(command -v node)" || die "node is not on PATH." node_dir="$(dirname "$node_bin")" cat </dev/null sudo systemctl daemon-reload log_ok "Wrote ${ADMIN_UNIT_PATH}." } # --- Phase D: start and verify ---------------------------------------------- wait_for_admin() { local code _ for _ in $(seq 1 20); do code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 \ "http://127.0.0.1:${ADMIN_PORT}/admin" || echo "000")" if [[ $code == "200" ]]; then return 0 fi sleep 1 done return 1 } start_admin_service() { log_info "Starting ${ADMIN_UNIT}…" sudo systemctl enable --now "$ADMIN_UNIT" sudo systemctl restart "$ADMIN_UNIT" if wait_for_admin; then log_ok "Admin editor answering on 127.0.0.1:${ADMIN_PORT}." else sudo systemctl status --no-pager "$ADMIN_UNIT" || true die "The admin editor did not come up on 127.0.0.1:${ADMIN_PORT}." fi } admin_report() { cat <