- Add deploy.sh: idempotent production deploy (scaffold, build, systemd, health check) - Fix update.sh: align all paths from /opt/nextwks/ to /opt/nextworkspace/ (blueprint-compliant) - deploy.sh safe for first-time setup and subsequent updates - Creates full blueprint directory tree: config/, data/, logs/, src/core/
216 lines
6.6 KiB
Bash
Executable file
216 lines
6.6 KiB
Bash
Executable file
#!/bin/bash
|
|
# ============================================================
|
|
# NextWks — Production Deploy Script
|
|
# Idempotent: safe for first-time setup and subsequent updates.
|
|
# Run: sudo bash deploy.sh
|
|
# ============================================================
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
|
info() { echo -e "${BLUE}[+]${NC} $1"; }
|
|
ok() { echo -e "${GREEN}[✓]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
|
die() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
|
|
header(){ echo -e "\n${BOLD}${CYAN}── $1 ──${NC}"; }
|
|
|
|
# ============================================================
|
|
# CONFIG
|
|
# ============================================================
|
|
INSTALL_DIR="/opt/nextworkspace"
|
|
SRC_DIR="${INSTALL_DIR}/src/core"
|
|
CONFIG_DIR="${INSTALL_DIR}/config/nextworkspace"
|
|
DATA_DIR="${INSTALL_DIR}/data/core"
|
|
CERTS_DIR="${INSTALL_DIR}/data/certs"
|
|
LOGS_DIR="${INSTALL_DIR}/logs"
|
|
STATIC_DIR="${INSTALL_DIR}/static"
|
|
BIN_PATH="${INSTALL_DIR}/core"
|
|
CONFIG_PATH="${CONFIG_DIR}/config.yaml"
|
|
SERVICE_FILE="/etc/systemd/system/nextwks.service"
|
|
SVC_USER="nextwks"
|
|
REPO_URL="https://git.lohmar.co.uk/lexton-it/NextWks.git"
|
|
|
|
# Must be root
|
|
[ "${EUID:-$(id -u)}" -ne 0 ] && die "Run as root: sudo bash deploy.sh"
|
|
|
|
# ============================================================
|
|
# PHASE 1: DIRECTORY SCAFFOLD + SERVICE USER
|
|
# ============================================================
|
|
header "Phase 1: Directory Structure"
|
|
|
|
if ! id "$SVC_USER" &>/dev/null; then
|
|
useradd -r -s /usr/sbin/nologin -d /nonexistent "$SVC_USER"
|
|
ok "Created service user: $SVC_USER"
|
|
else
|
|
info "Service user exists: $SVC_USER"
|
|
fi
|
|
|
|
mkdir -p "$SRC_DIR" "$CONFIG_DIR/apps.d" "$DATA_DIR" "$CERTS_DIR" "$LOGS_DIR" "$STATIC_DIR"
|
|
chown -R "$SVC_USER:$SVC_USER" "$DATA_DIR" "$CERTS_DIR" "$LOGS_DIR" "$STATIC_DIR"
|
|
ok "Directory tree created at $INSTALL_DIR/"
|
|
|
|
# ============================================================
|
|
# PHASE 2: FETCH + BUILD
|
|
# ============================================================
|
|
header "Phase 2: Build"
|
|
|
|
if [ -d "$SRC_DIR/.git" ]; then
|
|
info "Pulling latest from origin..."
|
|
cd "$SRC_DIR"
|
|
git fetch origin main --quiet
|
|
git reset --hard origin/main --quiet
|
|
ok "Repo updated"
|
|
else
|
|
info "Cloning repository..."
|
|
git clone --depth 1 "$REPO_URL" "$SRC_DIR" --quiet
|
|
cd "$SRC_DIR"
|
|
ok "Repo cloned"
|
|
fi
|
|
|
|
VERSION=$(cat "$SRC_DIR/VERSION" 2>/dev/null || echo "dev")
|
|
COMMIT_SHA=$(cd "$SRC_DIR" && git rev-parse --short HEAD)
|
|
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
if ! command -v go &>/dev/null; then
|
|
die "Go is not installed. Run: apt install golang-go"
|
|
fi
|
|
|
|
info "Building v${VERSION} (${COMMIT_SHA})..."
|
|
cd "$SRC_DIR/src"
|
|
|
|
go build -ldflags="-s -w \
|
|
-X git.lohmar.co.uk/lexton-it/NextWks/core/version.Version=${VERSION} \
|
|
-X git.lohmar.co.uk/lexton-it/NextWks/core/version.BuildTime=${BUILD_TIME} \
|
|
-X git.lohmar.co.uk/lexton-it/NextWks/core/version.CommitSHA=${COMMIT_SHA}" \
|
|
-o "$BIN_PATH" .
|
|
|
|
chmod 755 "$BIN_PATH"
|
|
chown "$SVC_USER:$SVC_USER" "$BIN_PATH"
|
|
ok "Binary: $BIN_PATH"
|
|
|
|
# ============================================================
|
|
# PHASE 3: CONFIG + ASSETS
|
|
# ============================================================
|
|
header "Phase 3: Configuration"
|
|
|
|
if [ ! -f "$CONFIG_PATH" ]; then
|
|
ADMIN_TOKEN=$(openssl rand -hex 32 2>/dev/null || head -c32 /dev/urandom | xxd -p -c32)
|
|
SESSION_SECRET=$(openssl rand -hex 32 2>/dev/null || head -c32 /dev/urandom | xxd -p -c32)
|
|
|
|
cat > "$CONFIG_PATH" << CONFIGEOF
|
|
# NextWks — Production Configuration
|
|
# Generated: $(date)
|
|
server:
|
|
host: "0.0.0.0"
|
|
port: 8080
|
|
|
|
admin:
|
|
secret_token: "${ADMIN_TOKEN}"
|
|
|
|
database:
|
|
type: "sqlite"
|
|
path: "${DATA_DIR}/nextwks.db"
|
|
|
|
authelia:
|
|
host: "http://127.0.0.1:9091"
|
|
config_path: "/opt/authelia/configuration.yml"
|
|
users_db_path: "/opt/authelia/users_database.yml"
|
|
|
|
oidc:
|
|
issuer_url: "https://app.nextwks.eu/auth"
|
|
client_id: "nextwks"
|
|
client_secret: ""
|
|
redirect_url: "https://app.nextwks.eu/access"
|
|
|
|
smtp:
|
|
host: ""
|
|
port: 587
|
|
username: ""
|
|
password: ""
|
|
from: "noreply@nextwks.local"
|
|
|
|
session:
|
|
secret: "${SESSION_SECRET}"
|
|
expiry_minutes: 60
|
|
|
|
tls:
|
|
enabled: false
|
|
domain: ""
|
|
email: ""
|
|
storage_path: "${CERTS_DIR}"
|
|
staging: false
|
|
CONFIGEOF
|
|
|
|
chmod 600 "$CONFIG_PATH"
|
|
chown "$SVC_USER:$SVC_USER" "$CONFIG_PATH"
|
|
ok "Config: $CONFIG_PATH"
|
|
warn "IMPORTANT: Edit $CONFIG_PATH with your domain, email, and SMTP settings!"
|
|
else
|
|
ok "Config exists (not overwritten): $CONFIG_PATH"
|
|
fi
|
|
|
|
if [ -d "$SRC_DIR/app/static" ]; then
|
|
cp -r "$SRC_DIR/app/static/"* "$STATIC_DIR/" 2>/dev/null || true
|
|
chown -R "$SVC_USER:$SVC_USER" "$STATIC_DIR"
|
|
ok "Static assets copied"
|
|
fi
|
|
|
|
# ============================================================
|
|
# PHASE 4: SYSTEMD
|
|
# ============================================================
|
|
header "Phase 4: Systemd"
|
|
|
|
cat > "$SERVICE_FILE" << SERVICEEOF
|
|
[Unit]
|
|
Description=Next Workspace (NextWks) Core
|
|
Documentation=https://git.lohmar.co.uk/lexton-it/NextWks
|
|
After=network-online.target authelia.service
|
|
Wants=network-online.target authelia.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=${SVC_USER}
|
|
Group=${SVC_USER}
|
|
WorkingDirectory=${INSTALL_DIR}
|
|
ExecStart=${BIN_PATH} -config ${CONFIG_PATH}
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
NoNewPrivileges=yes
|
|
ProtectSystem=strict
|
|
ProtectHome=yes
|
|
ReadWritePaths=${DATA_DIR} ${LOGS_DIR} ${CERTS_DIR}
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SERVICEEOF
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable nextwks 2>/dev/null || true
|
|
systemctl restart nextwks
|
|
ok "Systemd unit installed and service restarted"
|
|
|
|
# ============================================================
|
|
# PHASE 5: HEALTH CHECK
|
|
# ============================================================
|
|
header "Phase 5: Health Check"
|
|
|
|
sleep 2
|
|
if curl -s --max-time 5 http://localhost:8080/api/health 2>/dev/null | grep -q '"status":"ok"'; then
|
|
ok "Health check passed — NextWks v${VERSION} (${COMMIT_SHA}) is running"
|
|
else
|
|
warn "Health check failed — check logs: journalctl -u nextwks -n 30"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}════════════════════════════════════════${NC}"
|
|
echo -e "${GREEN} NextWks v${VERSION} deployed${NC}"
|
|
echo -e "${GREEN}════════════════════════════════════════${NC}"
|
|
echo ""
|
|
info " Binary: ${BIN_PATH}"
|
|
info " Config: ${CONFIG_PATH}"
|
|
info " Data: ${DATA_DIR}"
|
|
info " Logs: journalctl -u nextwks -f"
|
|
info " Version: ${VERSION} (${COMMIT_SHA})"
|
|
echo ""
|