Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/bin/bash
2# Fast oven deploy with verbose output
3# Usage: ./deploy.sh [--no-restart]
4
5set -e
6
7OVEN_HOST="137.184.237.166"
8SSH_KEY="${SSH_KEY:-$(dirname "$0")/../aesthetic-computer-vault/oven/ssh/oven-deploy-key}"
9REMOTE_DIR="/opt/oven"
10NATIVE_GIT_FETCH_URL="${NATIVE_GIT_FETCH_URL:-https://tangled.org/aesthetic.computer/core.git}"
11SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12AC_SOURCE="$SCRIPT_DIR/../system/public/aesthetic.computer"
13FEDAC_SOURCE="$SCRIPT_DIR/../fedac"
14VAULT_OS_KEY="$SCRIPT_DIR/../aesthetic-computer-vault/oven/os-build-admin-key.txt"
15
16echo "🚀 Deploying oven..."
17echo " Host: $OVEN_HOST"
18echo " Key: $SSH_KEY"
19
20# Get current git version for OVEN_VERSION env var
21GIT_VERSION=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
22echo " Version: $GIT_VERSION"
23
24# Time the rsync
25START_TIME=$(date +%s%3N)
26
27echo ""
28echo "📦 Syncing oven files..."
29rsync -avz --progress --delete \
30 --exclude='node_modules' \
31 --exclude='.git' \
32 --exclude='*.log' \
33 --exclude='ac-source' \
34 --exclude='native-git' \
35 --exclude='secrets' \
36 -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \
37 "$SCRIPT_DIR/" \
38 "root@$OVEN_HOST:$REMOTE_DIR/"
39
40END_SYNC=$(date +%s%3N)
41SYNC_TIME=$((END_SYNC - START_TIME))
42echo ""
43echo "✅ Oven sync complete in ${SYNC_TIME}ms"
44
45# Sync aesthetic.computer source files needed for bundle generation
46echo ""
47echo "📦 Syncing ac-source files for bundler..."
48rsync -avz --progress --delete \
49 --include='*/' \
50 --include='*.mjs' \
51 --include='*.js' \
52 --include='*.json' \
53 --include='*.lisp' \
54 --exclude='*' \
55 -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \
56 "$AC_SOURCE/" \
57 "root@$OVEN_HOST:$REMOTE_DIR/ac-source/"
58
59END_AC_SYNC=$(date +%s%3N)
60AC_SYNC_TIME=$((END_AC_SYNC - END_SYNC))
61echo ""
62echo "✅ ac-source sync complete in ${AC_SYNC_TIME}ms"
63
64# Sync fedac scripts/overlays used by background OS base-image build jobs
65echo ""
66echo "📦 Syncing fedac OS build pipeline..."
67rsync -avz --progress --delete \
68 --exclude='.git' \
69 --exclude='*.img' \
70 --exclude='*.iso' \
71 --exclude='*.qcow2' \
72 --exclude='*.log' \
73 --exclude='native/build/' \
74 -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \
75 "$FEDAC_SOURCE/" \
76 "root@$OVEN_HOST:$REMOTE_DIR/fedac/"
77
78END_FEDAC_SYNC=$(date +%s%3N)
79FEDAC_SYNC_TIME=$((END_FEDAC_SYNC - END_AC_SYNC))
80echo ""
81echo "✅ fedac sync complete in ${FEDAC_SYNC_TIME}ms"
82
83# Install kernel build tools needed for native OTA builds (idempotent)
84echo ""
85echo "🔧 Installing native kernel build tools..."
86ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" \
87 "apt-get install -y -q gcc make flex bison libelf-dev libssl-dev bc cpio lz4 musl-tools python3 pahole libdrm-dev libasound2-dev flite1-dev pkg-config dosfstools mtools util-linux 2>&1 | tail -5 || true"
88echo "✅ Kernel build tools ready"
89
90# Install Nix package manager for NixOS-based native builds (idempotent)
91echo ""
92echo "❄️ Installing Nix package manager..."
93ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" bash -s <<'NIX_EOF'
94 if command -v nix >/dev/null 2>&1; then
95 echo "Nix already installed: $(nix --version)"
96 else
97 curl -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm 2>&1 | tail -10
98 fi
99 NIX_BIN=""
100 for candidate in \
101 /nix/var/nix/profiles/default/bin/nix \
102 /root/.nix-profile/bin/nix \
103 /home/oven/.nix-profile/bin/nix; do
104 if [ -x "$candidate" ]; then
105 NIX_BIN="$candidate"
106 break
107 fi
108 done
109 if [ -n "$NIX_BIN" ]; then
110 ln -sf "$NIX_BIN" /usr/local/bin/nix
111 NIX_GC_BIN="$(dirname "$NIX_BIN")/nix-collect-garbage"
112 if [ -x "$NIX_GC_BIN" ]; then
113 ln -sf "$NIX_GC_BIN" /usr/local/bin/nix-collect-garbage
114 fi
115 echo "Nix binary: $NIX_BIN"
116 else
117 echo "WARNING: nix binary not found after install"
118 fi
119 if id -u oven >/dev/null 2>&1; then
120 mkdir -p /home/oven/.cache/nix
121 chown -R oven:oven /home/oven/.cache
122 fi
123 # Enable flakes
124 mkdir -p /etc/nix
125 grep -q 'experimental-features' /etc/nix/nix.conf 2>/dev/null || \
126 echo 'experimental-features = nix-command flakes' >> /etc/nix/nix.conf
127 # Garbage collection timer (weekly, keep 7 days)
128 if ! systemctl is-enabled nix-gc.timer >/dev/null 2>&1; then
129 cat > /etc/systemd/system/nix-gc.service <<'SVC'
130[Unit]
131Description=Nix store garbage collection
132[Service]
133Type=oneshot
134ExecStart=/nix/var/nix/profiles/default/bin/nix-collect-garbage --delete-older-than 7d
135SVC
136 cat > /etc/systemd/system/nix-gc.timer <<'TMR'
137[Unit]
138Description=Weekly Nix garbage collection
139[Timer]
140OnCalendar=weekly
141Persistent=true
142[Install]
143WantedBy=timers.target
144TMR
145 systemctl daemon-reload
146 systemctl enable --now nix-gc.timer
147 fi
148NIX_EOF
149echo "✅ Nix ready"
150
151# Install TeX Live for papers PDF builds (idempotent)
152echo ""
153echo "📄 Installing TeX Live for papers builds..."
154ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" \
155 "apt-get install -y -q texlive-xetex texlive-fonts-extra texlive-latex-extra texlive-bibtex-extra texlive-lang-chinese texlive-lang-cjk fonts-droid-fallback 2>&1 | tail -5 || true"
156echo "✅ TeX Live ready"
157
158# Optional vault-managed admin key for /os-base-build endpoints
159echo ""
160if [ -f "$VAULT_OS_KEY" ]; then
161 echo "🔐 Syncing OS build admin key from vault..."
162 ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" "
163mkdir -p $REMOTE_DIR/secrets
164chmod 700 $REMOTE_DIR/secrets
165if id -u oven >/dev/null 2>&1; then
166 chown oven:oven $REMOTE_DIR/secrets
167fi
168"
169 rsync -avz --progress \
170 -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \
171 "$VAULT_OS_KEY" \
172 "root@$OVEN_HOST:$REMOTE_DIR/secrets/os-build-admin-key.txt"
173 ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" "
174chmod 600 $REMOTE_DIR/secrets/os-build-admin-key.txt
175if id -u oven >/dev/null 2>&1; then
176 chown oven:oven $REMOTE_DIR/secrets/os-build-admin-key.txt
177fi
178if grep -q '^OS_BUILD_ADMIN_KEY_FILE=' $REMOTE_DIR/.env 2>/dev/null; then
179 sed -i 's|^OS_BUILD_ADMIN_KEY_FILE=.*|OS_BUILD_ADMIN_KEY_FILE=$REMOTE_DIR/secrets/os-build-admin-key.txt|' $REMOTE_DIR/.env
180else
181 echo 'OS_BUILD_ADMIN_KEY_FILE=$REMOTE_DIR/secrets/os-build-admin-key.txt' >> $REMOTE_DIR/.env
182fi
183"
184 echo "✅ OS build admin key synced"
185else
186 echo "⚠️ No vault key at $VAULT_OS_KEY (skipping OS_BUILD_ADMIN_KEY_FILE provisioning)"
187fi
188
189END_SECRET_SYNC=$(date +%s%3N)
190SECRET_SYNC_TIME=$((END_SECRET_SYNC - END_FEDAC_SYNC))
191echo "✅ Secret sync stage complete in ${SECRET_SYNC_TIME}ms"
192
193# Sync BDF font files + glyph caches for bundle font embedding
194echo ""
195echo "📦 Syncing font assets (BDF + glyph caches)..."
196rsync -avz --progress \
197 --include='*/' \
198 --include='*.bdf' \
199 --include='*.bdf.gz' \
200 --include='*.json' \
201 --exclude='*' \
202 -e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \
203 "$SCRIPT_DIR/../system/public/assets/type/" \
204 "root@$OVEN_HOST:$REMOTE_DIR/assets-type/"
205
206END_FONT_SYNC=$(date +%s%3N)
207FONT_SYNC_TIME=$((END_FONT_SYNC - END_SECRET_SYNC))
208echo ""
209echo "✅ Font glyph sync complete in ${FONT_SYNC_TIME}ms"
210
211# Ensure OS cache path is writable by the oven service user.
212echo ""
213echo "🧹 Ensuring OS cache directory + permissions..."
214ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" "
215mkdir -p $REMOTE_DIR/cache
216if id -u oven >/dev/null 2>&1; then
217 chown -R oven:oven $REMOTE_DIR
218fi
219if grep -q '^OS_CACHE_DIR=' $REMOTE_DIR/.env 2>/dev/null; then
220 sed -i 's|^OS_CACHE_DIR=.*|OS_CACHE_DIR=$REMOTE_DIR/cache|' $REMOTE_DIR/.env
221else
222 echo 'OS_CACHE_DIR=$REMOTE_DIR/cache' >> $REMOTE_DIR/.env
223fi
224"
225echo "✅ OS cache path ready: $REMOTE_DIR/cache"
226
227# Set up native git repo for auto-polling OTA builds
228echo ""
229echo "📦 Setting up native git repo for OTA auto-builds..."
230echo " Fetch remote: $NATIVE_GIT_FETCH_URL"
231ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" "
232set -euo pipefail
233NATIVE_GIT_DIR=/opt/oven/native-git
234NATIVE_GIT_FETCH_URL='$NATIVE_GIT_FETCH_URL'
235if [ ! -d \$NATIVE_GIT_DIR/.git ]; then
236 echo ' Cloning repo (first time)...'
237 git clone --branch main --single-branch \$NATIVE_GIT_FETCH_URL \$NATIVE_GIT_DIR
238else
239 echo ' Git repo exists, fetching latest...'
240 cd \$NATIVE_GIT_DIR
241 git remote set-url origin \$NATIVE_GIT_FETCH_URL
242 git fetch origin main --quiet || true
243 git branch --set-upstream-to=origin/main main >/dev/null 2>&1 || true
244 if git rev-parse --verify origin/main >/dev/null 2>&1 && git merge-base --is-ancestor HEAD origin/main; then
245 git merge origin/main --ff-only --quiet || true
246 else
247 echo ' Repo has local commits or divergence; leaving checkout as-is (native build preflight will hard-sync to origin/main).'
248 fi
249fi
250if id -u oven >/dev/null 2>&1; then
251 chown -R oven:oven \$NATIVE_GIT_DIR
252 su - oven -s /bin/bash -c 'git config --global --add safe.directory /opt/oven/native-git' 2>/dev/null || true
253fi
254echo ' Done.'
255"
256echo "✅ Native git repo ready"
257
258# Configure git push credentials for papers PDF auto-push
259echo ""
260echo "🔑 Configuring git push credentials for papers auto-build..."
261ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" "
262NATIVE_GIT_DIR=/opt/oven/native-git
263# Read OVEN_GH_TOKEN from .env if it exists
264OVEN_GH_TOKEN=\$(grep '^OVEN_GH_TOKEN=' $REMOTE_DIR/.env 2>/dev/null | cut -d= -f2-)
265if [ -n \"\$OVEN_GH_TOKEN\" ]; then
266 cd \$NATIVE_GIT_DIR
267 # Keep fetch on Tangled, but push papers auto-build commits to the GitHub mirror.
268 git remote set-url --push origin https://x-access-token:\${OVEN_GH_TOKEN}@github.com/whistlegraph/aesthetic-computer.git
269 echo ' Push URL configured with token auth'
270else
271 echo ' WARNING: OVEN_GH_TOKEN not found in $REMOTE_DIR/.env — papers auto-push will fail'
272 echo ' Add OVEN_GH_TOKEN=ghp_... to aesthetic-computer-vault/oven/.env and re-deploy'
273fi
274"
275echo "✅ Git push credentials configured"
276
277# Restart unless --no-restart flag
278if [ "$1" != "--no-restart" ]; then
279 echo ""
280 echo "🔄 Restarting oven service..."
281
282 # Update OVEN_VERSION in .env and rewrite the managed systemd override, then restart
283 ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" "
284set -euo pipefail
285cd $REMOTE_DIR
286# Update version in .env
287if grep -q '^OVEN_VERSION=' .env 2>/dev/null; then
288 sed -i 's/^OVEN_VERSION=.*/OVEN_VERSION=$GIT_VERSION/' .env
289else
290 echo 'OVEN_VERSION=$GIT_VERSION' >> .env
291fi
292install -m 0644 $REMOTE_DIR/infra/oven.service /etc/systemd/system/oven.service
293# Rewrite systemd override from scratch so stale directives do not survive deploys.
294mkdir -p /etc/systemd/system/oven.service.d
295cat > /etc/systemd/system/oven.service.d/override.conf <<EOF
296[Service]
297Environment=OVEN_VERSION=$GIT_VERSION
298Environment=PATH=/usr/local/bin:/nix/var/nix/profiles/default/bin:/home/oven/.nix-profile/bin:/root/.nix-profile/bin:/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin
299LimitNOFILE=65536
300EOF
301systemctl daemon-reload
302systemctl restart oven
303sleep 2
304systemctl is-active --quiet oven.service
305systemctl status oven.service --no-pager | sed -n '1,5p'
306"
307
308 END_RESTART=$(date +%s%3N)
309 RESTART_TIME=$((END_RESTART - END_FONT_SYNC))
310
311 echo ""
312 echo "✅ Restart complete in ${RESTART_TIME}ms"
313
314 # Prewarm the bundle cache after restart
315 echo ""
316 echo "🔥 Prewarming bundle cache..."
317 PREWARM_RESULT=$(ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no "root@$OVEN_HOST" \
318 "curl -s -X POST http://localhost:3002/bundle-prewarm --max-time 120" 2>/dev/null || echo '{"error":"prewarm timeout"}')
319 echo " $PREWARM_RESULT"
320
321 END_PREWARM=$(date +%s%3N)
322 PREWARM_TIME=$((END_PREWARM - END_RESTART))
323 TOTAL_TIME=$((END_PREWARM - START_TIME))
324
325 echo ""
326 echo "✅ Prewarm complete in ${PREWARM_TIME}ms"
327 echo "🏁 Total deploy time: ${TOTAL_TIME}ms"
328else
329 echo ""
330 echo "⏭️ Skipped restart (--no-restart)"
331fi
332
333echo ""
334echo "🔥 Done! https://oven.aesthetic.computer"