- Replace deploy.sh and install.sh with tools/nextwks.sh - Build in /tmp/nextwks-build (fresh clone every time), no more /opt/NextWks - Script saves itself to ~/nextwks.sh on --install for easy future access - Add AGENT.md with workflow rules for the new approach - Secrets persisted in /opt/backup/.env (JWT, SESSION, password hash) - Update README and CHANGELOG
244 lines
9.7 KiB
Bash
Executable file
244 lines
9.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO_URL="https://git.lohmar.co.uk/lexton-it/NextWks.git"
|
|
BUILD_DIR="/tmp/nextwks-build"
|
|
TARGET_DIR="/opt/nextworkspace"
|
|
BACKUP_DIR="/opt/backup"
|
|
NETWORK_NAME="nextwks-net"
|
|
HEALTH_CHECK_RETRIES=10
|
|
HEALTH_CHECK_INTERVAL=2
|
|
|
|
usage() {
|
|
echo "Usage: $0 [--install|--update|--destroy]"
|
|
echo " --install First-time setup on a bare VM (prompts for config)"
|
|
echo " --update Smart update: pull, build, copy, bounce containers"
|
|
echo " --destroy Full greenfield redeploy (uses saved secrets)"
|
|
exit 1
|
|
}
|
|
|
|
[ $# -eq 0 ] && usage
|
|
MODE="${1#--}"
|
|
case "$MODE" in install|update|destroy) ;; *) usage ;; esac
|
|
|
|
# --- Load existing env (if any) ---
|
|
if [ -f "$BACKUP_DIR/.env" ]; then
|
|
set -a; source "$BACKUP_DIR/.env"; set +a
|
|
elif [ -f "$TARGET_DIR/.env" ]; then
|
|
set -a; source "$TARGET_DIR/.env"; set +a
|
|
fi
|
|
DOMAIN="${DOMAIN:-nextwks.eu}"
|
|
|
|
echo "=== NextWorkspace ${MODE} ==="
|
|
|
|
# ============================================================
|
|
# 1. INSTALL MODE — first-time setup
|
|
# ============================================================
|
|
if [ "$MODE" = "install" ]; then
|
|
echo "[*] Installing system dependencies..."
|
|
apt-get update -qq && apt-get install -y -qq git build-essential curl podman podman-compose
|
|
|
|
if ! command -v go &>/dev/null; then
|
|
echo "[*] Installing Go..."
|
|
GO_URL="https://go.dev/dl/$(curl -sL https://go.dev/VERSION?m=text | head -1).linux-amd64.tar.gz"
|
|
curl -sL "$GO_URL" -o /tmp/go.tar.gz
|
|
rm -rf /usr/local/go
|
|
tar -C /usr/local -xzf /tmp/go.tar.gz
|
|
rm /tmp/go.tar.gz
|
|
echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh
|
|
chmod +x /etc/profile.d/go.sh
|
|
export PATH=$PATH:/usr/local/go/bin
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- NextWorkspace Configuration ---"
|
|
read -p "Domain [nextwks.eu]: " input; DOMAIN="${input:-$DOMAIN}"
|
|
read -p "TLS email (Let's Encrypt): " TLS_EMAIL
|
|
while [ -z "$TLS_EMAIL" ]; do read -p "TLS email (required): " TLS_EMAIL; done
|
|
while echo "$TLS_EMAIL" | grep -qv '@'; do read -p "Invalid email: " TLS_EMAIL; done
|
|
read -p "Admin username: " ADMIN_USERNAME
|
|
while [ -z "$ADMIN_USERNAME" ]; do read -p "Admin username (required): " ADMIN_USERNAME; done
|
|
ADMIN_PASSWORD=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 12)
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
echo " Domain: $DOMAIN"
|
|
echo " TLS email: $TLS_EMAIL"
|
|
echo " Admin username: $ADMIN_USERNAME"
|
|
echo " Admin password: $ADMIN_PASSWORD"
|
|
echo " Save this password — it won't be shown again!"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
read -p "SMTP host [smtp.openxchange.eu]: " SMTP_HOST; SMTP_HOST="${SMTP_HOST:-smtp.openxchange.eu}"
|
|
read -p "SMTP port [587]: " SMTP_PORT; SMTP_PORT="${SMTP_PORT:-587}"
|
|
read -p "SMTP user [post@nextwks.eu]: " SMTP_USER; SMTP_USER="${SMTP_USER:-post@nextwks.eu}"
|
|
read -sp "SMTP password: " SMTP_PASS; echo ""
|
|
[ -z "$SMTP_PASS" ] && echo "ERROR: SMTP password required" && exit 1
|
|
|
|
# Persist config to backup vault
|
|
mkdir -p "$BACKUP_DIR"
|
|
cat > "$BACKUP_DIR/.env" <<EOF
|
|
# NextWorkspace Configuration — auto-generated by nextwks.sh --install
|
|
DOMAIN=$DOMAIN
|
|
TLS_EMAIL=$TLS_EMAIL
|
|
ADMIN_USERNAME=$ADMIN_USERNAME
|
|
ADMIN_PASSWORD=$ADMIN_PASSWORD
|
|
SMTP_HOST=$SMTP_HOST
|
|
SMTP_PORT=$SMTP_PORT
|
|
SMTP_USER=$SMTP_USER
|
|
SMTP_PASS=$SMTP_PASS
|
|
EOF
|
|
chmod 600 "$BACKUP_DIR/.env"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 2. CLONE fresh (every mode — ensures latest code)
|
|
# ============================================================
|
|
echo "[*] Cloning repository..."
|
|
rm -rf "$BUILD_DIR"
|
|
git clone --depth 1 "$REPO_URL" "$BUILD_DIR"
|
|
cd "$BUILD_DIR"
|
|
|
|
# Save script to user's home for easy future access (--install only)
|
|
if [ "$MODE" = "install" ]; then
|
|
USER_HOME=$(eval echo "~${SUDO_USER:-}" 2>/dev/null || echo "$HOME")
|
|
cp "$BUILD_DIR/tools/nextwks.sh" "$USER_HOME/nextwks.sh"
|
|
chmod +x "$USER_HOME/nextwks.sh"
|
|
echo "[*] Saved to $USER_HOME/nextwks.sh — use it for future updates"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 3. BUILD static binary
|
|
# ============================================================
|
|
echo "[*] Building static binary..."
|
|
export PATH=$PATH:/usr/local/go/bin
|
|
CGO_ENABLED=0 go build -o nextworkspace .
|
|
|
|
# ============================================================
|
|
# 4. CREATE target directory structure
|
|
# ============================================================
|
|
mkdir -p "$TARGET_DIR/config/caddy" "$TARGET_DIR/config/authelia" \
|
|
"$TARGET_DIR/data/caddy" "$TARGET_DIR/data/authelia" \
|
|
"$TARGET_DIR/compose" "$TARGET_DIR/www" \
|
|
"$TARGET_DIR/config/nextworkspace" "$TARGET_DIR/logs"
|
|
|
|
# ============================================================
|
|
# 5. TEARDOWN (destroy mode only)
|
|
# ============================================================
|
|
if [ "$MODE" = "destroy" ]; then
|
|
echo "[*] Full teardown..."
|
|
podman stop caddy authelia launcher 2>/dev/null || true
|
|
podman rm caddy authelia launcher 2>/dev/null || true
|
|
rm -rf "$TARGET_DIR"
|
|
mkdir -p "$TARGET_DIR/config/caddy" "$TARGET_DIR/config/authelia" \
|
|
"$TARGET_DIR/data/caddy" "$TARGET_DIR/data/authelia" \
|
|
"$TARGET_DIR/compose" "$TARGET_DIR/www" \
|
|
"$TARGET_DIR/config/nextworkspace" "$TARGET_DIR/logs"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 6. COPY artifacts to target
|
|
# ============================================================
|
|
echo "[*] Copying artifacts..."
|
|
cp nextworkspace "$TARGET_DIR/nextworkspace"
|
|
cp "$BUILD_DIR/VERSION" "$TARGET_DIR/VERSION"
|
|
if [ -d "$BUILD_DIR/config/www" ]; then
|
|
cp -r "$BUILD_DIR/config/www"/* "$TARGET_DIR/www/"
|
|
fi
|
|
if [ -d "$BUILD_DIR/lng" ]; then
|
|
rm -rf "$TARGET_DIR/lng"
|
|
cp -r "$BUILD_DIR/lng" "$TARGET_DIR/lng"
|
|
fi
|
|
if [ -d "$BUILD_DIR/config/nextworkspace" ]; then
|
|
cp -r "$BUILD_DIR/config/nextworkspace"/* "$TARGET_DIR/config/nextworkspace/"
|
|
fi
|
|
|
|
# Restore .env from backup
|
|
if [ -f "$BACKUP_DIR/.env" ]; then
|
|
cp "$BACKUP_DIR/.env" "$TARGET_DIR/.env"
|
|
chmod 644 "$TARGET_DIR/.env"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 7. GENERATE config files with placeholder substitution
|
|
# (install + destroy modes; update skips to keep secrets stable)
|
|
# ============================================================
|
|
if [ "$MODE" != "update" ]; then
|
|
echo "[*] Generating config files..."
|
|
|
|
# Caddyfile
|
|
sed -e "s|{DOMAIN}|$DOMAIN|g" -e "s|{TLS_EMAIL}|${TLS_EMAIL:-admin@$DOMAIN}|g" \
|
|
"$BUILD_DIR/config/caddy/Caddyfile" > "$TARGET_DIR/config/caddy/Caddyfile"
|
|
|
|
# Authelia config — preserve existing secrets if present
|
|
JWT_SECRET="${JWT_SECRET:-$(openssl rand -hex 32)}"
|
|
SESSION_SECRET="${SESSION_SECRET:-$(openssl rand -hex 32)}"
|
|
sed -e "s|{DOMAIN}|$DOMAIN|g" -e "s|{JWT_SECRET}|$JWT_SECRET|g" \
|
|
-e "s|{SESSION_SECRET}|$SESSION_SECRET|g" \
|
|
-e "s|{SMTP_HOST}|${SMTP_HOST:-smtp.openxchange.eu}|g" \
|
|
-e "s|{SMTP_PORT}|${SMTP_PORT:-587}|g" \
|
|
-e "s|{SMTP_USER}|${SMTP_USER:-post@nextwks.eu}|g" \
|
|
-e "s|{SMTP_PASS}|$SMTP_PASS|g" \
|
|
"$BUILD_DIR/config/authelia/configuration.yml" > "$TARGET_DIR/config/authelia/configuration.yml"
|
|
|
|
# Users database
|
|
ADMIN_PASSWORD_HASH="${ADMIN_PASSWORD_HASH:-}"
|
|
if [ -z "$ADMIN_PASSWORD_HASH" ] && [ -n "${ADMIN_PASSWORD:-}" ]; then
|
|
ADMIN_PASSWORD_HASH=$(cd "$BUILD_DIR" && go run ./tools/hash-password/ "$ADMIN_PASSWORD" 2>/dev/null || echo "")
|
|
fi
|
|
sed -e "s|{ADMIN_PASSWORD_HASH}|$ADMIN_PASSWORD_HASH|g" \
|
|
-e "s|{TLS_EMAIL}|${TLS_EMAIL:-admin@$DOMAIN}|g" \
|
|
"$BUILD_DIR/config/authelia/users_database.yml" > "$TARGET_DIR/config/authelia/users_database.yml"
|
|
|
|
# Persist generated secrets so --destroy is idempotent
|
|
if [ -f "$BACKUP_DIR/.env" ]; then
|
|
# Update existing .env with any new secrets
|
|
sed -i "/^JWT_SECRET=/d; /^SESSION_SECRET=/d; /^ADMIN_PASSWORD_HASH=/d" "$BACKUP_DIR/.env" 2>/dev/null || true
|
|
fi
|
|
echo "JWT_SECRET=$JWT_SECRET" >> "$BACKUP_DIR/.env"
|
|
echo "SESSION_SECRET=$SESSION_SECRET" >> "$BACKUP_DIR/.env"
|
|
[ -n "$ADMIN_PASSWORD_HASH" ] && echo "ADMIN_PASSWORD_HASH=$ADMIN_PASSWORD_HASH" >> "$BACKUP_DIR/.env"
|
|
chmod 600 "$BACKUP_DIR/.env"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 8. DEPLOY stack
|
|
# ============================================================
|
|
echo "[*] Deploying containers on $NETWORK_NAME..."
|
|
|
|
# Ensure network exists
|
|
podman network create "$NETWORK_NAME" 2>/dev/null || true
|
|
|
|
# AUTHELIA_SECRET is SESSION_SECRET (Authelia session.secret)
|
|
AUTHELIA_SECRET="${SESSION_SECRET:-}"
|
|
if [ -z "$AUTHELIA_SECRET" ]; then
|
|
# Update mode — extract from existing config
|
|
AUTHELIA_SECRET=$(sed -n '/^session:/,/^[a-z]/p' "$TARGET_DIR/config/authelia/configuration.yml" \
|
|
| grep 'secret:' | awk '{print $2}' 2>/dev/null || echo "")
|
|
fi
|
|
|
|
# Generate compose file with substituted secret
|
|
sed -e "s|{AUTHELIA_SECRET}|$AUTHELIA_SECRET|g" \
|
|
"$BUILD_DIR/compose/stack.yaml" > "$TARGET_DIR/compose/stack.yaml"
|
|
|
|
podman-compose -f "$TARGET_DIR/compose/stack.yaml" down 2>/dev/null || true
|
|
sleep 1
|
|
podman-compose -f "$TARGET_DIR/compose/stack.yaml" up -d 2>&1 || echo "[WARN] Stack deploy had issues"
|
|
|
|
# ============================================================
|
|
# 9. HEALTH CHECK
|
|
# ============================================================
|
|
echo "[*] Running health check..."
|
|
for i in $(seq 1 $HEALTH_CHECK_RETRIES); do
|
|
HEALTH=$(podman exec launcher wget -qO- http://127.0.0.1:9000/health 2>/dev/null || echo "")
|
|
if [ "$HEALTH" = "OK" ]; then
|
|
echo "[OK] NextWorkspace launcher is healthy"
|
|
echo "[OK] https://$DOMAIN/"
|
|
exit 0
|
|
fi
|
|
sleep $HEALTH_CHECK_INTERVAL
|
|
done
|
|
|
|
echo "[FAIL] Health check failed — launcher did not respond"
|
|
exit 1
|