-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy.sh
More file actions
executable file
·296 lines (262 loc) · 10.9 KB
/
Copy pathdeploy.sh
File metadata and controls
executable file
·296 lines (262 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# ── Usage ──────────────────────────────────────────────────────────
usage() {
ENVS=$(ls "$SCRIPT_DIR"/deploy/*.env 2>/dev/null | xargs -I{} basename {} .env | tr '\n' ' ')
cat <<EOF
Usage: $0 <env> <command>
Environments: ${ENVS:-none found}
Commands:
release-deploy <dir> Upload pre-built binary + static from extracted tarball, install + restart
build Build the release binary on the server
deploy Sync source + build on server + install + restart
install Sync source + install + restart (skip build; set DEPLOY_BINARY_DIR to use a pre-built binary)
setup First-time server setup (user, dirs, systemd, cron)
restart Restart the service on the server
logs Tail the server logs
status Show service status
EOF
exit 1
}
[[ $# -ge 2 ]] || usage
# ── Load environment config ───────────────────────────────────────
ENV_NAME="$1"
COMMAND="$2"
ENV_FILE="$SCRIPT_DIR/deploy/${ENV_NAME}.env"
if [[ ! -f "$ENV_FILE" ]]; then
echo "Error: config file not found: $ENV_FILE"
echo "Available environments:"
ls "$SCRIPT_DIR"/deploy/*.env 2>/dev/null | xargs -I{} basename {} .env | sed 's/^/ /'
exit 1
fi
# shellcheck source=/dev/null
source "$ENV_FILE"
SERVER="$DEPLOY_SERVER"
SSH_PORT="${DEPLOY_SSH_PORT:-22}"
EXTRA_ARG="${3:-}"
# Multiplex SSH: one TCP connection shared across the many rapid ssh/scp/rsync
# invocations a deploy makes. Avoids tripping sshd MaxStartups / fail2ban and
# speeds things up (no repeated handshakes). Skipped in CI where each step runs
# in a fresh runner with its own single connection.
SSH_MUX_OPTS=""
if [[ "${DEPLOY_CI:-false}" != "true" ]]; then
SSH_MUX_OPTS="-o ControlMaster=auto -o ControlPath=/tmp/myapps-ssh-%C -o ControlPersist=60s"
fi
# ── Helpers ────────────────────────────────────────────────────────
# In CI (DEPLOY_CI=true) we skip -t (no TTY available for sudo prompts).
# The deploy SSH user must have passwordless sudo configured on the server.
ssh_server() {
if [[ "${DEPLOY_CI:-false}" == "true" ]]; then
ssh $SSH_MUX_OPTS -p "$SSH_PORT" "$SERVER" "$@"
else
ssh $SSH_MUX_OPTS -t -p "$SSH_PORT" "$SERVER" "$@"
fi
}
sync_source() {
echo "▸ Syncing source to $SERVER:$DEPLOY_REMOTE_BUILD_DIR..."
rsync -az --delete \
-e "ssh $SSH_MUX_OPTS -p $SSH_PORT" \
--exclude target \
--exclude .git \
--exclude data \
--exclude models \
--exclude '.env' \
./ "$SERVER:$DEPLOY_REMOTE_BUILD_DIR/"
}
release_install() {
local release_dir="${1:?Usage: $0 <env> release-deploy <release-dir>}"
[[ -f "$release_dir/myapps" ]] || { echo "Error: binary not found: $release_dir/myapps"; exit 1; }
[[ -d "$release_dir/static" ]] || { echo "Error: static dir not found: $release_dir/static"; exit 1; }
echo "▸ Uploading release binary to $SERVER..."
scp $SSH_MUX_OPTS -P "$SSH_PORT" "$release_dir/myapps" "$SERVER:/tmp/myapps.new"
echo "▸ Installing binary..."
ssh_server DEPLOY_REMOTE_DIR="$DEPLOY_REMOTE_DIR" bash <<'INSTALL'
set -euo pipefail
sudo mv /tmp/myapps.new $DEPLOY_REMOTE_DIR/myapps.new
sudo mv $DEPLOY_REMOTE_DIR/myapps.new $DEPLOY_REMOTE_DIR/myapps
sudo chown myapps:myapps $DEPLOY_REMOTE_DIR/myapps
sudo chmod +x $DEPLOY_REMOTE_DIR/myapps
INSTALL
echo "▸ Syncing static files..."
rsync -az --delete -e "ssh $SSH_MUX_OPTS -p $SSH_PORT" "$release_dir/static/" "$SERVER:/tmp/myapps-static/"
ssh_server DEPLOY_REMOTE_DIR="$DEPLOY_REMOTE_DIR" DEPLOY_ICON="$DEPLOY_ICON" bash <<'STATIC'
set -euo pipefail
sudo rsync -a --delete /tmp/myapps-static/ $DEPLOY_REMOTE_DIR/static/
if [[ -n "$DEPLOY_ICON" && "$DEPLOY_ICON" != "icon.svg" ]]; then
sudo cp $DEPLOY_REMOTE_DIR/static/$DEPLOY_ICON $DEPLOY_REMOTE_DIR/static/icon.svg
fi
sudo chown -R myapps:myapps $DEPLOY_REMOTE_DIR/static
rm -rf /tmp/myapps-static
STATIC
}
build() {
sync_source
echo "▸ Building release on $SERVER..."
ssh $SSH_MUX_OPTS -p "$SSH_PORT" "$SERVER" "source \$HOME/.cargo/env && export RUSTC_WRAPPER=sccache && cd $DEPLOY_REMOTE_BUILD_DIR && cargo build --release"
}
install() {
local binary_dir="${DEPLOY_BINARY_DIR:-$DEPLOY_REMOTE_BUILD_DIR}"
echo "▸ Installing binary and static files..."
ssh_server DEPLOY_BINARY_DIR="$binary_dir" DEPLOY_REMOTE_BUILD_DIR="$DEPLOY_REMOTE_BUILD_DIR" DEPLOY_REMOTE_DIR="$DEPLOY_REMOTE_DIR" DEPLOY_ICON="$DEPLOY_ICON" bash <<'INSTALL'
set -euo pipefail
sudo cp $DEPLOY_BINARY_DIR/target/release/myapps $DEPLOY_REMOTE_DIR/myapps.new
sudo mv $DEPLOY_REMOTE_DIR/myapps.new $DEPLOY_REMOTE_DIR/myapps
sudo chown myapps:myapps $DEPLOY_REMOTE_DIR/myapps
sudo chmod +x $DEPLOY_REMOTE_DIR/myapps
sudo rsync -a --delete $DEPLOY_REMOTE_BUILD_DIR/static/ $DEPLOY_REMOTE_DIR/static/
# Copy environment-specific icon as icon.svg
if [[ -n "$DEPLOY_ICON" && "$DEPLOY_ICON" != "icon.svg" ]]; then
sudo cp $DEPLOY_REMOTE_DIR/static/$DEPLOY_ICON $DEPLOY_REMOTE_DIR/static/icon.svg
fi
sudo chown -R myapps:myapps $DEPLOY_REMOTE_DIR/static
INSTALL
}
restart() {
echo "▸ Restarting $DEPLOY_SERVICE_NAME service..."
ssh_server "sudo systemctl restart $DEPLOY_SERVICE_NAME"
echo "▸ Done. Checking status..."
ssh_server "sudo systemctl --no-pager status $DEPLOY_SERVICE_NAME"
}
setup() {
echo "▸ Running first-time server setup on $SERVER ($ENV_NAME)..."
echo " (you may be prompted for your sudo password)"
ssh_server \
DEPLOY_DOMAIN="$DEPLOY_DOMAIN" \
DEPLOY_REMOTE_DIR="$DEPLOY_REMOTE_DIR" \
DEPLOY_SERVICE_NAME="$DEPLOY_SERVICE_NAME" \
DEPLOY_NGINX_SITE="$DEPLOY_NGINX_SITE" \
DEPLOY_PORT="$DEPLOY_PORT" \
DEPLOY_CRON_ENABLED="$DEPLOY_CRON_ENABLED" \
ENV_NAME="$ENV_NAME" \
bash <<'SETUP'
set -euo pipefail
# Install Rust if not present
if ! command -v cargo &>/dev/null; then
echo " Installing Rust toolchain..."
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
echo " Rust installed"
fi
# Install build dependencies
echo " Installing build dependencies..."
sudo apt-get install -y pkg-config libssl-dev sqlite3
# Install sccache if not present
if ! command -v sccache &>/dev/null; then
echo " Installing sccache..."
cargo install sccache --locked
echo " sccache installed"
fi
# Create dedicated system user (no login shell, no home dir)
if ! id myapps &>/dev/null; then
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapps
echo " Created user 'myapps'"
fi
# Create directory structure
sudo mkdir -p $DEPLOY_REMOTE_DIR/{data,static}
sudo chown -R myapps:myapps $DEPLOY_REMOTE_DIR
sudo chmod 750 $DEPLOY_REMOTE_DIR
# Create .env template if it doesn't exist
if [[ ! -f $DEPLOY_REMOTE_DIR/.env ]]; then
sudo tee $DEPLOY_REMOTE_DIR/.env > /dev/null <<ENV
DATABASE_URL=sqlite://$DEPLOY_REMOTE_DIR/data/myapps.db
BASE_URL=https://$DEPLOY_DOMAIN
ENCRYPTION_KEY=
VAPID_PRIVATE_KEY=
VAPID_PUBLIC_KEY=
VAPID_SUBJECT=mailto:you@example.com
WHISPER_CLI_PATH=/opt/whisper.cpp/build/bin/whisper-cli
WHISPER_MODELS_DIR=/opt/whisper.cpp/models
LLAMA_SERVER_URL=
BIND_ADDR=127.0.0.1:$DEPLOY_PORT
DEPLOY_APPS=${DEPLOY_APPS:-}
AUTH_SSO_HEADER=${DEPLOY_AUTH_SSO_HEADER:-}
EXTERNAL_APPS=${DEPLOY_EXTERNAL_APPS:-}
SEED=${DEPLOY_SEED:-false}
CLEANUP_INACTIVE_DAYS=0
ENV
sudo chown myapps:myapps $DEPLOY_REMOTE_DIR/.env
sudo chmod 600 $DEPLOY_REMOTE_DIR/.env
echo " Created $DEPLOY_REMOTE_DIR/.env — edit it with your values"
fi
# Install systemd service
sudo tee /etc/systemd/system/$DEPLOY_SERVICE_NAME.service > /dev/null <<SERVICE
[Unit]
Description=MyApps platform ($DEPLOY_SERVICE_NAME)
After=network.target
[Service]
Type=simple
User=myapps
Group=myapps
WorkingDirectory=$DEPLOY_REMOTE_DIR
ExecStart=$DEPLOY_REMOTE_DIR/myapps serve
Restart=on-failure
RestartSec=5
# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=$DEPLOY_REMOTE_DIR
[Install]
WantedBy=multi-user.target
SERVICE
sudo systemctl daemon-reload
echo " Installed $DEPLOY_SERVICE_NAME.service"
# Install cron job only if enabled
if [[ "$DEPLOY_CRON_ENABLED" == "true" ]]; then
sudo tee /etc/cron.d/$DEPLOY_SERVICE_NAME > /dev/null <<CRON
# MyApps daily scheduled tasks ($DEPLOY_SERVICE_NAME)
0 6 * * * myapps $DEPLOY_REMOTE_DIR/myapps cron
CRON
sudo chmod 644 /etc/cron.d/$DEPLOY_SERVICE_NAME
echo " Installed cron job (daily at 06:00)"
else
echo " Cron disabled for this environment"
fi
# Install nginx site config if not present
if [[ ! -f /etc/nginx/sites-available/$DEPLOY_NGINX_SITE ]]; then
sudo tee /etc/nginx/sites-available/$DEPLOY_NGINX_SITE > /dev/null <<NGINX
server {
listen 80;
server_name $DEPLOY_DOMAIN;
location / {
proxy_pass http://127.0.0.1:$DEPLOY_PORT/;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
NGINX
sudo ln -sf /etc/nginx/sites-available/$DEPLOY_NGINX_SITE /etc/nginx/sites-enabled/$DEPLOY_NGINX_SITE
sudo nginx -t && sudo systemctl reload nginx
echo " Installed nginx config for $DEPLOY_DOMAIN (HTTP only)"
echo " To enable HTTPS, run: sudo apt install python3-certbot-nginx && sudo certbot --nginx -d $DEPLOY_DOMAIN"
else
echo " nginx config already exists, skipping"
fi
echo ""
echo "Setup complete. Next steps:"
echo " 1. Edit $DEPLOY_REMOTE_DIR/.env with your values"
echo " 2. From your dev machine, run: ./deploy.sh $ENV_NAME deploy"
echo " 3. Create a user: sudo -u myapps $DEPLOY_REMOTE_DIR/myapps create-user --username <name> --password <pass>"
echo " 4. Set up HTTPS: sudo apt install python3-certbot-nginx && sudo certbot --nginx -d $DEPLOY_DOMAIN"
SETUP
}
# ── Command dispatch ───────────────────────────────────────────────
case "${COMMAND}" in
release-deploy) release_install "$EXTRA_ARG" && restart ;;
build) build ;;
deploy) build && install && restart ;;
install) sync_source && install && restart ;;
setup) setup ;;
restart) restart ;;
logs) ssh_server "sudo journalctl -u $DEPLOY_SERVICE_NAME -f --no-pager" ;;
status) ssh_server "sudo systemctl --no-pager status $DEPLOY_SERVICE_NAME" ;;
*) usage ;;
esac