Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/usr/bin/env fish
2# lith Deployment Script
3# Deploys the AC monolith (frontend + API) to DigitalOcean droplet
4
5set RED '\033[0;31m'
6set GREEN '\033[0;32m'
7set YELLOW '\033[1;33m'
8set NC '\033[0m'
9
10set SCRIPT_DIR (dirname (status --current-filename))
11set REPO_ROOT (realpath "$SCRIPT_DIR/..")
12set VAULT_DIR "$SCRIPT_DIR/../aesthetic-computer-vault"
13set SSH_KEY "$VAULT_DIR/home/.ssh/id_rsa"
14set SERVICE_ENV "$VAULT_DIR/lith/.env"
15set LITH_USER "root"
16set REMOTE_DIR "/opt/ac"
17set DEFAULT_LITH_HOST "lith.aesthetic.computer"
18set DEFAULT_LITH_DROPLET_NAME "ac-lith"
19set TARGET_HOST $DEFAULT_LITH_HOST
20set TARGET_DROPLET_NAME $DEFAULT_LITH_DROPLET_NAME
21set LOCAL_BRANCH (git -C $REPO_ROOT branch --show-current 2>/dev/null)
22set TARGET_BRANCH $LOCAL_BRANCH
23
24if set -q LITH_HOST
25 set TARGET_HOST $LITH_HOST
26end
27
28if set -q LITH_DROPLET_NAME
29 set TARGET_DROPLET_NAME $LITH_DROPLET_NAME
30end
31
32if set -q DEPLOY_BRANCH
33 set TARGET_BRANCH $DEPLOY_BRANCH
34end
35
36if test -z "$TARGET_BRANCH"
37 set TARGET_BRANCH main
38end
39
40function ssh_ok --argument host
41 ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o ConnectTimeout=10 $LITH_USER@$host "echo ok" &>/dev/null
42end
43
44function get_do_token
45 if set -q DIGITALOCEAN_ACCESS_TOKEN
46 echo $DIGITALOCEAN_ACCESS_TOKEN
47 return 0
48 end
49
50 if set -q DO_TOKEN
51 echo $DO_TOKEN
52 return 0
53 end
54
55 for token_file in \
56 "$VAULT_DIR/help/deploy.env" \
57 "$VAULT_DIR/judge/deploy.env" \
58 "$VAULT_DIR/oven/deploy.env" \
59 "$VAULT_DIR/at/deploy.env"
60 if not test -f $token_file
61 continue
62 end
63
64 set token_line (rg -m1 '^DO_TOKEN=' $token_file)
65 if test -n "$token_line"
66 string replace -r '^DO_TOKEN=' '' -- $token_line
67 return 0
68 end
69 end
70
71 return 1
72end
73
74function get_lith_host_from_do
75 if not command -sq doctl
76 return 1
77 end
78
79 set do_token (get_do_token)
80 if test -z "$do_token"
81 return 1
82 end
83
84 set row (env DIGITALOCEAN_ACCESS_TOKEN="$do_token" \
85 doctl compute droplet list --format Name,PublicIPv4 --no-header 2>/dev/null | \
86 rg "^$TARGET_DROPLET_NAME\\s")
87
88 if test -z "$row"
89 return 1
90 end
91
92 set compact_row (string replace -ra '\s+' ' ' -- (string trim -- $row))
93 set fields (string split ' ' -- $compact_row)
94
95 if test (count $fields) -lt 2
96 return 1
97 end
98
99 echo $fields[2]
100end
101
102# Check for required files
103if not test -f $SSH_KEY
104 echo -e "$RED x SSH key not found: $SSH_KEY$NC"
105 exit 1
106end
107
108if not test -f $SERVICE_ENV
109 echo -e "$RED x Service env not found: $SERVICE_ENV$NC"
110 exit 1
111end
112
113if not rg -q '^DEPLOY_SECRET=' $SERVICE_ENV
114 echo -e "$RED x DEPLOY_SECRET missing from $SERVICE_ENV$NC"
115 echo -e "$YELLOW lith reads this file via /opt/ac/system/.env on the server.$NC"
116 exit 1
117end
118
119# Test SSH connection
120echo -e "$GREEN-> Testing SSH connection to $TARGET_HOST...$NC"
121if not ssh_ok $TARGET_HOST
122 set fallback_host (get_lith_host_from_do)
123
124 if test -n "$fallback_host"; and test "$fallback_host" != "$TARGET_HOST"
125 echo -e "$YELLOW Falling back to DigitalOcean droplet $TARGET_DROPLET_NAME at $fallback_host.$NC"
126 set TARGET_HOST $fallback_host
127 end
128
129 if not ssh_ok $TARGET_HOST
130 echo -e "$RED x Cannot connect to $TARGET_HOST$NC"
131 exit 1
132 end
133end
134
135echo -e "$GREEN-> Connected to $TARGET_HOST.$NC"
136
137# Deploy from pushed git state only. This avoids production drift from local rsync overlays.
138echo -e "$GREEN-> Verifying origin/$TARGET_BRANCH...$NC"
139git -C $REPO_ROOT fetch origin $TARGET_BRANCH --quiet
140set ORIGIN_HEAD (git -C $REPO_ROOT rev-parse origin/$TARGET_BRANCH)
141
142if test "$LOCAL_BRANCH" = "$TARGET_BRANCH"
143 set LOCAL_HEAD (git -C $REPO_ROOT rev-parse HEAD)
144 if test "$LOCAL_HEAD" != "$ORIGIN_HEAD"
145 echo -e "$RED x Local $TARGET_BRANCH is ahead of origin/$TARGET_BRANCH.$NC"
146 echo -e "$YELLOW Push first. This deploy script no longer rsyncs uncommitted or unpushed code into production.$NC"
147 exit 1
148 end
149end
150
151echo -e "$GREEN-> Deploying branch $TARGET_BRANCH at $ORIGIN_HEAD...$NC"
152ssh -i $SSH_KEY $LITH_USER@$TARGET_HOST "\
153cd $REMOTE_DIR && \
154git fetch origin $TARGET_BRANCH --quiet && \
155if git show-ref --verify --quiet refs/heads/$TARGET_BRANCH; then \
156 git checkout $TARGET_BRANCH --quiet; \
157else \
158 git checkout -B $TARGET_BRANCH origin/$TARGET_BRANCH --quiet; \
159fi && \
160git reset --hard origin/$TARGET_BRANCH --quiet && \
161git rev-parse HEAD > system/public/.commit-ref"
162
163# Upload env
164echo -e "$GREEN-> Uploading environment...$NC"
165# Note: lith.service reads EnvironmentFile=/opt/ac/system/.env, so the
166# canonical vault source lives at aesthetic-computer-vault/lith/.env and is
167# uploaded into system/.env on the remote host.
168scp -i $SSH_KEY $SERVICE_ENV $LITH_USER@$TARGET_HOST:$REMOTE_DIR/system/.env
169
170# Install deps
171echo -e "$GREEN-> Installing dependencies...$NC"
172ssh -i $SSH_KEY $LITH_USER@$TARGET_HOST "cd $REMOTE_DIR/lith && npm install --omit=dev && cd $REMOTE_DIR/system && npm install --omit=dev"
173
174# Install service file + Caddy config from the deployed checkout
175echo -e "$GREEN-> Updating service + Caddy config...$NC"
176ssh -i $SSH_KEY $LITH_USER@$TARGET_HOST "\
177cp $REMOTE_DIR/lith/lith.service /etc/systemd/system/lith.service && \
178cp $REMOTE_DIR/lith/Caddyfile /etc/caddy/Caddyfile && \
179systemctl daemon-reload && \
180systemctl reload caddy"
181
182# Restart lith service
183echo -e "$GREEN-> Restarting lith...$NC"
184ssh -i $SSH_KEY $LITH_USER@$TARGET_HOST "systemctl restart lith"
185
186echo -e "$GREEN-> Done. lith deployed to $TARGET_HOST$NC"