Skip to main content

max / makenotwork

5.9 KB · 177 lines History Blame Raw
1 #!/bin/bash
2 # Multithreaded Deployment Script
3 # Builds natively on astra (aarch64), deploys to /opt/multithreaded.
4 # Run from the multithreaded project root.
5 #
6 # Usage:
7 # ./deploy/deploy.sh # Build + deploy + restart
8 # ./deploy/deploy.sh --setup # First-time: create user, dirs, db, build, install, seed
9
10 set -e
11
12 # Configuration
13 # By tailnet name, not by the 100.106.221.39 that stood here until 2026-08-23.
14 # A written-down address goes stale silently, and this script rsyncs a source
15 # tree and restarts a unit on whatever answers it.
16 SERVER="max@astra"
17 REMOTE_DIR="/opt/multithreaded"
18 SRC_DIR="src/multithreaded"
19 BINARY_NAME="multithreaded"
20 DEPLOY_DIR="deploy"
21
22 # Check we're in the right directory
23 if [ ! -f "Cargo.toml" ] || ! grep -q 'name = "multithreaded"' Cargo.toml; then
24 echo "Error: Run this script from the MNW/multithreaded/ directory"
25 exit 1
26 fi
27
28 rsync_source() {
29 echo "[rsync] Uploading source to $SERVER:~/$SRC_DIR/..."
30 ssh $SERVER "mkdir -p ~/$SRC_DIR"
31 # node_modules is reinstalled on the build host by build.rs (npm ci) and is
32 # large, platform-specific, and regenerable. static/dist is NOT excluded:
33 # it is emitted there by the same build script during cargo build.
34 rsync -az --delete \
35 --exclude target/ \
36 --exclude .env \
37 --exclude .git/ \
38 --exclude frontend/node_modules/ \
39 ./ $SERVER:~/$SRC_DIR/
40
41 # Sync the sibling crates the manifests point at (`../shared/...` resolves
42 # the same way from ~/src/multithreaded on the build host).
43 #
44 # Read the list out of the manifests rather than hardcoding it. The hardcoded
45 # version listed docengine and livechat, which mt no longer takes by path,
46 # and omitted pom-contract, which it does: the build failed on the build host
47 # for a dependency the deploy script had never heard of. A list derived from
48 # the manifests cannot drift from them.
49 local dep_paths dep_path dep
50 dep_paths="$(grep -ho 'path = "\.\./[^"]*"' Cargo.toml crates/*/Cargo.toml \
51 | sed 's/path = "//; s/"$//' | sort -u)"
52 for dep_path in $dep_paths; do
53 dep="${dep_path#../}"
54 if [ -d "$dep_path" ]; then
55 echo "[rsync] Syncing sibling crate: $dep"
56 ssh $SERVER "mkdir -p ~/src/$dep"
57 rsync -az --delete --exclude target --exclude .git \
58 "$dep_path/" "$SERVER:~/src/$dep/"
59 else
60 echo "[rsync] ERROR: manifest points at $dep_path, which does not exist" >&2
61 exit 1
62 fi
63 done
64 echo "[rsync] Done"
65 }
66
67 build_remote() {
68 echo "[build] Building release on astra..."
69 ssh $SERVER "cd ~/$SRC_DIR && ~/.cargo/bin/cargo build --release 2>&1"
70 echo "[build] Done"
71 }
72
73 # static/ is deliberately not copied: it is compiled into the binary
74 # (src/static_assets.rs), so the binary IS the assets. migrations/ is copied for
75 # operator convenience only — sqlx::migrate!() embeds them at compile time too.
76 deploy_files() {
77 echo "[deploy] Stopping service + copying binary to $REMOTE_DIR..."
78 ssh $SERVER "
79 sudo systemctl stop multithreaded || true
80 sudo cp ~/$SRC_DIR/target/release/$BINARY_NAME $REMOTE_DIR/$BINARY_NAME
81 sudo chmod +x $REMOTE_DIR/$BINARY_NAME
82 sudo rsync -a --delete ~/$SRC_DIR/migrations/ $REMOTE_DIR/migrations/
83 sudo chown -R multithreaded:multithreaded $REMOTE_DIR
84 "
85 echo "[deploy] Done"
86 }
87
88 restart_app() {
89 echo "[restart] Restarting multithreaded..."
90 ssh $SERVER "sudo systemctl restart multithreaded"
91 sleep 2
92 echo ""
93 ssh $SERVER "sudo systemctl status multithreaded --no-pager"
94 echo ""
95 echo "[health] Checking..."
96 ssh $SERVER "curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:3400/"
97 }
98
99 first_time_setup() {
100 echo "=== First-Time Setup ==="
101
102 echo "[setup] Creating system user..."
103 ssh $SERVER "
104 sudo useradd --system --shell /usr/sbin/nologin --home-dir $REMOTE_DIR multithreaded 2>/dev/null || echo 'User already exists'
105 "
106
107 echo "[setup] Creating directories..."
108 ssh $SERVER "
109 sudo mkdir -p $REMOTE_DIR
110 sudo chown multithreaded:multithreaded $REMOTE_DIR
111 "
112
113 echo "[setup] Creating database role + database..."
114 ssh $SERVER "
115 sudo -u postgres createuser multithreaded 2>/dev/null || echo 'Role already exists'
116 createdb multithreaded 2>/dev/null || echo 'Database already exists'
117 sudo -u postgres psql -c 'GRANT ALL PRIVILEGES ON DATABASE multithreaded TO multithreaded;' 2>/dev/null
118 sudo -u postgres psql -d multithreaded -c 'GRANT ALL ON SCHEMA public TO multithreaded;' 2>/dev/null
119 "
120
121 rsync_source
122 build_remote
123
124 echo "[setup] Installing files..."
125 deploy_files
126
127 echo "[setup] Installing env file..."
128 ssh $SERVER "
129 sudo cp ~/$SRC_DIR/$DEPLOY_DIR/env.production $REMOTE_DIR/.env
130 sudo chmod 600 $REMOTE_DIR/.env
131 sudo chown multithreaded:multithreaded $REMOTE_DIR/.env
132 "
133
134 echo "[setup] Installing systemd service..."
135 ssh $SERVER "
136 sudo cp ~/$SRC_DIR/$DEPLOY_DIR/multithreaded.service /etc/systemd/system/multithreaded.service
137 sudo systemctl daemon-reload
138 sudo systemctl enable multithreaded
139 "
140
141 echo "[setup] Starting service..."
142 ssh $SERVER "sudo systemctl start multithreaded"
143 sleep 2
144
145 echo "[setup] Running seed..."
146 ssh $SERVER "
147 cd $REMOTE_DIR
148 sudo -u multithreaded $REMOTE_DIR/$BINARY_NAME --seed
149 "
150
151 echo ""
152 ssh $SERVER "sudo systemctl status multithreaded --no-pager"
153 echo ""
154 echo "[health] Checking..."
155 ssh $SERVER "curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:3400/"
156 }
157
158 case "${1:-deploy}" in
159 --setup)
160 first_time_setup
161 ;;
162 deploy|"")
163 echo "=== Deploy ==="
164 rsync_source
165 build_remote
166 deploy_files
167 restart_app
168 ;;
169 *)
170 echo "Usage: $0 [--setup]"
171 exit 1
172 ;;
173 esac
174
175 echo ""
176 echo "=== Done ==="
177