diff --git a/README.md b/README.md index d4bf712..fde296b 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,23 @@ NextWks/ - Go 1.22+ - Authelia v4.38 (install with `bash scripts/install-authelia.sh`) +### Production Install (recommended) + +```bash +curl -fsSL https://git.lohmar.co.uk/lexton-it/NextWks/raw/main/install.sh | bash +``` + +The installer walks you through: +- Email configuration (SMTP/IMAP) +- Admin user creation +- URL setup (workspace + auth + proxy) +- All secrets auto-generated +- Authelia configuration written +- Systemd service created +- Smoke test verification + +To re-run with saved answers: `./install.sh --from-env` + ### Development ```bash diff --git a/install.sh b/install.sh index 3118cc6..2ce3ed9 100755 --- a/install.sh +++ b/install.sh @@ -1,308 +1,260 @@ #!/bin/bash -# Next Workspace (NextWks) - Bare-Metal Installer -# Deploys compiled binaries to /opt/nextwks/ for production use +# ============================================================ +# Next Workspace (NextWks) — Installer +# +# curl -fsSL https://git.lohmar.co.uk/lexton-it/NextWks/raw/main/install.sh | bash # # Usage: -# ./install.sh Build and install -# ./install.sh --skip-build Install existing binaries only -# ./install.sh --build-only Compile binary only (no install) -# ./install.sh --config-only Generate config only -# ./install.sh --status Check installation health -# ./install.sh --uninstall Remove installation -# ./install.sh --help Show this help - +# ./install.sh Full interactive install +# ./install.sh --from-env Load answers from .env +# ./install.sh --status Check installation health +# ./install.sh --uninstall Remove everything +# ============================================================ set -euo pipefail # Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - +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' error() { echo -e "${RED}Error:${NC} $1" >&2; } success() { echo -e "${GREEN}$1${NC}"; } info() { echo -e "${BLUE}$1${NC}"; } -warn() { echo -e "${YELLOW}Warning:${NC} $1"; } +warn() { echo -e "${YELLOW}$1${NC}"; } +header() { echo -e "\n${BOLD}${CYAN}$1${NC}"; } -# Configuration +# ============================================================ +# PATHS +# ============================================================ REPO_DIR="$(cd "$(dirname "$0")" && pwd)" INSTALL_DIR="/opt/nextwks" BIN_DIR="${INSTALL_DIR}/bin" DATA_DIR="${INSTALL_DIR}/data" -MODULES_DIR="${INSTALL_DIR}/modules" STATIC_DIR="${INSTALL_DIR}/static" CONFIG_FILE="${INSTALL_DIR}/config.yaml" +ENV_FILE="${REPO_DIR}/.env" +AUTHELIA_DIR="/opt/authelia" +AUTHELIA_CONFIG="${AUTHELIA_DIR}/configuration.yml" SERVICE_FILE="/etc/systemd/system/nextwks.service" -HEALTH_URL="http://localhost:8080/api/health" -# Parse arguments -SKIP_BUILD=false -BUILD_ONLY=false -CONFIG_ONLY=false -UNINSTALL=false -CHECK_STATUS=false -ADMIN_USER="" +# ============================================================ +# DEFAULTS +# ============================================================ +SMTP_HOST_DEFAULT="smtp.openxchange.eu" +SMTP_PORT_DEFAULT="587" +SMTP_USER_DEFAULT="post@2-4-h.app" +IMAP_HOST_DEFAULT="imap.openxchange.eu" +IMAP_PORT_DEFAULT="993" +NEXTWKS_URL_DEFAULT="https://wks.lohmar.co.uk" +AUTH_URL_DEFAULT="https://auth.lohmar.co.uk" +PROXY_IP_DEFAULT="172.16.0.10" +# ============================================================ +# PARSE FLAGS +# ============================================================ +MODE="install" +FROM_ENV=false for arg in "$@"; do case "$arg" in - --skip-build) SKIP_BUILD=true ;; - --build-only) BUILD_ONLY=true ;; - --config-only) CONFIG_ONLY=true ;; - --uninstall) UNINSTALL=true ;; - --status) CHECK_STATUS=true ;; - --admin=*) ADMIN_USER="${arg#*=}" ;; - --admin) - error "Use --admin=username,email (e.g., --admin=cclohmar,claus@lohmar.co.uk)" - exit 1 - ;; + --from-env) FROM_ENV=true ;; + --status) MODE="status" ;; + --uninstall) MODE="uninstall" ;; --help) - cat << 'HELPEOF' -NextWks Installer — Bare-metal deployment tool - - ./install.sh Build and install - ./install.sh --skip-build Install existing binary only - ./install.sh --build-only Compile only (no install) - ./install.sh --config-only Generate config only - ./install.sh --admin=user,email Create initial admin user - ./install.sh --status Check installation health - ./install.sh --uninstall Remove installation - -Examples: - ./install.sh Full build + install - ./install.sh --admin=cclohmar,cl@sechpoint.app Create admin during install - ./install.sh --status Check what's running -HELPEOF - exit 0 - ;; - *) error "Unknown argument: $arg (use --help for options)"; exit 1 ;; + head -20 "$0" | grep "^#" | sed 's/^# //; 1s/.*/NextWks Installer v2026.6.0001/' + exit 0 ;; + *) error "Unknown: $arg (use --help)"; exit 1 ;; esac done -# --- Status Check --- -if [ "$CHECK_STATUS" = true ]; then - echo "" - info "Next Workspace Installation Status" - echo "-----------------------------------" - - if [ -f "${INSTALL_DIR}/bin/core" ]; then - VERSION=$("${INSTALL_DIR}/bin/core" 2>&1 | head -1 || echo "unknown") - success "✓ Binary installed: ${INSTALL_DIR}/bin/core" - else - warn "✗ Binary not found: ${INSTALL_DIR}/bin/core (not installed)" - fi - - if [ -f "$CONFIG_FILE" ]; then - success "✓ Configuration: $CONFIG_FILE" - else - warn "✗ Configuration: not found" - fi - - if systemctl is-active --quiet nextwks 2>/dev/null; then - success "✓ Service running: nextwks" - elif systemctl is-enabled --quiet nextwks 2>/dev/null; then - warn "⚠ Service nextwks: enabled but not running" - else - info " Service nextwks: not active" - fi - - if [ -f "/opt/authelia/authelia" ]; then - success "✓ Authelia found: /opt/authelia/" - if systemctl is-active --quiet authelia 2>/dev/null; then - success " Authelia service: running (port 9091)" - fi - else - warn "✗ Authelia: not installed" - fi - - # Try health check if server appears to be running - if command -v curl &>/dev/null; then - HEALTH=$(curl -s --max-time 2 "$HEALTH_URL" 2>/dev/null || echo "") - if [ "$HEALTH" = '{"status":"ok"}' ]; then - success "✓ Health endpoint: OK (http://localhost:8080)" - elif [ -n "$HEALTH" ]; then - warn "⚠ Health endpoint: unexpected response: $HEALTH" - else - info " Health endpoint: not responding" - fi - fi - - echo "" - exit 0 -fi - -# --- Uninstall --- -if [ "$UNINSTALL" = true ]; then +# ============================================================ +# UNINSTALL +# ============================================================ +if [ "$MODE" = "uninstall" ]; then info "Uninstalling Next Workspace..." - - if [ -f "$SERVICE_FILE" ]; then - systemctl stop nextwks 2>/dev/null || true - systemctl disable nextwks 2>/dev/null || true - rm -f "$SERVICE_FILE" - systemctl daemon-reload - info "Removed systemd service" - fi - - if [ -d "$INSTALL_DIR" ]; then - rm -rf "$INSTALL_DIR" - success "Removed $INSTALL_DIR" - else - info "No installation found at $INSTALL_DIR" - fi - + systemctl stop nextwks 2>/dev/null || true + systemctl disable nextwks 2>/dev/null || true + rm -f "$SERVICE_FILE" + systemctl daemon-reload + [ -d "$INSTALL_DIR" ] && { rm -rf "$INSTALL_DIR"; success "Removed $INSTALL_DIR"; } success "Uninstall complete" exit 0 fi -# --- Prerequisites --- -info "Checking prerequisites..." - -if [ "$SKIP_BUILD" = false ] && [ "$CONFIG_ONLY" = false ] && [ "$BUILD_ONLY" = false ]; then - if ! command -v go &>/dev/null; then - error "Go is not installed. Install Go 1.22+ first." - exit 1 - fi +# ============================================================ +# STATUS +# ============================================================ +if [ "$MODE" = "status" ]; then + echo ""; header "Next Workspace Status" + [ -f "${BIN_DIR}/core" ] && success "✓ Binary: ${BIN_DIR}/core" || warn "✗ Binary: not found" + [ -f "$CONFIG_FILE" ] && success "✓ Config: $CONFIG_FILE" || warn "✗ Config: not found" + systemctl is-active --quiet nextwks 2>/dev/null && success "✓ Service: running" || info " Service: stopped" + systemctl is-active --quiet authelia 2>/dev/null && success "✓ Authelia: running" || info " Authelia: stopped" + command -v curl &>/dev/null && [ "$(curl -s --max-time 2 http://localhost:8080/api/health 2>/dev/null)" = '{"status":"ok"}' ] && success "✓ API: OK" || info " API: not responding" + echo ""; exit 0 fi -# Check Authelia -if [ ! -f "/opt/authelia/authelia" ]; then - warn "Authelia is not installed at /opt/authelia/" - warn "Admin user management requires Authelia to function." - warn "Install with: bash $REPO_DIR/scripts/install-authelia.sh" +# ============================================================ +# INTERACTIVE WIZARD +# ============================================================ +gather_inputs() { + # Load from .env if --from-env + if [ "$FROM_ENV" = true ] && [ -f "$ENV_FILE" ]; then + source "$ENV_FILE" + success "Loaded configuration from $ENV_FILE" + return + fi + echo "" + header "┌─────────────────────────────────────────┐" + header "│ Next Workspace — Setup Wizard │" + header "└─────────────────────────────────────────┘" + info "Press Enter to accept defaults shown in [brackets]" + echo "" + + # --- Email --- + header "── Email Configuration ──" + read -p " SMTP Host [$SMTP_HOST_DEFAULT]: " SMTP_HOST + SMTP_HOST="${SMTP_HOST:-$SMTP_HOST_DEFAULT}" + read -p " SMTP Port [$SMTP_PORT_DEFAULT]: " SMTP_PORT + SMTP_PORT="${SMTP_PORT:-$SMTP_PORT_DEFAULT}" + read -p " IMAP Host [$IMAP_HOST_DEFAULT]: " IMAP_HOST + IMAP_HOST="${IMAP_HOST:-$IMAP_HOST_DEFAULT}" + read -p " IMAP Port [$IMAP_PORT_DEFAULT]: " IMAP_PORT + IMAP_PORT="${IMAP_PORT:-$IMAP_PORT_DEFAULT}" + read -p " SMTP Username [$SMTP_USER_DEFAULT]: " SMTP_USER + SMTP_USER="${SMTP_USER:-$SMTP_USER_DEFAULT}" + echo -n " SMTP Password []: "; read -s SMTP_PASS; echo "" + echo "" + + # --- Admin --- + header "── Admin User ──" + while [ -z "${ADMIN_UNAME:-}" ]; do + read -p " Username: " ADMIN_UNAME + [ -z "$ADMIN_UNAME" ] && warn "Username is required" + done + read -p " Email: " ADMIN_EMAIL + echo "" + + # --- URLs --- + header "── URLs ──" + read -p " NextWks URL [$NEXTWKS_URL_DEFAULT]: " NEXTWKS_URL + NEXTWKS_URL="${NEXTWKS_URL:-$NEXTWKS_URL_DEFAULT}" + read -p " Auth URL [$AUTH_URL_DEFAULT]: " AUTH_URL + AUTH_URL="${AUTH_URL:-$AUTH_URL_DEFAULT}" + read -p " Reverse Proxy [$PROXY_IP_DEFAULT]: " PROXY_IP + PROXY_IP="${PROXY_IP:-$PROXY_IP_DEFAULT}" + + # Extract domains from URLs + NEXTWKS_DOMAIN=$(echo "$NEXTWKS_URL" | sed 's|https\?://||;s|/.*||') + AUTH_DOMAIN=$(echo "$AUTH_URL" | sed 's|https\?://||;s|/.*||') + + # --- Confirm --- + echo "" + header "── Review ──" + info " SMTP: ${SMTP_USER}@${SMTP_HOST}:${SMTP_PORT}" + info " Admin: ${ADMIN_UNAME} (${ADMIN_EMAIL:-no email})" + info " NextWks: ${NEXTWKS_URL}" + info " Auth: ${AUTH_URL}" + info " Proxy: ${PROXY_IP}" + echo "" + read -p " Install with these settings? [Y/n]: " CONFIRM + [ "$CONFIRM" = "n" ] || [ "$CONFIRM" = "N" ] && { echo "Aborted."; exit 0; } + + # Save to .env for reuse + cat > "$ENV_FILE" << ENVEOF +SMTP_HOST="${SMTP_HOST}" +SMTP_PORT="${SMTP_PORT}" +IMAP_HOST="${IMAP_HOST}" +IMAP_PORT="${IMAP_PORT}" +SMTP_USER="${SMTP_USER}" +SMTP_PASS="${SMTP_PASS}" +ADMIN_UNAME="${ADMIN_UNAME}" +ADMIN_EMAIL="${ADMIN_EMAIL}" +NEXTWKS_URL="${NEXTWKS_URL}" +AUTH_URL="${AUTH_URL}" +PROXY_IP="${PROXY_IP}" +ENVEOF + success "Settings saved to $ENV_FILE" +} + +[ "$MODE" = "install" ] && gather_inputs + +# ============================================================ +# BUILD +# ============================================================ +header "── Building ──" +if ! command -v go &>/dev/null; then + error "Go 1.22+ required" + exit 1 fi -# --- Static Assets --- -if [ "$CONFIG_ONLY" = false ] && [ "$BUILD_ONLY" = false ]; then - info "Installing static assets..." - if [ -d "$REPO_DIR/app/static" ]; then - mkdir -p "$STATIC_DIR" - cp -r "$REPO_DIR/app/static"/* "$STATIC_DIR/" - success "Static assets installed to $STATIC_DIR" - fi -fi +cd "$REPO_DIR/src" +VERSION=$(cat "$REPO_DIR/VERSION" 2>/dev/null || echo "dev") +BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +COMMIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") -# --- Build --- -if [ "$SKIP_BUILD" = false ] && [ "$CONFIG_ONLY" = false ]; then - info "Building Next Workspace core..." +info "Running tests..." +go test -count=1 ./... 2>&1 | tail -2 || warn "Some tests failed — continuing" - # Build with version info from ldflags - VERSION=$(cat "$REPO_DIR/VERSION" 2>/dev/null || echo "dev") - BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - COMMIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") +info "Compiling (${VERSION})..." +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 "$REPO_DIR/app/core" . +success "Binary built: app/core (${VERSION})" - cd "$REPO_DIR/src" +# ============================================================ +# INSTALL +# ============================================================ +header "── Installing ──" +mkdir -p "$BIN_DIR" "$DATA_DIR" "$STATIC_DIR" +cp "$REPO_DIR/app/core" "$BIN_DIR/core" && chmod 755 "$BIN_DIR/core" +[ -d "$REPO_DIR/app/static" ] && cp -r "$REPO_DIR/app/static"/* "$STATIC_DIR/" +success "Copied files to $INSTALL_DIR/" - # Run tests first - info "Running tests..." - if ! go test -count=1 ./... 2>&1 | tail -1; then - warn "Some tests failed — continuing build anyway" - fi - - # Build with stripped symbols and version info - 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 "$REPO_DIR/app/core" . - success "Core binary built: app/core (${VERSION})" - - if [ "$BUILD_ONLY" = true ]; then - success "Build complete (--build-only, skipping install)" - exit 0 - fi -fi - -# --- Install --- -if [ "$CONFIG_ONLY" = false ]; then - info "Installing to $INSTALL_DIR..." - - # Create directory structure - mkdir -p "$BIN_DIR" "$DATA_DIR" "$MODULES_DIR" "$STATIC_DIR" - - # Copy binary - if [ -f "$REPO_DIR/app/core" ]; then - cp "$REPO_DIR/app/core" "$BIN_DIR/core" - chmod 755 "$BIN_DIR/core" - success "Installed binary to $BIN_DIR/core" - else - error "Binary not found at app/core. Run without --skip-build or build manually." - exit 1 - fi - - # Copy static assets if not already done - if [ -d "$REPO_DIR/app/static" ] && [ ! -f "$STATIC_DIR/manifest.json" ]; then - cp -r "$REPO_DIR/app/static"/* "$STATIC_DIR/" - fi - - # Copy modules if any exist - if [ -d "$REPO_DIR/app/modules" ] && [ "$(ls -A "$REPO_DIR/app/modules" 2>/dev/null)" ]; then - cp -r "$REPO_DIR/app/modules"/* "$MODULES_DIR/" - success "Installed modules" - fi -fi - -# --- Configuration --- -info "Generating configuration..." - -if [ ! -f "$CONFIG_FILE" ]; then - ADMIN_SECRET=$(openssl rand -hex 32 2>/dev/null || head -c 32 /dev/urandom | xxd -p -c 32) - SESSION_SECRET=$(openssl rand -hex 32 2>/dev/null || head -c 32 /dev/urandom | xxd -p -c 32) - - cat > "$CONFIG_FILE" << CONFIGEOF -# Next Workspace (NextWks) - Production Configuration -# Auto-generated by install.sh on $(date) +# ============================================================ +# CONFIG +# ============================================================ +header "── Configuration ──" +ADMIN_TOKEN=$(openssl rand -hex 32 2>/dev/null || head -c32 /dev/urandom | xxd -p -c32) +SESSION_KEY=$(openssl rand -hex 32 2>/dev/null || head -c32 /dev/urandom | xxd -p -c32) +cat > "$CONFIG_FILE" << CONFIGEOF +# Next Workspace — $(date +%Y-%m-%d) server: host: "0.0.0.0" port: 8080 - admin: - secret_token: "${ADMIN_SECRET}" - + 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" - + config_path: "${AUTHELIA_CONFIG}" + users_db_path: "${AUTHELIA_DIR}/users_database.yml" oidc: - issuer_url: "https://auth.lohmar.co.uk" + issuer_url: "${AUTH_URL}" client_id: "nextwks" client_secret: "" - redirect_url: "https://wks.lohmar.co.uk/auth/callback" - domain: "wks.lohmar.co.uk" - + redirect_url: "${NEXTWKS_URL}/auth/callback" + domain: "${NEXTWKS_DOMAIN}" smtp: - host: "" - port: 587 - username: "" - password: "" - from: "noreply@nextwks.local" - + host: "${SMTP_HOST}" + port: ${SMTP_PORT} + username: "${SMTP_USER}" + password: "${SMTP_PASS}" + from: "${SMTP_USER}" session: - secret: "${SESSION_SECRET}" + secret: "${SESSION_KEY}" expiry_minutes: 60 CONFIGEOF +chmod 600 "$CONFIG_FILE" +success "Config: $CONFIG_FILE" - chmod 600 "$CONFIG_FILE" - success "Configuration generated: $CONFIG_FILE" - echo "" - warn " Admin Secret Token: ${ADMIN_SECRET}" - warn " Store this securely! Required for admin API access." - echo "" -else - info "Configuration already exists at $CONFIG_FILE (not overwritten)" -fi - -# --- Systemd Service --- -if [ "$CONFIG_ONLY" = false ]; then - info "Setting up systemd service..." - - cat > "$SERVICE_FILE" << SERVICEEOF +# ============================================================ +# SYSTEMD +# ============================================================ +header "── Systemd ──" +cat > "$SERVICE_FILE" << SERVICEEOF [Unit] Description=Next Workspace (NextWks) Core After=network.target authelia.service @@ -318,126 +270,169 @@ RestartSec=5 StandardOutput=journal StandardError=journal -# Security hardening NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ProtectHome=yes -ReadWritePaths=${DATA_DIR} ${MODULES_DIR} /opt/authelia/users_database.yml +ReadWritePaths=${DATA_DIR} /opt/authelia/users_database.yml ReadOnlyPaths=${INSTALL_DIR}/config.yaml ${INSTALL_DIR}/static [Install] WantedBy=multi-user.target SERVICEEOF +systemctl daemon-reload +success "Service: $SERVICE_FILE" - systemctl daemon-reload - success "Systemd service created: $SERVICE_FILE" +# ============================================================ +# AUTHELIA CONFIG +# ============================================================ +if [ -f "${AUTHELIA_DIR}/authelia" ]; then + header "── Authelia Config ──" + JWT_SECRET=$(openssl rand -base64 32) + SESSION_SECRET=$(openssl rand -base64 32) + STORAGE_KEY=$(openssl rand -base64 32) + OIDC_HMAC=$(openssl rand -base64 32) + + # Generate RSA key for OIDC signing + openssl genrsa -out /tmp/nw-oidc.key 2048 2>/dev/null + OIDC_KEY=$(cat /tmp/nw-oidc.key) + rm -f /tmp/nw-oidc.key + + # Hash current admin password (if users exist) or generate one + ADMIN_HASH=$("${AUTHELIA_DIR}/authelia" crypto hash generate --password "$(openssl rand -base64 12)" 2>/dev/null | awk '{print $NF}' || echo "\$argon2id\$v=19\$m=65536,t=3,p=4\$placeholder\$placeholder") + + cat > "$AUTHELIA_CONFIG" << AUTHEOF +theme: light +default_redirection_url: "${NEXTWKS_URL}" +server: + host: 0.0.0.0 + port: 9091 +authentication_backend: + password_reset: + disable: false + file: + path: "${AUTHELIA_DIR}/users_database.yml" + watch: true +session: + name: authelia_session + secret: "${SESSION_SECRET}" + expiration: 1h + inactivity: 5m + cookies: + - domain: "${AUTH_DOMAIN}" + authelia_url: "${AUTH_URL}" + default_redirection_url: "${NEXTWKS_URL}" +notifier: + smtp: + address: "${SMTP_HOST}:${SMTP_PORT}" + username: "${SMTP_USER}" + password: "${SMTP_PASS}" + sender: "Authelia <${SMTP_USER}>" +storage: + encryption_key: "${STORAGE_KEY}" + local: + path: "${AUTHELIA_DIR}/db.sqlite3" +access_control: + default_policy: deny + rules: + - domain: "${AUTH_DOMAIN}" + policy: bypass + - domain: "${NEXTWKS_DOMAIN}" + policy: one_factor + - domain: "*.${NEXTWKS_DOMAIN}" + policy: one_factor +totp: + issuer: authelia.com +identity_providers: + oidc: + hmac_secret: "${OIDC_HMAC}" + jwks: + - key_id: "nextwks-oidc-key" + algorithm: "RS256" + use: "sig" + key: | +$(echo "$OIDC_KEY" | sed 's/^/ /') + clients: + - client_id: "nextwks" + client_name: "Next Workspace" + public: true + redirect_uris: + - "${NEXTWKS_URL}/auth/callback" + - "http://localhost:8080/auth/callback" + scopes: + - "openid" + - "profile" + - "email" + authorization_policy: "one_factor" + consent_mode: "pre-configured" + pre_configured_consent_duration: "1 year" + userinfo_signed_response_alg: "none" +AUTHEOF + chmod 600 "$AUTHELIA_CONFIG" + success "Authelia config written" + systemctl restart authelia 2>/dev/null && info "Authelia restarted" || info "Authelia not running (will start later)" fi -# --- Smoke Test --- -if [ "$CONFIG_ONLY" = false ]; then - info "Starting smoke test..." +# ============================================================ +# SMOKE TEST + ADMIN CREATION +# ============================================================ +header "── Verification ──" - # Start the server temporarily to verify it works - if [ -f "$BIN_DIR/core" ] && [ -f "$CONFIG_FILE" ]; then - "$BIN_DIR/core" -config "$CONFIG_FILE" & - SMOKE_PID=$! - sleep 2 +"$BIN_DIR/core" -config "$CONFIG_FILE" & +SMOKE_PID=$! +sleep 2 - if command -v curl &>/dev/null; then - RESPONSE=$(curl -s --max-time 3 "$HEALTH_URL" 2>/dev/null || echo "") - if [ "$RESPONSE" = '{"status":"ok"}' ]; then - success "Smoke test passed — server responds OK" - else - warn "Smoke test: server started but health check returned '$RESPONSE'" - fi +if command -v curl &>/dev/null; then + RESPONSE=$(curl -s --max-time 3 http://localhost:8080/api/health 2>/dev/null || echo "") + if [ "$RESPONSE" = '{"status":"ok"}' ]; then + success "Server started OK" + + # Create admin user + info "Creating admin user..." + RESULT=$(curl -s -X POST http://localhost:8080/admin/api/users \ + -H "Authorization: Bearer $ADMIN_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"users\":[{\"username\":\"$ADMIN_UNAME\",\"display_name\":\"$ADMIN_UNAME\",\"email\":\"$ADMIN_EMAIL\",\"role\":\"admin\",\"groups\":\"admins\"}]}") + + ADMIN_PASS=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['results'][0].get('generated_password',''))" 2>/dev/null || echo "") + ADMIN_ERROR=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['results'][0].get('error',''))" 2>/dev/null || echo "") + + if [ -n "$ADMIN_PASS" ]; then + success "Admin user created!" + elif [ -n "$ADMIN_ERROR" ]; then + warn "Admin creation: $ADMIN_ERROR" fi - - # --- Admin User Creation --- - if [ "$CONFIG_ONLY" = false ]; then - # Read admin token from config - ADMIN_TOKEN=$(grep secret_token "$CONFIG_FILE" | head -1 | sed 's/.*: *"*//;s/"*$//' | xargs) - ADMIN_API="http://localhost:8080/admin/api/users" - - # Check if interactive or --admin flag was provided - if [ -t 0 ] && [ -z "$ADMIN_USER" ] && [ ! -f "$CONFIG_FILE.initialized" ]; then - echo "" - info "No --admin flag provided. Create an initial admin user?" - read -p "Enter username:email (or press Enter to skip): " ADMIN_INPUT - if [ -n "$ADMIN_INPUT" ]; then - ADMIN_USER="$ADMIN_INPUT" - fi - fi - - if [ -n "$ADMIN_USER" ]; then - # Parse username,email - ADMIN_UNAME="${ADMIN_USER%%,*}" - ADMIN_EMAIL="${ADMIN_USER#*,}" - if [ "$ADMIN_UNAME" = "$ADMIN_EMAIL" ]; then - ADMIN_EMAIL="" - fi - - info "Creating admin user: $ADMIN_UNAME..." - RESULT=$(curl -s -X POST "$ADMIN_API" \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"users\":[{\"username\":\"$ADMIN_UNAME\",\"display_name\":\"$ADMIN_UNAME\",\"email\":\"$ADMIN_EMAIL\",\"role\":\"admin\",\"groups\":\"admins\"}]}") - - PASSWORD=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['results'][0].get('generated_password',''))" 2>/dev/null || echo "") - ERROR=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['results'][0].get('error',''))" 2>/dev/null || echo "") - - if [ -n "$PASSWORD" ]; then - success "Admin user created!" - echo "" - warn " ┌─────────────────────────────────────────┐" - warn " │ Username: $ADMIN_UNAME" - warn " │ Email: ${ADMIN_EMAIL:-}" - warn " │ Password: $PASSWORD" - warn " │ Groups: admins" - warn " └─────────────────────────────────────────┘" - echo "" - warn " Save this password! It cannot be recovered." - warn " User will be synced to Authelia automatically." - echo "" - elif [ -n "$ERROR" ]; then - warn "Admin creation failed: $ERROR" - else - warn "Could not parse response from admin API" - fi - fi - fi - - # Mark as initialized to skip interactive prompt next time - touch "$CONFIG_FILE.initialized" 2>/dev/null || true - - kill $SMOKE_PID 2>/dev/null || true - wait $SMOKE_PID 2>/dev/null || true + else + warn "Health check failed: $RESPONSE" fi fi -# --- Summary --- +kill $SMOKE_PID 2>/dev/null; wait $SMOKE_PID 2>/dev/null + +# ============================================================ +# SUMMARY +# ============================================================ echo "" -success "═══════════════════════════════════════════" -success " Next Workspace (NextWks) installed!" -success "═══════════════════════════════════════════" +success "════════════════════════════════════════" +success " Next Workspace v${VERSION} installed" +success "════════════════════════════════════════" echo "" -info " Binary: ${BIN_DIR}/core" -info " Config: ${CONFIG_FILE}" -info " Data: ${DATA_DIR}/" -info " Static assets: ${STATIC_DIR}/" -info " Service: systemctl start nextwks" +info " Workspace: ${NEXTWKS_URL}" +info " Auth: ${AUTH_URL}" +info " Admin UI: ${NEXTWKS_URL}/admin" echo "" -info " Workspace: https://wks.lohmar.co.uk/" -info " Admin UI: http://localhost:8080/admin" -info " Health API: http://localhost:8080/api/health" -info " Auth status: http://localhost:8080/auth/status" +info " Start: systemctl enable --now nextwks" +info " Logs: journalctl -u nextwks -f" +info " Status: ./install.sh --status" echo "" -info " Start: systemctl enable --now nextwks" -info " Logs: journalctl -u nextwks -f" -info " Status: ./install.sh --status" -echo "" -warn " Next step: Register NextWks as OIDC client in Authelia:" -warn " • Edit /opt/authelia/configuration.yml" -warn " • Add client_id: nextwks with redirect_uri: https://wks.lohmar.co.uk/auth/callback" -warn " • Restart: systemctl restart authelia" +if [ -n "${ADMIN_PASS:-}" ]; then + warn " ┌─────────────────────────────────────────┐" + warn " │ Admin login: ${AUTH_URL}" + warn " │ Username: ${ADMIN_UNAME}" + warn " │ Password: ${ADMIN_PASS}" + warn " │ Role: admin" + warn " └─────────────────────────────────────────┘" + echo "" + warn " Save this password! It cannot be recovered." +fi echo ""