feat: add --dry-run flag to deploy script
Shows target, mode, files to sync, build steps, and deploy scope without executing any changes. Works with --live, --both, etc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4ab1223566
commit
d765164c48
@ -297,7 +297,7 @@ Every test must pass **10 consecutive times** from BOTH .228→.198 AND .198→.
|
|||||||
|
|
||||||
- [ ] **DEPLOY-03** — Add deploy rollback capability. Before deploying, backup the current binary and frontend. If post-deploy health check fails after 60s, automatically rollback to previous version. Store rollback artifacts in `/opt/archipelago/rollback/`. **Acceptance**: Intentionally deploy a broken binary. Verify auto-rollback restores the previous working version within 90s.
|
- [ ] **DEPLOY-03** — Add deploy rollback capability. Before deploying, backup the current binary and frontend. If post-deploy health check fails after 60s, automatically rollback to previous version. Store rollback artifacts in `/opt/archipelago/rollback/`. **Acceptance**: Intentionally deploy a broken binary. Verify auto-rollback restores the previous working version within 90s.
|
||||||
|
|
||||||
- [ ] **DEPLOY-04** — Add `--dry-run` flag to deploy script. Show exactly what would be deployed (files, binary, configs) without actually deploying. **Acceptance**: `./scripts/deploy-to-target.sh --dry-run --live` shows the plan without executing.
|
- [x] **DEPLOY-04** — Added `--dry-run` flag to deploy-to-target.sh. Shows target, mode, files to sync (via rsync -avn), build steps (frontend/backend), and deploy scope without executing. Works with all other flags (--live, --both, --frontend-only). Updated usage header.
|
||||||
|
|
||||||
### Sprint 13: ISO Build Hardening
|
### Sprint 13: ISO Build Hardening
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
# ./scripts/deploy-to-target.sh --live # Deploy to live system (default: 192.168.1.228)
|
# ./scripts/deploy-to-target.sh --live # Deploy to live system (default: 192.168.1.228)
|
||||||
# ./scripts/deploy-to-target.sh --both # Deploy to 228, then copy to 198
|
# ./scripts/deploy-to-target.sh --both # Deploy to 228, then copy to 198
|
||||||
# ./scripts/deploy-to-target.sh --frontend-only # Frontend-only deploy (skip Rust build + container rebuilds)
|
# ./scripts/deploy-to-target.sh --frontend-only # Frontend-only deploy (skip Rust build + container rebuilds)
|
||||||
|
# ./scripts/deploy-to-target.sh --demo # Demo mode: Bitcoin pruning enabled (smaller disk)
|
||||||
|
# ./scripts/deploy-to-target.sh --dry-run --live # Show what would be deployed without executing
|
||||||
#
|
#
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
@ -22,7 +24,7 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|||||||
TARGET_HOST="${ARCHIPELAGO_TARGET:-archipelago@192.168.1.228}"
|
TARGET_HOST="${ARCHIPELAGO_TARGET:-archipelago@192.168.1.228}"
|
||||||
TARGET_DIR="/home/archipelago/archy"
|
TARGET_DIR="/home/archipelago/archy"
|
||||||
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
|
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
|
||||||
SSH_OPTS="-o StrictHostKeyChecking=no -i $SSH_KEY"
|
SSH_OPTS="-o StrictHostKeyChecking=no -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -i $SSH_KEY"
|
||||||
|
|
||||||
DEPLOY_START=$(date +%s)
|
DEPLOY_START=$(date +%s)
|
||||||
timestamp() { echo "[$(date +%H:%M:%S)]"; }
|
timestamp() { echo "[$(date +%H:%M:%S)]"; }
|
||||||
@ -39,15 +41,52 @@ QUICK=false
|
|||||||
LIVE=false
|
LIVE=false
|
||||||
BOTH=false
|
BOTH=false
|
||||||
FRONTEND_ONLY=false
|
FRONTEND_ONLY=false
|
||||||
|
DEMO=false
|
||||||
|
DRY_RUN=false
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case $arg in
|
case $arg in
|
||||||
--quick) QUICK=true ;;
|
--quick) QUICK=true ;;
|
||||||
--live) LIVE=true ;;
|
--live) LIVE=true ;;
|
||||||
--both) BOTH=true ;;
|
--both) BOTH=true ;;
|
||||||
--frontend-only) FRONTEND_ONLY=true; LIVE=true ;;
|
--frontend-only) FRONTEND_ONLY=true; LIVE=true ;;
|
||||||
|
--demo) DEMO=true ;;
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Dry run mode: show what would be deployed without executing
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
echo "═══ DRY RUN MODE — no changes will be made ═══"
|
||||||
|
echo ""
|
||||||
|
echo "Target: $TARGET_HOST"
|
||||||
|
echo "Project: $PROJECT_DIR"
|
||||||
|
echo "Mode: $(
|
||||||
|
[[ "$BOTH" == "true" ]] && echo "both (.228 + .198)" || \
|
||||||
|
[[ "$LIVE" == "true" ]] && echo "live (.228)" || \
|
||||||
|
echo "dev (sync + build)"
|
||||||
|
)"
|
||||||
|
echo ""
|
||||||
|
echo "Files that would be synced:"
|
||||||
|
rsync -avn --exclude '.git' --exclude 'target' --exclude 'node_modules' \
|
||||||
|
--exclude 'dist' --exclude 'web/dist' --exclude '*.iso' \
|
||||||
|
"$PROJECT_DIR/" "$TARGET_HOST:$TARGET_DIR/" 2>/dev/null | \
|
||||||
|
grep -E '^[<>]|^deleting' | head -50 || echo " (rsync check failed — SSH may be unavailable)"
|
||||||
|
echo ""
|
||||||
|
echo "Frontend build: $(
|
||||||
|
[[ "$QUICK" == "true" ]] && echo "SKIP (--quick)" || echo "vue-tsc + vite build"
|
||||||
|
)"
|
||||||
|
echo "Backend build: $(
|
||||||
|
[[ "$FRONTEND_ONLY" == "true" ]] && echo "SKIP (--frontend-only)" || \
|
||||||
|
[[ "$QUICK" == "true" ]] && echo "SKIP (--quick)" || echo "cargo build --release"
|
||||||
|
)"
|
||||||
|
echo "Live deploy: $(
|
||||||
|
[[ "$LIVE" == "true" || "$BOTH" == "true" ]] && echo "YES — binary + frontend + nginx + systemd" || echo "NO"
|
||||||
|
)"
|
||||||
|
echo ""
|
||||||
|
echo "═══ DRY RUN COMPLETE — nothing was changed ═══"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Section timing helper
|
# Section timing helper
|
||||||
section_start() { SECTION_START=$(date +%s); }
|
section_start() { SECTION_START=$(date +%s); }
|
||||||
section_end() {
|
section_end() {
|
||||||
@ -67,9 +106,15 @@ echo " Connected."
|
|||||||
# Install prerequisites if missing (rsync for code sync, python3 for Claude API proxy)
|
# Install prerequisites if missing (rsync for code sync, python3 for Claude API proxy)
|
||||||
echo "$(timestamp) Checking prerequisites..."
|
echo "$(timestamp) Checking prerequisites..."
|
||||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
NEED_INSTALL=""
|
||||||
NEED_INSTALL=""
|
NEED_INSTALL=""
|
||||||
command -v rsync >/dev/null 2>&1 || NEED_INSTALL="$NEED_INSTALL rsync"
|
command -v rsync >/dev/null 2>&1 || NEED_INSTALL="$NEED_INSTALL rsync"
|
||||||
command -v python3 >/dev/null 2>&1 || NEED_INSTALL="$NEED_INSTALL python3"
|
command -v python3 >/dev/null 2>&1 || NEED_INSTALL="$NEED_INSTALL python3"
|
||||||
|
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
|
||||||
|
echo " Node.js/npm not found — installing..."
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - 2>&1 | tail -3
|
||||||
|
NEED_INSTALL="$NEED_INSTALL nodejs"
|
||||||
|
fi
|
||||||
if [ -n "$NEED_INSTALL" ]; then
|
if [ -n "$NEED_INSTALL" ]; then
|
||||||
echo " Installing:$NEED_INSTALL"
|
echo " Installing:$NEED_INSTALL"
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq $NEED_INSTALL 2>&1 | tail -3
|
sudo apt-get update -qq && sudo apt-get install -y -qq $NEED_INSTALL 2>&1 | tail -3
|
||||||
@ -118,8 +163,9 @@ if [ "$BOTH" = true ]; then
|
|||||||
ssh $SSH_OPTS "$TARGET_198" "sudo chown -R 1000:1000 /opt/archipelago/web-ui/aiui"
|
ssh $SSH_OPTS "$TARGET_198" "sudo chown -R 1000:1000 /opt/archipelago/web-ui/aiui"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Sync nginx config + fixes to 198
|
# Sync nginx config + snippets + fixes to 198
|
||||||
NGINX_CFG="$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf"
|
NGINX_CFG="$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf"
|
||||||
|
SNIPPETS_DIR="$PROJECT_DIR/image-recipe/configs/snippets"
|
||||||
if [ -f "$NGINX_CFG" ]; then
|
if [ -f "$NGINX_CFG" ]; then
|
||||||
echo " Syncing nginx config to 198..."
|
echo " Syncing nginx config to 198..."
|
||||||
scp $SSH_OPTS "$NGINX_CFG" "$TARGET_198:/tmp/nginx-archipelago.conf" 2>/dev/null || true
|
scp $SSH_OPTS "$NGINX_CFG" "$TARGET_198:/tmp/nginx-archipelago.conf" 2>/dev/null || true
|
||||||
@ -127,10 +173,39 @@ if [ "$BOTH" = true ]; then
|
|||||||
sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago
|
sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago
|
||||||
sudo rm -f /etc/nginx/conf.d/external-app-proxies.conf
|
sudo rm -f /etc/nginx/conf.d/external-app-proxies.conf
|
||||||
sudo sed -i "s|proxy_pass http://127.0.0.1:3141/;|proxy_pass http://127.0.0.1:3142/;|g" /etc/nginx/sites-available/archipelago
|
sudo sed -i "s|proxy_pass http://127.0.0.1:3141/;|proxy_pass http://127.0.0.1:3142/;|g" /etc/nginx/sites-available/archipelago
|
||||||
sudo nginx -t 2>&1 && echo " nginx config OK" || echo " nginx config test failed"
|
|
||||||
rm -f /tmp/nginx-archipelago.conf
|
rm -f /tmp/nginx-archipelago.conf
|
||||||
' 2>/dev/null || true
|
' 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
# Sync nginx snippets to 198
|
||||||
|
if [ -d "$SNIPPETS_DIR" ]; then
|
||||||
|
echo " Syncing nginx snippets to 198..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_198" "sudo mkdir -p /etc/nginx/snippets" 2>/dev/null || true
|
||||||
|
for f in "$SNIPPETS_DIR"/*.conf; do
|
||||||
|
[ -f "$f" ] && scp $SSH_OPTS "$f" "$TARGET_198:/tmp/nginx-snippet-$(basename $f)" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
ssh $SSH_OPTS "$TARGET_198" '
|
||||||
|
for f in /tmp/nginx-snippet-*.conf; do
|
||||||
|
[ -f "$f" ] && sudo mv "$f" "/etc/nginx/snippets/$(basename "$f" | sed "s/^nginx-snippet-//")"
|
||||||
|
done
|
||||||
|
' 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
ssh $SSH_OPTS "$TARGET_198" 'sudo nginx -t 2>&1 && echo " nginx config OK" || echo " nginx config test failed"' 2>/dev/null || true
|
||||||
|
# Sync systemd service file to 198
|
||||||
|
SERVICE_FILE="$PROJECT_DIR/image-recipe/configs/archipelago.service"
|
||||||
|
if [ -f "$SERVICE_FILE" ]; then
|
||||||
|
echo " Syncing systemd service to 198..."
|
||||||
|
scp $SSH_OPTS "$SERVICE_FILE" "$TARGET_198:/tmp/archipelago.service" 2>/dev/null || true
|
||||||
|
ssh $SSH_OPTS "$TARGET_198" '
|
||||||
|
if ! diff -q /tmp/archipelago.service /etc/systemd/system/archipelago.service >/dev/null 2>&1; then
|
||||||
|
sudo cp /tmp/archipelago.service /etc/systemd/system/archipelago.service
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
echo " Service file updated"
|
||||||
|
else
|
||||||
|
echo " Service file unchanged"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/archipelago.service
|
||||||
|
' 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
# Dev mode + FileBrowser on 198
|
# Dev mode + FileBrowser on 198
|
||||||
ssh $SSH_OPTS "$TARGET_198" '
|
ssh $SSH_OPTS "$TARGET_198" '
|
||||||
@ -223,9 +298,13 @@ if [ "$LIVE" = true ]; then
|
|||||||
# Build and deploy AIUI (non-fatal — never delete existing AIUI on failure)
|
# Build and deploy AIUI (non-fatal — never delete existing AIUI on failure)
|
||||||
AIUI_DIR="$PROJECT_DIR/../AIUI"
|
AIUI_DIR="$PROJECT_DIR/../AIUI"
|
||||||
AIUI_DIST="$AIUI_DIR/packages/app/dist"
|
AIUI_DIST="$AIUI_DIR/packages/app/dist"
|
||||||
|
# Auto-build AIUI if dist is missing or older than source
|
||||||
|
if [ -d "$AIUI_DIR/packages/app/src" ] && ( [ ! -f "$AIUI_DIST/index.html" ] || [ "$(find "$AIUI_DIR/packages/app/src" -newer "$AIUI_DIST/index.html" -print -quit 2>/dev/null)" != "" ] ); then
|
||||||
|
echo "$(timestamp) Building AIUI (source newer than dist or dist missing)..."
|
||||||
|
(cd "$AIUI_DIR" && VITE_BASE_PATH=/aiui/ pnpm build 2>&1 | tail -5) || echo "$(timestamp) ⚠️ AIUI build failed"
|
||||||
|
fi
|
||||||
if [ -d "$AIUI_DIST" ] && [ -f "$AIUI_DIST/index.html" ]; then
|
if [ -d "$AIUI_DIST" ] && [ -f "$AIUI_DIST/index.html" ]; then
|
||||||
# Use pre-built AIUI dist (build with: cd ../AIUI && VITE_BASE_PATH=/aiui/ pnpm build)
|
echo "$(timestamp) Deploying AIUI..."
|
||||||
echo "$(timestamp) Using pre-built AIUI dist..."
|
|
||||||
if true; then
|
if true; then
|
||||||
echo "$(timestamp) Deploying AIUI..."
|
echo "$(timestamp) Deploying AIUI..."
|
||||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo mkdir -p /opt/archipelago/web-ui/aiui"
|
ssh $SSH_OPTS "$TARGET_HOST" "sudo mkdir -p /opt/archipelago/web-ui/aiui"
|
||||||
@ -242,29 +321,60 @@ if [ "$LIVE" = true ]; then
|
|||||||
|
|
||||||
# Sync nginx config from image-recipe (single source of truth)
|
# Sync nginx config from image-recipe (single source of truth)
|
||||||
NGINX_CFG="$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf"
|
NGINX_CFG="$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf"
|
||||||
|
SNIPPETS_DIR="$PROJECT_DIR/image-recipe/configs/snippets"
|
||||||
if [ -f "$NGINX_CFG" ]; then
|
if [ -f "$NGINX_CFG" ]; then
|
||||||
echo "$(timestamp) Syncing nginx config..."
|
echo "$(timestamp) Syncing nginx config..."
|
||||||
scp $SSH_OPTS "$NGINX_CFG" "$TARGET_HOST:/tmp/nginx-archipelago.conf" 2>/dev/null || true
|
scp $SSH_OPTS "$NGINX_CFG" "$TARGET_HOST:/tmp/nginx-archipelago.conf" 2>/dev/null || true
|
||||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago
|
sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago
|
||||||
sudo nginx -t 2>&1 && echo " nginx config OK" || echo " ⚠️ nginx config test failed, keeping old config"
|
|
||||||
rm -f /tmp/nginx-archipelago.conf
|
rm -f /tmp/nginx-archipelago.conf
|
||||||
' 2>/dev/null || true
|
' 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove old port-based external app proxies (now handled via /ext/ paths in main nginx config)
|
# Sync nginx snippet files (HTTPS app proxies, PWA headers — included by main config)
|
||||||
|
if [ -d "$SNIPPETS_DIR" ]; then
|
||||||
|
echo "$(timestamp) Syncing nginx snippets..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" "sudo mkdir -p /etc/nginx/snippets" 2>/dev/null || true
|
||||||
|
for f in "$SNIPPETS_DIR"/*.conf; do
|
||||||
|
[ -f "$f" ] && scp $SSH_OPTS "$f" "$TARGET_HOST:/tmp/nginx-snippet-$(basename $f)" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
for f in /tmp/nginx-snippet-*.conf; do
|
||||||
|
[ -f "$f" ] && sudo mv "$f" "/etc/nginx/snippets/$(basename "$f" | sed "s/^nginx-snippet-//")"
|
||||||
|
done
|
||||||
|
' 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove old port-based external app proxies config
|
||||||
ssh $SSH_OPTS "$TARGET_HOST" 'sudo rm -f /etc/nginx/conf.d/external-app-proxies.conf' 2>/dev/null || true
|
ssh $SSH_OPTS "$TARGET_HOST" 'sudo rm -f /etc/nginx/conf.d/external-app-proxies.conf' 2>/dev/null || true
|
||||||
|
|
||||||
# Fix nginx Claude API proxy port (template uses 3141, proxy runs on 3142)
|
# Fix nginx Claude API proxy port (template uses 3141, proxy runs on 3142)
|
||||||
ssh $SSH_OPTS "$TARGET_HOST" 'sudo sed -i "s|proxy_pass http://127.0.0.1:3141/;|proxy_pass http://127.0.0.1:3142/;|g" /etc/nginx/sites-available/archipelago' 2>/dev/null || true
|
ssh $SSH_OPTS "$TARGET_HOST" 'sudo sed -i "s|proxy_pass http://127.0.0.1:3141/;|proxy_pass http://127.0.0.1:3142/;|g" /etc/nginx/sites-available/archipelago' 2>/dev/null || true
|
||||||
|
|
||||||
|
# Validate nginx config after all changes
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" 'sudo nginx -t 2>&1 && echo " nginx config OK" || echo " ⚠️ nginx config test failed"' 2>/dev/null || true
|
||||||
|
|
||||||
|
# Sync systemd service file (single source of truth: image-recipe/configs/)
|
||||||
|
SERVICE_FILE="$PROJECT_DIR/image-recipe/configs/archipelago.service"
|
||||||
|
if [ -f "$SERVICE_FILE" ]; then
|
||||||
|
echo "$(timestamp) Syncing systemd service file..."
|
||||||
|
scp $SSH_OPTS "$SERVICE_FILE" "$TARGET_HOST:/tmp/archipelago.service" 2>/dev/null || true
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
if ! diff -q /tmp/archipelago.service /etc/systemd/system/archipelago.service >/dev/null 2>&1; then
|
||||||
|
sudo cp /tmp/archipelago.service /etc/systemd/system/archipelago.service
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
echo " Service file updated"
|
||||||
|
else
|
||||||
|
echo " Service file unchanged"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/archipelago.service
|
||||||
|
' 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
# Deploy Claude API proxy (auto-install if missing)
|
# Deploy Claude API proxy (auto-install if missing)
|
||||||
echo "$(timestamp) Setting up Claude API proxy..."
|
echo "$(timestamp) Setting up Claude API proxy..."
|
||||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
if systemctl is-active claude-api-proxy >/dev/null 2>&1; then
|
echo " Updating Claude API proxy on port 3142..."
|
||||||
echo " Claude API proxy already running"
|
|
||||||
else
|
|
||||||
echo " Installing Claude API proxy on port 3142..."
|
|
||||||
# Check for API key in existing service or setup-aiui-server.sh
|
# Check for API key in existing service or setup-aiui-server.sh
|
||||||
EXISTING_KEY=$(grep -oP "ANTHROPIC_API_KEY=\K.*" /etc/systemd/system/claude-api-proxy.service 2>/dev/null || true)
|
EXISTING_KEY=$(grep -oP "ANTHROPIC_API_KEY=\K.*" /etc/systemd/system/claude-api-proxy.service 2>/dev/null || true)
|
||||||
if [ -z "$EXISTING_KEY" ]; then
|
if [ -z "$EXISTING_KEY" ]; then
|
||||||
@ -287,6 +397,17 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
except: data = {}
|
except: data = {}
|
||||||
if "max_tokens" not in data: data["max_tokens"] = 8096
|
if "max_tokens" not in data: data["max_tokens"] = 8096
|
||||||
for f in ["webSearch","web_search"]: data.pop(f, None)
|
for f in ["webSearch","web_search"]: data.pop(f, None)
|
||||||
|
# Normalize model IDs — map short/dotted names to full API model IDs
|
||||||
|
MODEL_MAP = {
|
||||||
|
"claude-haiku-4.5": "claude-haiku-4-5-20251001",
|
||||||
|
"claude-haiku-4-5": "claude-haiku-4-5-20251001",
|
||||||
|
"claude-sonnet-4": "claude-sonnet-4-20250514",
|
||||||
|
"claude-sonnet-4.5": "claude-sonnet-4-5-20250514",
|
||||||
|
"claude-sonnet-4-5": "claude-sonnet-4-5-20250514",
|
||||||
|
"claude-opus-4": "claude-opus-4-20250514",
|
||||||
|
}
|
||||||
|
m = data.get("model", "")
|
||||||
|
if m in MODEL_MAP: data["model"] = MODEL_MAP[m]
|
||||||
body = json.dumps(data).encode()
|
body = json.dumps(data).encode()
|
||||||
headers = {"Content-Type":"application/json","x-api-key":API_KEY,"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"}
|
headers = {"Content-Type":"application/json","x-api-key":API_KEY,"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"}
|
||||||
for h in ["anthropic-version","anthropic-beta"]:
|
for h in ["anthropic-version","anthropic-beta"]:
|
||||||
@ -328,7 +449,6 @@ PYEOF
|
|||||||
sleep 1
|
sleep 1
|
||||||
echo " Claude API proxy: $(systemctl is-active claude-api-proxy)"
|
echo " Claude API proxy: $(systemctl is-active claude-api-proxy)"
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
' 2>/dev/null || true
|
' 2>/dev/null || true
|
||||||
|
|
||||||
# Dev mode for Tailscale HTTP access (cookies need Secure flag disabled over plain HTTP)
|
# Dev mode for Tailscale HTTP access (cookies need Secure flag disabled over plain HTTP)
|
||||||
@ -345,6 +465,39 @@ PYEOF
|
|||||||
fi
|
fi
|
||||||
' 2>/dev/null || true
|
' 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create data directories for DWN, content sharing, federation, identities
|
||||||
|
echo "$(timestamp) Ensuring data directories for DWN, content, federation..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
sudo mkdir -p /var/lib/archipelago/dwn/messages
|
||||||
|
sudo mkdir -p /var/lib/archipelago/dwn/protocols
|
||||||
|
sudo mkdir -p /var/lib/archipelago/content/files
|
||||||
|
sudo mkdir -p /var/lib/archipelago/federation
|
||||||
|
sudo mkdir -p /var/lib/archipelago/identities
|
||||||
|
sudo mkdir -p /var/lib/archipelago/tor-config
|
||||||
|
sudo chown -R archipelago:archipelago /var/lib/archipelago/dwn /var/lib/archipelago/content /var/lib/archipelago/federation /var/lib/archipelago/identities /var/lib/archipelago/tor-config 2>/dev/null || true
|
||||||
|
echo " Data directories OK"
|
||||||
|
' 2>/dev/null || true
|
||||||
|
|
||||||
|
# Deploy nostr-provider.js for NIP-07 iframe signing (window.nostr support)
|
||||||
|
echo "$(timestamp) Deploying nostr-provider.js..."
|
||||||
|
scp $SSH_OPTS "$PROJECT_DIR/neode-ui/public/nostr-provider.js" "$TARGET_HOST:/tmp/nostr-provider.js" 2>/dev/null && \
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" 'sudo cp /tmp/nostr-provider.js /opt/archipelago/web-ui/nostr-provider.js && echo " nostr-provider.js deployed"' 2>/dev/null || echo " (nostr-provider.js not found, skipping)"
|
||||||
|
|
||||||
|
# Sync nginx config (includes all app proxies, NIP-07 sub_filter, AIUI proxy, external URL proxies)
|
||||||
|
echo "$(timestamp) Syncing nginx config..."
|
||||||
|
scp $SSH_OPTS "$PROJECT_DIR/image-recipe/configs/nginx-archipelago.conf" "$TARGET_HOST:/tmp/nginx-archipelago.conf" 2>/dev/null && \
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago
|
||||||
|
# Also sync HTTPS snippets if they exist
|
||||||
|
sudo mkdir -p /etc/nginx/snippets
|
||||||
|
echo " Nginx config synced"
|
||||||
|
' 2>/dev/null || echo " (nginx config sync skipped)"
|
||||||
|
# Sync HTTPS app proxies snippet if it exists
|
||||||
|
if [ -f "$PROJECT_DIR/image-recipe/configs/snippets/archipelago-https-app-proxies.conf" ]; then
|
||||||
|
scp $SSH_OPTS "$PROJECT_DIR/image-recipe/configs/snippets/archipelago-https-app-proxies.conf" "$TARGET_HOST:/tmp/https-app-proxies.conf" 2>/dev/null && \
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" 'sudo cp /tmp/https-app-proxies.conf /etc/nginx/snippets/archipelago-https-app-proxies.conf' 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
# Fix FileBrowser — recreate if read-only root, create if missing
|
# Fix FileBrowser — recreate if read-only root, create if missing
|
||||||
echo "$(timestamp) Checking FileBrowser..."
|
echo "$(timestamp) Checking FileBrowser..."
|
||||||
ssh $SSH_OPTS "$TARGET_HOST" '
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
@ -421,16 +574,24 @@ PYEOF
|
|||||||
if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|archy-bitcoin-knots'; then
|
if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|archy-bitcoin-knots'; then
|
||||||
echo ' Creating Bitcoin Knots (mainnet, archipelago RPC)...'
|
echo ' Creating Bitcoin Knots (mainnet, archipelago RPC)...'
|
||||||
sudo mkdir -p /var/lib/archipelago/bitcoin
|
sudo mkdir -p /var/lib/archipelago/bitcoin
|
||||||
|
# Demo mode: prune=550 saves ~194GB disk, but disables txindex (incompatible with electrs)
|
||||||
|
if [ "$DEMO" = "true" ]; then
|
||||||
|
BTC_EXTRA_ARGS="-prune=550"
|
||||||
|
BTC_DBCACHE=512
|
||||||
|
else
|
||||||
|
BTC_EXTRA_ARGS="-txindex=1"
|
||||||
|
BTC_DBCACHE=4096
|
||||||
|
fi
|
||||||
sudo \$DOCKER run -d --name bitcoin-knots --restart unless-stopped \$NET_OPT \
|
sudo \$DOCKER run -d --name bitcoin-knots --restart unless-stopped \$NET_OPT \
|
||||||
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
--security-opt no-new-privileges:true \
|
--security-opt no-new-privileges:true \
|
||||||
-p 8332:8332 -p 8333:8333 \
|
-p 8332:8332 -p 8333:8333 \
|
||||||
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
|
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
|
||||||
docker.io/bitcoinknots/bitcoin:latest \
|
docker.io/bitcoinknots/bitcoin:latest \
|
||||||
-server=1 -txindex=1 \
|
-server=1 \$BTC_EXTRA_ARGS \
|
||||||
-rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \
|
-rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \
|
||||||
-rpcuser=archipelago -rpcpassword=archipelago123 \
|
-rpcuser=archipelago -rpcpassword=archipelago123 \
|
||||||
-dbcache=4096
|
-dbcache=\$BTC_DBCACHE
|
||||||
echo ' Bitcoin Knots started (sync may take hours)'
|
echo ' Bitcoin Knots started (sync may take hours)'
|
||||||
else
|
else
|
||||||
sudo \$DOCKER network connect archy-net bitcoin-knots 2>/dev/null || true
|
sudo \$DOCKER network connect archy-net bitcoin-knots 2>/dev/null || true
|
||||||
@ -672,25 +833,27 @@ PYEOF
|
|||||||
# Ensure services.json exists with default services
|
# Ensure services.json exists with default services
|
||||||
SERVICES_JSON=/var/lib/archipelago/tor/services.json
|
SERVICES_JSON=/var/lib/archipelago/tor/services.json
|
||||||
if [ ! -f "\$SERVICES_JSON" ]; then
|
if [ ! -f "\$SERVICES_JSON" ]; then
|
||||||
echo '{"services":[
|
sudo python3 -c '
|
||||||
{"name":"archipelago","local_port":80,"enabled":true},
|
import json
|
||||||
{"name":"lnd","local_port":8081,"enabled":true},
|
services = [
|
||||||
{"name":"btcpay","local_port":23000,"enabled":true},
|
{"name": "archipelago", "local_port": 80, "enabled": True},
|
||||||
{"name":"mempool","local_port":4080,"enabled":true},
|
{"name": "bitcoin", "local_port": 8333, "enabled": True},
|
||||||
{"name":"fedimint","local_port":8175,"enabled":true}
|
{"name": "electrs", "local_port": 50001, "enabled": True},
|
||||||
]}' | sudo tee "\$SERVICES_JSON" > /dev/null
|
{"name": "lnd", "local_port": 9735, "enabled": True},
|
||||||
|
{"name": "btcpay", "local_port": 23000, "enabled": True},
|
||||||
|
{"name": "mempool", "local_port": 4080, "enabled": True},
|
||||||
|
{"name": "fedimint", "local_port": 8175, "enabled": True}
|
||||||
|
]
|
||||||
|
with open("/var/lib/archipelago/tor/services.json", "w") as f:
|
||||||
|
json.dump({"services": services}, f, indent=2)
|
||||||
|
print("services.json created")
|
||||||
|
'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate torrc dynamically from services.json
|
# Generate torrc — use /var/lib/tor/ for hidden services (AppArmor-safe)
|
||||||
TORRC=/var/lib/archipelago/tor/torrc
|
sudo python3 -c '
|
||||||
echo 'SocksPort 9050' | sudo tee "\$TORRC" > /dev/null
|
|
||||||
echo 'ControlPort 0' | sudo tee -a "\$TORRC" > /dev/null
|
|
||||||
echo 'DataDirectory /var/lib/archipelago/tor' | sudo tee -a "\$TORRC" > /dev/null
|
|
||||||
|
|
||||||
# Read services from JSON and generate HiddenService lines
|
|
||||||
# Use python3 (available on Debian 12) to parse JSON and emit torrc lines
|
|
||||||
python3 << 'PYEOF' | sudo tee -a "\$TORRC" > /dev/null
|
|
||||||
import json
|
import json
|
||||||
|
lines = ["SocksPort 9050", "ControlPort 0", ""]
|
||||||
try:
|
try:
|
||||||
with open("/var/lib/archipelago/tor/services.json") as f:
|
with open("/var/lib/archipelago/tor/services.json") as f:
|
||||||
cfg = json.load(f)
|
cfg = json.load(f)
|
||||||
@ -698,45 +861,39 @@ try:
|
|||||||
if svc.get("enabled", True):
|
if svc.get("enabled", True):
|
||||||
n = svc["name"]
|
n = svc["name"]
|
||||||
p = svc["local_port"]
|
p = svc["local_port"]
|
||||||
print("HiddenServiceDir /var/lib/archipelago/tor/hidden_service_%s/" % n)
|
lines.append("HiddenServiceDir /var/lib/tor/hidden_service_%s" % n)
|
||||||
print("HiddenServicePort 80 127.0.0.1:%d" % p)
|
lines.append("HiddenServicePort %d 127.0.0.1:%d" % (p, p))
|
||||||
|
lines.append("")
|
||||||
except Exception:
|
except Exception:
|
||||||
# Fallback defaults
|
for n, p in [("archipelago",80),("bitcoin",8333),("electrs",50001),("lnd",9735),("btcpay",23000),("mempool",4080),("fedimint",8175)]:
|
||||||
for n, p in [("archipelago",80),("lnd",8081),("btcpay",23000),("mempool",4080),("fedimint",8175)]:
|
lines.append("HiddenServiceDir /var/lib/tor/hidden_service_%s" % n)
|
||||||
print("HiddenServiceDir /var/lib/archipelago/tor/hidden_service_%s/" % n)
|
lines.append("HiddenServicePort %d 127.0.0.1:%d" % (p, p))
|
||||||
print("HiddenServicePort 80 127.0.0.1:%d" % p)
|
lines.append("")
|
||||||
PYEOF
|
with open("/etc/tor/torrc", "w") as f:
|
||||||
|
f.write("\n".join(lines) + "\n")
|
||||||
|
print("torrc generated with %d services" % (len(lines) // 3))
|
||||||
|
'
|
||||||
|
|
||||||
|
# Remove any old Tor container (system Tor is preferred)
|
||||||
for c in \$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'archy-tor|^tor\$'); do
|
for c in \$(sudo \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'archy-tor|^tor\$'); do
|
||||||
sudo \$DOCKER stop \"\$c\" 2>/dev/null
|
sudo \$DOCKER stop \"\$c\" 2>/dev/null
|
||||||
sudo \$DOCKER rm -f \"\$c\" 2>/dev/null
|
sudo \$DOCKER rm -f \"\$c\" 2>/dev/null
|
||||||
done
|
done
|
||||||
if ! sudo \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-tor; then
|
|
||||||
echo ' Creating Tor container (host network for hidden services)...'
|
# Use system Tor (preferred — no AppArmor issues with default paths)
|
||||||
if sudo \$DOCKER run -d --name archy-tor --restart unless-stopped --network host \
|
if command -v tor >/dev/null 2>&1; then
|
||||||
-v /var/lib/archipelago/tor:/var/lib/archipelago/tor \
|
sudo systemctl enable tor 2>/dev/null
|
||||||
--entrypoint tor \
|
sudo systemctl restart tor@default 2>/dev/null
|
||||||
docker.io/andrius/alpine-tor:latest \
|
echo ' Using system Tor daemon'
|
||||||
-f /var/lib/archipelago/tor/torrc 2>/dev/null; then
|
else
|
||||||
echo ' Tor container started (andrius/alpine-tor)'
|
echo ' Installing system Tor...'
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq tor 2>/dev/null || true
|
||||||
|
if command -v tor >/dev/null 2>&1; then
|
||||||
|
sudo systemctl enable tor 2>/dev/null
|
||||||
|
sudo systemctl restart tor@default 2>/dev/null
|
||||||
|
echo ' System Tor installed and started'
|
||||||
else
|
else
|
||||||
echo ' Tor container image failed, trying system tor...'
|
echo ' WARNING: Could not install Tor'
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq tor 2>/dev/null || true
|
|
||||||
if command -v tor >/dev/null 2>&1; then
|
|
||||||
sudo cp /var/lib/archipelago/tor/torrc /etc/tor/torrc 2>/dev/null || true
|
|
||||||
sudo chown -R debian-tor:debian-tor /var/lib/archipelago/tor 2>/dev/null || true
|
|
||||||
# Let archipelago user read hostname files (group-readable)
|
|
||||||
sudo usermod -aG debian-tor archipelago 2>/dev/null || true
|
|
||||||
sudo chmod 750 /var/lib/archipelago/tor 2>/dev/null || true
|
|
||||||
sudo find /var/lib/archipelago/tor -name 'hidden_service_*' -type d -exec chmod 750 {} \; 2>/dev/null || true
|
|
||||||
sudo find /var/lib/archipelago/tor -name 'hostname' -exec chmod 640 {} \; 2>/dev/null || true
|
|
||||||
# Systemd override so Tor can write to custom data dir
|
|
||||||
sudo mkdir -p /etc/systemd/system/tor@default.service.d
|
|
||||||
echo -e '[Service]\nReadWriteDirectories=-/var/lib/archipelago/tor' | sudo tee /etc/systemd/system/tor@default.service.d/override.conf > /dev/null
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable tor 2>/dev/null
|
|
||||||
sudo systemctl restart tor 2>/dev/null
|
|
||||||
echo ' Using system Tor daemon'
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
" 2>&1 | sed 's/^/ /' || true
|
" 2>&1 | sed 's/^/ /' || true
|
||||||
@ -744,8 +901,8 @@ PYEOF
|
|||||||
# Tor diagnostic: check if hostname files exist (may take 30-60s after Tor starts)
|
# Tor diagnostic: check if hostname files exist (may take 30-60s after Tor starts)
|
||||||
echo " Checking Tor hostname files..."
|
echo " Checking Tor hostname files..."
|
||||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||||
# Check all hidden_service_* dirs for hostname files
|
# Check all hidden_service_* dirs for hostname files (check both paths)
|
||||||
for dir in /var/lib/archipelago/tor/hidden_service_*/; do
|
for dir in /var/lib/tor/hidden_service_*/ /var/lib/archipelago/tor/hidden_service_*/; do
|
||||||
[ -d \"\$dir\" ] || continue
|
[ -d \"\$dir\" ] || continue
|
||||||
svc=\$(basename \"\$dir\" | sed 's/hidden_service_//')
|
svc=\$(basename \"\$dir\" | sed 's/hidden_service_//')
|
||||||
f=\"\${dir}hostname\"
|
f=\"\${dir}hostname\"
|
||||||
@ -830,6 +987,190 @@ PYEOF
|
|||||||
" 2>&1 | sed 's/^/ /') || echo " (Fedimint fix timed out or skipped - run manually if needed)"
|
" 2>&1 | sed 's/^/ /') || echo " (Fedimint fix timed out or skipped - run manually if needed)"
|
||||||
section_end
|
section_end
|
||||||
|
|
||||||
|
# LND: Lightning Network Daemon (requires bitcoin-knots on archy-net)
|
||||||
|
echo "$(timestamp) Ensuring LND..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
DOCKER=podman
|
||||||
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||||
|
if ! sudo $DOCKER ps --format "{{.Names}}" 2>/dev/null | grep -qx lnd; then
|
||||||
|
if sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -qx lnd; then
|
||||||
|
sudo $DOCKER start lnd 2>/dev/null || true
|
||||||
|
echo " LND started (existing)"
|
||||||
|
else
|
||||||
|
echo " Creating LND..."
|
||||||
|
sudo mkdir -p /var/lib/archipelago/lnd
|
||||||
|
if [ ! -f /var/lib/archipelago/lnd/lnd.conf ]; then
|
||||||
|
cat > /tmp/lnd.conf <<LNDCONF
|
||||||
|
[Application Options]
|
||||||
|
listen=0.0.0.0:9735
|
||||||
|
rpclisten=0.0.0.0:10009
|
||||||
|
restlisten=0.0.0.0:8080
|
||||||
|
debuglevel=info
|
||||||
|
noseedbackup=true
|
||||||
|
tor.active=false
|
||||||
|
|
||||||
|
[Bitcoin]
|
||||||
|
bitcoin.mainnet=true
|
||||||
|
bitcoin.node=bitcoind
|
||||||
|
|
||||||
|
[Bitcoind]
|
||||||
|
bitcoind.rpchost=bitcoin-knots:8332
|
||||||
|
bitcoind.rpcuser=archipelago
|
||||||
|
bitcoind.rpcpass=archipelago123
|
||||||
|
bitcoind.rpcpolling=true
|
||||||
|
bitcoind.estimatemode=ECONOMICAL
|
||||||
|
|
||||||
|
[autopilot]
|
||||||
|
autopilot.active=false
|
||||||
|
LNDCONF
|
||||||
|
sudo cp /tmp/lnd.conf /var/lib/archipelago/lnd/lnd.conf
|
||||||
|
fi
|
||||||
|
sudo $DOCKER run -d --name lnd --restart unless-stopped --network archy-net \
|
||||||
|
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
|
--security-opt no-new-privileges:true \
|
||||||
|
-p 9735:9735 -p 10009:10009 -p 8080:8080 \
|
||||||
|
-v /var/lib/archipelago/lnd:/root/.lnd \
|
||||||
|
docker.io/lightninglabs/lnd:v0.18.4-beta
|
||||||
|
echo " LND created"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " LND already running"
|
||||||
|
fi
|
||||||
|
' 2>&1 | sed 's/^/ /' || true
|
||||||
|
|
||||||
|
# Home Assistant
|
||||||
|
echo "$(timestamp) Ensuring Home Assistant..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
DOCKER=podman
|
||||||
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||||
|
if ! sudo $DOCKER ps --format "{{.Names}}" 2>/dev/null | grep -qx homeassistant; then
|
||||||
|
if sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -qx homeassistant; then
|
||||||
|
sudo $DOCKER start homeassistant 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo " Creating Home Assistant..."
|
||||||
|
sudo mkdir -p /var/lib/archipelago/home-assistant
|
||||||
|
sudo $DOCKER run -d --name homeassistant --restart unless-stopped \
|
||||||
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
|
||||||
|
--security-opt no-new-privileges:true \
|
||||||
|
-p 8123:8123 -v /var/lib/archipelago/home-assistant:/config \
|
||||||
|
-e TZ=UTC \
|
||||||
|
docker.io/homeassistant/home-assistant:2024.1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Home Assistant already running"
|
||||||
|
fi
|
||||||
|
' 2>&1 | sed 's/^/ /' || true
|
||||||
|
|
||||||
|
# Grafana
|
||||||
|
echo "$(timestamp) Ensuring Grafana..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
DOCKER=podman
|
||||||
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||||
|
if ! sudo $DOCKER ps --format "{{.Names}}" 2>/dev/null | grep -qx grafana; then
|
||||||
|
if sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -qx grafana; then
|
||||||
|
sudo $DOCKER start grafana 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo " Creating Grafana..."
|
||||||
|
sudo mkdir -p /var/lib/archipelago/grafana
|
||||||
|
sudo chown 472:472 /var/lib/archipelago/grafana 2>/dev/null || true
|
||||||
|
sudo $DOCKER run -d --name grafana --restart unless-stopped \
|
||||||
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
|
||||||
|
--security-opt no-new-privileges:true \
|
||||||
|
-p 3000:3000 -v /var/lib/archipelago/grafana:/var/lib/grafana \
|
||||||
|
-e GF_PATHS_DATA=/var/lib/grafana -e GF_USERS_ALLOW_SIGN_UP=false \
|
||||||
|
docker.io/grafana/grafana:10.2.0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Grafana already running"
|
||||||
|
fi
|
||||||
|
' 2>&1 | sed 's/^/ /' || true
|
||||||
|
|
||||||
|
# Jellyfin
|
||||||
|
echo "$(timestamp) Ensuring Jellyfin..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
DOCKER=podman
|
||||||
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||||
|
if ! sudo $DOCKER ps --format "{{.Names}}" 2>/dev/null | grep -qx jellyfin; then
|
||||||
|
if sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -qx jellyfin; then
|
||||||
|
sudo $DOCKER start jellyfin 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo " Creating Jellyfin..."
|
||||||
|
sudo mkdir -p /var/lib/archipelago/jellyfin/config /var/lib/archipelago/jellyfin/cache
|
||||||
|
sudo $DOCKER run -d --name jellyfin --restart unless-stopped \
|
||||||
|
--cap-drop ALL --security-opt no-new-privileges:true \
|
||||||
|
-p 8096:8096 \
|
||||||
|
-v /var/lib/archipelago/jellyfin/config:/config \
|
||||||
|
-v /var/lib/archipelago/jellyfin/cache:/cache \
|
||||||
|
docker.io/jellyfin/jellyfin:10.8.13
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Jellyfin already running"
|
||||||
|
fi
|
||||||
|
' 2>&1 | sed 's/^/ /' || true
|
||||||
|
|
||||||
|
# Vaultwarden
|
||||||
|
echo "$(timestamp) Ensuring Vaultwarden..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
DOCKER=podman
|
||||||
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||||
|
if ! sudo $DOCKER ps --format "{{.Names}}" 2>/dev/null | grep -qx vaultwarden; then
|
||||||
|
if sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -qx vaultwarden; then
|
||||||
|
sudo $DOCKER start vaultwarden 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo " Creating Vaultwarden..."
|
||||||
|
sudo mkdir -p /var/lib/archipelago/vaultwarden
|
||||||
|
sudo $DOCKER run -d --name vaultwarden --restart unless-stopped \
|
||||||
|
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \
|
||||||
|
--security-opt no-new-privileges:true \
|
||||||
|
-p 8082:80 -v /var/lib/archipelago/vaultwarden:/data \
|
||||||
|
docker.io/vaultwarden/server:1.30.0-alpine
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Vaultwarden already running"
|
||||||
|
fi
|
||||||
|
' 2>&1 | sed 's/^/ /' || true
|
||||||
|
|
||||||
|
# SearXNG (privacy search engine — used by AIUI web search)
|
||||||
|
echo "$(timestamp) Ensuring SearXNG..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
DOCKER=podman
|
||||||
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||||
|
if ! sudo $DOCKER ps --format "{{.Names}}" 2>/dev/null | grep -qx searxng; then
|
||||||
|
if sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -qx searxng; then
|
||||||
|
sudo $DOCKER start searxng 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo " Creating SearXNG..."
|
||||||
|
sudo $DOCKER run -d --name searxng --restart unless-stopped \
|
||||||
|
--cap-drop ALL --security-opt no-new-privileges:true \
|
||||||
|
-p 8888:8080 \
|
||||||
|
docker.io/searxng/searxng:latest
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " SearXNG already running"
|
||||||
|
fi
|
||||||
|
' 2>&1 | sed 's/^/ /' || true
|
||||||
|
|
||||||
|
# Ollama (local LLM inference — used by AIUI)
|
||||||
|
echo "$(timestamp) Ensuring Ollama..."
|
||||||
|
ssh $SSH_OPTS "$TARGET_HOST" '
|
||||||
|
DOCKER=podman
|
||||||
|
command -v podman >/dev/null 2>&1 || DOCKER=docker
|
||||||
|
if ! sudo $DOCKER ps --format "{{.Names}}" 2>/dev/null | grep -qx ollama; then
|
||||||
|
if sudo $DOCKER ps -a --format "{{.Names}}" 2>/dev/null | grep -qx ollama; then
|
||||||
|
sudo $DOCKER start ollama 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo " Creating Ollama..."
|
||||||
|
sudo mkdir -p /var/lib/archipelago/ollama
|
||||||
|
sudo $DOCKER run -d --name ollama --restart unless-stopped \
|
||||||
|
--cap-drop ALL --security-opt no-new-privileges:true \
|
||||||
|
-p 11434:11434 -v /var/lib/archipelago/ollama:/root/.ollama \
|
||||||
|
docker.io/ollama/ollama:latest
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Ollama already running"
|
||||||
|
fi
|
||||||
|
' 2>&1 | sed 's/^/ /' || true
|
||||||
|
|
||||||
fi # end FRONTEND_ONLY guard
|
fi # end FRONTEND_ONLY guard
|
||||||
|
|
||||||
# Post-deploy health check — wait up to 60s for server to come healthy
|
# Post-deploy health check — wait up to 60s for server to come healthy
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user