clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

main
forge / server / provision.sh
provision.sh 4.3K
1#!/bin/sh2# Turn a fresh Debian 13 or Ubuntu 24.04 host into a forge. Run as root from3# a copy of this repo (e.g. rsync'd to /opt/forge):4#5# sh /opt/forge/server/provision.sh <domain> <your-name> <your-key.pub> [pages-domain]6#7# Installs git, sshd, Node, and Caddy; creates the git user with8# /srv/git as home; registers your key; serves pages on https://<domain>9# (Caddy gets certificates itself). With a pages-domain, sites are served from10# that separate origin instead of <domain>/sites/. Runs the live-updates11# server (and the sites server, with a pages-domain) under systemd. Safe to12# re-run.13set -eu14[ $# -ge 3 ] || { sed -n '2,/^set -eu/{/^#/s/^# \{0,1\}//p;}' "$0"; exit 1; }15domain=$1 owner=$2 key=$3 pages=${4:-}16app=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)17root=/srv/git18# Caddy site address; tests set this to :80 to skip TLS.19addr=${FORGE_CADDY_ADDR:-$domain}2021export DEBIAN_FRONTEND=noninteractive22apt-get update -q23# restic and rclone: forge backup and forge mirror (README.md, "Backups and moving hosts").24apt-get install -y -q --no-install-recommends git openssh-server caddy ca-certificates fcgiwrap restic rclone25# The forge runs its TypeScript directly, which takes Node 22.18 or newer;26# 24 LTS (24.7 or later) also lets passkeys be post-quantum (ML-DSA-44), as27# in the container. The distro's Node is used if it is new enough;28# otherwise install one first.29ts_node() { node -e 'process.exit(process.features.typescript ? 0 : 1)' 2>/dev/null; }30ts_node || apt-get install -y -q --no-install-recommends nodejs npm31ts_node || { echo "provision: forge needs Node 22.18 or newer (24 LTS is best), found $(node --version 2>/dev/null || echo none); install it and re-run" >&2; exit 1; }3233id git >/dev/null 2>&1 || adduser --system --group --shell /bin/sh --home "$root" git34mkdir -p "$root"35chown git:git "$root"36chmod 750 "$root"37# Caddy reads pages and site content through the git group.38usermod -aG git caddy39# Debian's fcgiwrap (git http-backend, for HTTPS clones) runs as www-data and needs to read the repos.40usermod -aG git www-data4142(cd "$app" && npm ci --omit=dev --no-audit --no-fund --silent)43chmod -R a+rX "$app"4445as_git() { su -s /bin/sh git -c "$*"; }46as_git "FORGE_ROOT=$root $app/bin/forge server init"4748sites_url=/sites/{owner}/{repo}/{site}/49previews_url=/previews/{owner}/{repo}/{site}/{branch}/50if [ -n "$pages" ]; then51 sites_url=https://$pages/sites/{owner}/{repo}/{site}/52 previews_url=https://$pages/previews/{owner}/{repo}/{site}/{branch}/53fi54cat >"$root/.forge/config" <<EOF55FORGE_NAME=forge56FORGE_BASE_URL=https://$domain57FORGE_SSH=git@$domain58FORGE_SITES_URL=$sites_url59FORGE_PREVIEWS_URL=$previews_url60EOF61chown git:git "$root/.forge/config"6263if ! grep -q "^$owner " "$root/.forge/keys" 2>/dev/null; then64 cp "$key" /tmp/forge-owner.pub65 chmod 644 /tmp/forge-owner.pub66 as_git "$app/bin/forge user add $owner /tmp/forge-owner.pub"67 rm -f /tmp/forge-owner.pub68fi6970as_git "FORGE_ROOT=$root $app/bin/forge server caddyfile '$addr' '$pages'" >/etc/caddy/Caddyfile7172as_git "$app/bin/forge build" || true7374# unit <name> <user> <script> <description>: a Node server behind Caddy.75unit() {76 cat >"/etc/systemd/system/$1.service" <<EOF77[Unit]78Description=$479After=network.target8081[Service]82User=$283Environment=FORGE_ROOT=$root FORGE_STATE=$root/.forge84EnvironmentFile=-$root/.forge/config85ExecStart=$(command -v node) $app/$386Restart=always8788[Install]89WantedBy=multi-user.target90EOF91}9293if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then94 # Live page updates, proxied at /events/.95 unit forge-events git lib/events-server.ts "forge live page updates"96 # Private repos' pages, and the 404 of every missing page: Caddy sends it97 # whatever www has no file for.98 unit forge-private git lib/private-server.ts "forge private pages"99 units="forge-events forge-private"100 # Sites on the pages domain. Runs as caddy: deploys are untrusted content.101 if [ -n "$pages" ]; then102 unit forge-pages caddy lib/pages-server.ts "forge repo sites"103 units="$units forge-pages"104 fi105 systemctl daemon-reload106 systemctl enable --now ssh caddy $units107 systemctl restart $units108 systemctl reload caddy109fi110111cat <<EOF112113forge is set up.114 pages: https://$domain/115 git: git@$domain:$owner/<repo>.git116 create: ssh git@$domain new <repo> "description"117 import: forge import --to git@$domain <any-git-url>118EOF