Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/usr/bin/env fish
2# Oven Deployment Script
3#!/usr/bin/env fish
4# Oven Deployment Script
5# Provisions and deploys the Oven video processing service to DigitalOcean
6
7# Colors for output
8set RED '\033[0;31m'
9set GREEN '\033[0;32m'
10set YELLOW '\033[1;33m'
11set NC '\033[0m' # No Color
12
13# Paths
14set SCRIPT_DIR (dirname (status --current-filename))
15set VAULT_DIR "$SCRIPT_DIR/../aesthetic-computer-vault/oven"
16set DEPLOY_ENV "$VAULT_DIR/deploy.env"
17set SERVICE_ENV "$VAULT_DIR/.env"
18
19# Check for required files
20if not test -f $DEPLOY_ENV
21 echo -e "$RED❌ Deployment config not found: $DEPLOY_ENV$NC"
22 exit 1
23end
24
25if not test -f $SERVICE_ENV
26 echo -e "$RED❌ Service config not found: $SERVICE_ENV$NC"
27 exit 1
28end
29
30# Load deployment configuration (parse bash-style env file)
31echo -e "$GREEN🔧 Loading deployment configuration...$NC"
32for line in (cat $DEPLOY_ENV | grep -v '^#' | grep -v '^$' | grep '=')
33 set -l parts (string split '=' $line)
34 if test (count $parts) -ge 2
35 set -gx $parts[1] (string join '=' $parts[2..-1])
36 end
37end
38
39set -e
40
41# Colors for output
42set RED '\033[0;31m'
43set GREEN '\033[0;32m'
44set YELLOW '\033[1;33m'
45set NC '\033[0m' # No Color
46
47# Paths
48set SCRIPT_DIR (dirname (status --current-filename))
49set VAULT_DIR "$SCRIPT_DIR/../aesthetic-computer-vault/oven"
50set DEPLOY_ENV "$VAULT_DIR/deploy.env"
51set SERVICE_ENV "$VAULT_DIR/.env"
52
53# Check for required files
54if not test -f $DEPLOY_ENV
55 echo -e "$RED❌ Deployment config not found: $DEPLOY_ENV$NC"
56 exit 1
57end
58
59if not test -f $SERVICE_ENV
60 echo -e "$RED❌ Service config not found: $SERVICE_ENV$NC"
61 exit 1
62end
63
64# Load deployment configuration
65echo -e "$GREEN🔧 Loading deployment configuration...$NC"
66source $DEPLOY_ENV
67
68# Check for required tools
69if not command -v doctl &> /dev/null
70 echo -e "$RED❌ doctl not found. Install with: wget https://github.com/digitalocean/doctl/releases/download/v1.109.0/doctl-1.109.0-linux-amd64.tar.gz$NC"
71 exit 1
72end
73
74if not command -v node &> /dev/null
75 echo -e "$RED❌ node not found$NC"
76 exit 1
77end
78
79# Authenticate with DigitalOcean
80echo -e "$GREEN🔑 Authenticating with DigitalOcean...$NC"
81doctl auth init --access-token $DO_TOKEN
82
83# Check if droplet already exists
84echo -e "$GREEN🔍 Checking for existing droplet...$NC"
85set EXISTING_DROPLET (doctl compute droplet list --format Name,PublicIPv4 | grep "^$DROPLET_NAME" | awk '{print $2}')
86
87if test -n "$EXISTING_DROPLET"
88 echo -e "$YELLOW⚠️ Droplet $DROPLET_NAME already exists at $EXISTING_DROPLET$NC"
89 echo -e "$YELLOW Would you like to redeploy to the existing droplet? (y/n)$NC"
90 read -l CONFIRM
91
92 if test "$CONFIRM" != "y"
93 echo -e "$RED❌ Deployment cancelled$NC"
94 exit 1
95 end
96
97 set DROPLET_IP $EXISTING_DROPLET
98else
99 # Create SSH key
100 echo -e "$GREEN🔑 Creating SSH key...$NC"
101 set SSH_KEY_FILE "$HOME/.ssh/$SSH_KEY_NAME"
102
103 if not test -f $SSH_KEY_FILE
104 ssh-keygen -t ed25519 -f $SSH_KEY_FILE -N "" -C "oven@aesthetic.computer"
105 end
106
107 # Upload SSH key to DigitalOcean
108 set SSH_PUBLIC_KEY (cat "$SSH_KEY_FILE.pub")
109 doctl compute ssh-key import $SSH_KEY_NAME --public-key-file "$SSH_KEY_FILE.pub" 2>/dev/null; or true
110
111 # Create droplet
112 echo -e "$GREEN🚀 Creating droplet: $DROPLET_NAME...$NC"
113 echo -e "$YELLOW Size: $DROPLET_SIZE$NC"
114 echo -e "$YELLOW Region: $DROPLET_REGION$NC"
115 echo -e "$YELLOW Image: $DROPLET_IMAGE$NC"
116
117 doctl compute droplet create $DROPLET_NAME \
118 --size $DROPLET_SIZE \
119 --image $DROPLET_IMAGE \
120 --region $DROPLET_REGION \
121 --ssh-keys (doctl compute ssh-key list --format ID --no-header) \
122 --wait
123
124 # Get droplet IP
125 echo -e "$GREEN⏳ Waiting for droplet to be ready...$NC"
126 sleep 30
127
128 set DROPLET_IP (doctl compute droplet list --format Name,PublicIPv4 | grep "^$DROPLET_NAME" | awk '{print $2}')
129
130 if test -z "$DROPLET_IP"
131 echo -e "$RED❌ Failed to get droplet IP$NC"
132 exit 1
133 end
134
135 echo -e "$GREEN✅ Droplet created: $DROPLET_IP$NC"
136
137 # Configure firewall
138 echo -e "$GREEN🔒 Configuring firewall...$NC"
139 doctl compute firewall create \
140 --name "oven-firewall" \
141 --inbound-rules "protocol:tcp,ports:22,sources:addresses:0.0.0.0/0,sources:addresses:::/0 protocol:tcp,ports:80,sources:addresses:0.0.0.0/0,sources:addresses:::/0 protocol:tcp,ports:443,sources:addresses:0.0.0.0/0,sources:addresses:::/0" \
142 --outbound-rules "protocol:tcp,ports:all,destinations:addresses:0.0.0.0/0,destinations:addresses:::/0 protocol:udp,ports:all,destinations:addresses:0.0.0.0/0,destinations:addresses:::/0" \
143 --droplet-ids (doctl compute droplet list --format ID --no-header | grep -A1 $DROPLET_NAME | tail -1) 2>/dev/null; or true
144end
145
146# Wait for SSH to be ready
147echo -e "$GREEN⏳ Waiting for SSH to be ready...$NC"
148set MAX_ATTEMPTS 30
149set ATTEMPT 1
150
151while test $ATTEMPT -le $MAX_ATTEMPTS
152 if ssh -i "$HOME/.ssh/$SSH_KEY_NAME" -o StrictHostKeyChecking=no -o ConnectTimeout=5 root@$DROPLET_IP "echo SSH ready" &>/dev/null
153 break
154 end
155 echo -e "$YELLOW Attempt $ATTEMPT/$MAX_ATTEMPTS...$NC"
156 sleep 10
157 set ATTEMPT (math $ATTEMPT + 1)
158end
159
160if test $ATTEMPT -gt $MAX_ATTEMPTS
161 echo -e "$RED❌ SSH connection timeout$NC"
162 exit 1
163end
164
165echo -e "$GREEN✅ SSH connection established$NC"
166
167# Create setup script
168echo -e "$GREEN📝 Creating server setup script...$NC"
169set SETUP_SCRIPT "/tmp/oven-setup.sh"
170
171echo '#!/bin/bash
172set -e
173
174echo "🔧 Setting up Oven server..."
175
176# Update system
177echo "📦 Updating system packages..."
178apt-get update
179apt-get upgrade -y
180
181# Install Node.js 20
182echo "📦 Installing Node.js 20..."
183curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
184apt-get install -y nodejs
185
186# Install ffmpeg
187echo "📦 Installing ffmpeg..."
188apt-get install -y ffmpeg
189
190# Install Caddy
191echo "📦 Installing Caddy..."
192apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl
193curl -1sLf '"'"'https://dl.cloudsmith.io/public/caddy/stable/gpg.key'"'"' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
194curl -1sLf '"'"'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt'"'"' | tee /etc/apt/sources.list.d/caddy-stable.list
195apt-get update
196apt-get install -y caddy
197
198# Create oven user
199echo "👤 Creating oven user..."
200useradd -m -s /bin/bash oven || true
201
202# Create directories
203echo "📁 Creating directories..."
204mkdir -p /opt/oven
205mkdir -p /var/log/oven
206chown -R oven:oven /opt/oven /var/log/oven
207
208echo "✅ Server setup complete"' > $SETUP_SCRIPT
209
210# Upload and run setup script
211echo -e "$GREEN📤 Uploading setup script...$NC"
212scp -i "$HOME/.ssh/$SSH_KEY_NAME" -o StrictHostKeyChecking=no $SETUP_SCRIPT root@$DROPLET_IP:/tmp/
213ssh -i "$HOME/.ssh/$SSH_KEY_NAME" root@$DROPLET_IP "chmod +x /tmp/oven-setup.sh && /tmp/oven-setup.sh"
214
215# Upload service files
216echo -e "$GREEN📤 Uploading service files...$NC"
217scp -i "$HOME/.ssh/$SSH_KEY_NAME" -o StrictHostKeyChecking=no $SCRIPT_DIR/package.json root@$DROPLET_IP:/opt/oven/
218scp -i "$HOME/.ssh/$SSH_KEY_NAME" -o StrictHostKeyChecking=no $SCRIPT_DIR/package-lock.json root@$DROPLET_IP:/opt/oven/
219scp -i "$HOME/.ssh/$SSH_KEY_NAME" -o StrictHostKeyChecking=no $SCRIPT_DIR/server.mjs root@$DROPLET_IP:/opt/oven/
220scp -i "$HOME/.ssh/$SSH_KEY_NAME" -o StrictHostKeyChecking=no $SCRIPT_DIR/baker.mjs root@$DROPLET_IP:/opt/oven/
221scp -i "$HOME/.ssh/$SSH_KEY_NAME" -o StrictHostKeyChecking=no $SCRIPT_DIR/grabber.mjs root@$DROPLET_IP:/opt/oven/
222scp -i "$HOME/.ssh/$SSH_KEY_NAME" -o StrictHostKeyChecking=no $SERVICE_ENV root@$DROPLET_IP:/opt/oven/.env
223
224# Update .env for production
225echo -e "$GREEN🔧 Configuring production environment...$NC"
226ssh -i "$HOME/.ssh/$SSH_KEY_NAME" root@$DROPLET_IP "cd /opt/oven && sed -i 's/NODE_ENV=development/NODE_ENV=production/g' .env && sed -i 's/PORT=3002/PORT=3000/g' .env"
227
228# Install dependencies
229echo -e "$GREEN📦 Installing Node.js dependencies...$NC"
230ssh -i "$HOME/.ssh/$SSH_KEY_NAME" root@$DROPLET_IP "cd /opt/oven && npm install --production"
231
232# Create systemd service
233echo -e "$GREEN🔧 Creating systemd service...$NC"
234ssh -i "$HOME/.ssh/$SSH_KEY_NAME" root@$DROPLET_IP "printf '[Unit]\nDescription=Oven Video Processing Service\nAfter=network.target\n\n[Service]\nType=simple\nUser=oven\nWorkingDirectory=/opt/oven\nEnvironment=NODE_ENV=production\nExecStart=/usr/bin/node /opt/oven/server.mjs\nRestart=always\nRestartSec=10\nStandardOutput=append:/var/log/oven/oven.log\nStandardError=append:/var/log/oven/oven-error.log\n\n[Install]\nWantedBy=multi-user.target\n' > /etc/systemd/system/oven.service"
235
236# Configure Caddy
237echo -e "$GREEN🔧 Configuring Caddy...$NC"
238ssh -i "$HOME/.ssh/$SSH_KEY_NAME" root@$DROPLET_IP "printf '{\n email me@jas.life\n}\n\n$OVEN_HOSTNAME {\n reverse_proxy localhost:3000\n encode gzip\n \n log {\n output file /var/log/caddy/oven.log\n }\n}\n' > /etc/caddy/Caddyfile"
239
240# Start services
241echo -e "$GREEN🚀 Starting services...$NC"
242ssh -i "$HOME/.ssh/$SSH_KEY_NAME" root@$DROPLET_IP "systemctl daemon-reload && systemctl enable oven && systemctl restart oven && systemctl restart caddy"
243
244# Wait for service to start
245echo -e "$GREEN⏳ Waiting for service to start...$NC"
246sleep 5
247
248# Check service status
249echo -e "$GREEN🔍 Checking service status...$NC"
250ssh -i "$HOME/.ssh/$SSH_KEY_NAME" root@$DROPLET_IP "systemctl status oven --no-pager | head -20"
251
252# Configure DNS
253echo -e "$GREEN🌐 Configuring DNS...$NC"
254echo -e "$YELLOW Creating A record: $OVEN_HOSTNAME -> $DROPLET_IP$NC"
255
256# Use Cloudflare API to create/update DNS record
257curl -X POST "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records" \
258 -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
259 -H "Content-Type: application/json" \
260 --data "{\"type\":\"A\",\"name\":\"oven\",\"content\":\"$DROPLET_IP\",\"ttl\":1,\"proxied\":false}" \
261 2>/dev/null || \
262curl -X PUT "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records" \
263 -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
264 -H "Content-Type: application/json" \
265 --data "{\"type\":\"A\",\"name\":\"oven\",\"content\":\"$DROPLET_IP\",\"ttl\":1,\"proxied\":false}"
266
267echo ""
268echo -e "$GREEN✅ Deployment complete!$NC"
269echo ""
270echo -e "$YELLOW📋 Service Information:$NC"
271echo -e " URL: https://$OVEN_HOSTNAME"
272echo -e " IP: $DROPLET_IP"
273echo -e " SSH: ssh -i ~/.ssh/$SSH_KEY_NAME root@$DROPLET_IP"
274echo ""
275echo -e "$YELLOW🔧 Useful commands:$NC"
276echo -e " Check status: systemctl status oven"
277echo -e " View logs: tail -f /var/log/oven/oven.log"
278echo -e " Restart: systemctl restart oven"
279echo ""
280echo -e "$YELLOW⚠️ Next steps:$NC"
281echo -e " 1. Update Netlify env var: OVEN_URL=https://$OVEN_HOSTNAME"
282echo -e " 2. Test health: curl https://$OVEN_HOSTNAME/health"
283echo -e " 3. Test dashboard: https://$OVEN_HOSTNAME"
284echo ""