🌿 Collaborative wiki on ATProto lichen.wiki
atproto
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add deployment scripts

juprodh 8805caf6 b69556e8

+243
+22
deploy/Caddyfile
··· 1 + lichen.wiki { 2 + tls { 3 + dns cloudflare {env.CLOUDFLARE_API_TOKEN} 4 + } 5 + 6 + encode gzip 7 + 8 + # Immutable blob cache headers (PDS-proxied blobs) 9 + handle /blob/* { 10 + header Cache-Control "public, max-age=31536000, immutable" 11 + reverse_proxy localhost:3000 12 + } 13 + 14 + # Static assets with long cache 15 + handle /public/* { 16 + header Cache-Control "public, max-age=86400" 17 + reverse_proxy localhost:3000 18 + } 19 + 20 + # Everything else 21 + reverse_proxy localhost:3000 22 + }
+13
deploy/caddy.service
··· 1 + [Unit] 2 + Description=Caddy web server 3 + After=network.target 4 + 5 + [Service] 6 + Type=simple 7 + ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile --envfile /etc/lichen/env 8 + Restart=on-failure 9 + RestartSec=5 10 + AmbientCapabilities=CAP_NET_BIND_SERVICE 11 + 12 + [Install] 13 + WantedBy=multi-user.target
+79
deploy/deploy.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + VPS="lichen" 5 + APP_DIR="/opt/lichen" 6 + DATA_DIR="/var/lib/lichen" 7 + PREV_DIR="/opt/lichen-prev" 8 + DEPLOY_LOG="/var/log/lichen-deploy.log" 9 + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") 10 + 11 + log() { echo "[$TIMESTAMP] $1"; } 12 + 13 + log "==> Building assets" 14 + bun run build:css 15 + bun run build:editor 16 + bun run build:viz 17 + 18 + log "==> Backing up DB and previous deploy on VPS" 19 + ssh "$VPS" " 20 + sudo cp ${DATA_DIR}/lichen.db ${DATA_DIR}/lichen-pre-deploy.db 2>/dev/null || true 21 + sudo rm -rf ${PREV_DIR} 22 + sudo cp -a ${APP_DIR} ${PREV_DIR} 2>/dev/null || true 23 + echo '${TIMESTAMP} deploy started' | sudo tee -a ${DEPLOY_LOG} > /dev/null 24 + " 25 + 26 + log "==> Syncing to VPS" 27 + rsync -avz --delete \ 28 + --exclude node_modules \ 29 + --exclude '*.db' \ 30 + --exclude '*.db-shm' \ 31 + --exclude '*.db-wal' \ 32 + --exclude data/ \ 33 + --exclude .git \ 34 + --rsync-path="sudo rsync" \ 35 + ./ "${VPS}:${APP_DIR}/" 36 + 37 + log "==> Installing deps + patching jose for Bun" 38 + ssh "$VPS" " 39 + sudo chown -R lichen:lichen ${APP_DIR} 40 + sudo -u lichen bash -c 'cd ${APP_DIR} && bun install --production' 41 + sudo sed -i 's|\"bun\": \"./dist/browser/|\"bun\": \"./dist/node/esm/|g' ${APP_DIR}/node_modules/jose/package.json 42 + " 43 + 44 + log "==> Updating config files" 45 + ssh "$VPS" " 46 + sudo mkdir -p /etc/caddy 47 + sudo cp ${APP_DIR}/deploy/Caddyfile /etc/caddy/Caddyfile 48 + sudo cp ${APP_DIR}/deploy/caddy.service /etc/systemd/system/ 49 + sudo cp ${APP_DIR}/deploy/lichen-appview.service /etc/systemd/system/ 50 + sudo cp ${APP_DIR}/deploy/lichen-firehose.service /etc/systemd/system/ 51 + sudo cp ${APP_DIR}/deploy/litestream.yml /etc/litestream.yml 52 + sudo systemctl daemon-reload 53 + " 54 + 55 + log "==> Restarting services" 56 + ssh "$VPS" "sudo systemctl restart caddy lichen-appview lichen-firehose" 57 + 58 + log "==> Health check (waiting 3s for startup)" 59 + sleep 3 60 + STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 https://lichen.wiki/) 61 + 62 + if [ "$STATUS" = "200" ]; then 63 + log "==> Health check passed (HTTP $STATUS)" 64 + ssh "$VPS" "echo '${TIMESTAMP} deploy succeeded (HTTP ${STATUS})' | sudo tee -a ${DEPLOY_LOG} > /dev/null" 65 + else 66 + log "!!! Health check FAILED (HTTP $STATUS)" 67 + log "!!! Rolling back to previous deploy" 68 + ssh "$VPS" " 69 + sudo rm -rf ${APP_DIR} 70 + sudo mv ${PREV_DIR} ${APP_DIR} 71 + sudo cp ${DATA_DIR}/lichen-pre-deploy.db ${DATA_DIR}/lichen.db 72 + sudo systemctl restart lichen-appview lichen-firehose 73 + echo '${TIMESTAMP} deploy FAILED (HTTP ${STATUS}), rolled back' | sudo tee -a ${DEPLOY_LOG} > /dev/null 74 + " 75 + log "==> Rolled back. Check logs: ssh ${VPS} 'sudo journalctl -u lichen-appview -n 50'" 76 + exit 1 77 + fi 78 + 79 + log "==> Done!"
+23
deploy/lichen-appview.service
··· 1 + [Unit] 2 + Description=Lichen Appview 3 + After=network.target 4 + 5 + [Service] 6 + Type=simple 7 + User=lichen 8 + Group=lichen 9 + WorkingDirectory=/opt/lichen 10 + ExecStart=/usr/local/bin/bun run src/server/index.ts 11 + Restart=on-failure 12 + RestartSec=5 13 + EnvironmentFile=/etc/lichen/env 14 + 15 + # Hardening 16 + NoNewPrivileges=true 17 + ProtectSystem=strict 18 + ProtectHome=true 19 + ReadWritePaths=/var/lib/lichen 20 + PrivateTmp=true 21 + 22 + [Install] 23 + WantedBy=multi-user.target
+23
deploy/lichen-firehose.service
··· 1 + [Unit] 2 + Description=Lichen Firehose Subscriber 3 + After=network.target lichen-appview.service 4 + 5 + [Service] 6 + Type=simple 7 + User=lichen 8 + Group=lichen 9 + WorkingDirectory=/opt/lichen 10 + ExecStart=/usr/local/bin/bun run src/firehose/index.ts 11 + Restart=on-failure 12 + RestartSec=5 13 + EnvironmentFile=/etc/lichen/env 14 + 15 + # Hardening 16 + NoNewPrivileges=true 17 + ProtectSystem=strict 18 + ProtectHome=true 19 + ReadWritePaths=/var/lib/lichen 20 + PrivateTmp=true 21 + 22 + [Install] 23 + WantedBy=multi-user.target
+5
deploy/litestream.yml
··· 1 + dbs: 2 + - path: /var/lib/lichen/lichen.db 3 + replicas: 4 + - type: file 5 + path: /var/lib/lichen/backups
+78
deploy/setup.sh
··· 1 + #!/usr/bin/env bash 2 + # Usage: ssh into VPS, then: sudo bash /opt/lichen/deploy/setup.sh 3 + set -euo pipefail 4 + 5 + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 6 + APP_USER="lichen" 7 + APP_DIR="/opt/lichen" 8 + DATA_DIR="/var/lib/lichen" 9 + 10 + echo "==> Updating system" 11 + apt-get update && apt-get upgrade -y 12 + apt-get install -y curl unzip git rsync 13 + 14 + echo "==> Installing Bun" 15 + if ! command -v bun &>/dev/null; then 16 + curl -fsSL https://bun.sh/install | bash 17 + cp ~/.bun/bin/bun /usr/local/bin/bun 18 + fi 19 + bun --version 20 + 21 + echo "==> Installing Caddy" 22 + if ! command -v caddy &>/dev/null; then 23 + curl -o /usr/bin/caddy "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fcaddy-dns%2Fcloudflare" 24 + chmod +x /usr/bin/caddy 25 + fi 26 + caddy version 27 + 28 + echo "==> Installing Litestream" 29 + if ! command -v litestream &>/dev/null; then 30 + curl -fsSL "https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.deb" -o /tmp/litestream.deb 31 + dpkg -i /tmp/litestream.deb 32 + rm /tmp/litestream.deb 33 + fi 34 + litestream version 35 + 36 + echo "==> Creating app user and directories" 37 + if ! id "$APP_USER" &>/dev/null; then 38 + useradd --system --create-home --shell /usr/sbin/nologin "$APP_USER" 39 + fi 40 + mkdir -p "$APP_DIR" "$DATA_DIR" "$DATA_DIR/backups" 41 + chown "$APP_USER:$APP_USER" "$DATA_DIR" "$DATA_DIR/backups" 42 + 43 + KEY_PATH="$DATA_DIR/oauth-private-key.pem" 44 + if [ ! -f "$KEY_PATH" ]; then 45 + echo "==> Generating ES256 private key" 46 + openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out "$KEY_PATH" 47 + chown "$APP_USER:$APP_USER" "$KEY_PATH" 48 + chmod 600 "$KEY_PATH" 49 + fi 50 + 51 + ENV_FILE="/etc/lichen/env" 52 + if [ ! -f "$ENV_FILE" ]; then 53 + echo "==> Creating environment file (edit CLOUDFLARE_API_TOKEN before starting)" 54 + mkdir -p /etc/lichen 55 + cat > "$ENV_FILE" <<'EOF' 56 + PUBLIC_URL=https://lichen.wiki 57 + OAUTH_PRIVATE_KEY_PATH= 58 + RELAY_URL=wss://bsky.network 59 + DB_PATH=/var/lib/lichen/lichen.db 60 + CLOUDFLARE_API_TOKEN= 61 + EOF 62 + chmod 600 "$ENV_FILE" 63 + fi 64 + 65 + echo "==> Installing config files" 66 + mkdir -p /etc/caddy 67 + cp "$SCRIPT_DIR/Caddyfile" /etc/caddy/Caddyfile 68 + cp "$SCRIPT_DIR/caddy.service" /etc/systemd/system/ 69 + cp "$SCRIPT_DIR/lichen-appview.service" /etc/systemd/system/ 70 + cp "$SCRIPT_DIR/lichen-firehose.service" /etc/systemd/system/ 71 + cp "$SCRIPT_DIR/litestream.yml" /etc/litestream.yml 72 + systemctl daemon-reload 73 + 74 + echo "" 75 + echo "==> Setup complete!" 76 + echo " 1. Edit /etc/lichen/env — set CLOUDFLARE_API_TOKEN" 77 + echo " 2. Deploy code: ./deploy/deploy.sh" 78 + echo " 3. Start: sudo systemctl enable --now caddy lichen-appview lichen-firehose litestream"