Unified install/update/destroy script with ephemeral build dir
- 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
This commit is contained in:
parent
07288f4e19
commit
81e5505f2a
7 changed files with 396 additions and 301 deletions
100
AGENT.md
Normal file
100
AGENT.md
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
# NextWorkspace — Agent Workflow Instructions
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
- Format: `MILESTONE.FEATURE.PATCH.BUILD` (e.g., `0.1.0.0032`)
|
||||||
|
- Bump VERSION file on every change before commit.
|
||||||
|
- Every commit must be tagged with the version: `git tag v$(cat VERSION)`
|
||||||
|
|
||||||
|
## Deployment Model (Unified Script)
|
||||||
|
|
||||||
|
The project uses a single unified script at `tools/nextwks.sh` for all operations.
|
||||||
|
The old `deploy.sh` and `install.sh` are deprecated.
|
||||||
|
|
||||||
|
**Key principles:**
|
||||||
|
- Source of truth is **git remote only**. No local `/opt/NextWks` repo.
|
||||||
|
- Building happens in `/tmp/nextwks-build/` via `git clone --depth 1` (fresh every time).
|
||||||
|
- Runtime goes to `/opt/nextworkspace/` (configs, compose, binary, lng).
|
||||||
|
- Secrets persist in `/opt/backup/.env` and survive `--destroy`.
|
||||||
|
- Stack runs on a shared `nextwks-net` podman bridge network.
|
||||||
|
|
||||||
|
### Script Flags
|
||||||
|
|
||||||
|
| Flag | What it does |
|
||||||
|
|------|-------------|
|
||||||
|
| `--install` | First-time setup on a bare VM: installs deps, prompts for config, builds binary, generates configs, deploys stack |
|
||||||
|
| `--update` | Smart update: clones fresh, rebuilds binary, copies to target, bounces containers |
|
||||||
|
| `--destroy` | Full greenfield redeploy: tears down containers, wipes `/opt/nextworkspace/`, rebuilds from scratch using saved secrets from `/opt/backup/.env` |
|
||||||
|
|
||||||
|
### Workflow for Making Changes
|
||||||
|
|
||||||
|
1. Edit code in the development clone.
|
||||||
|
2. Test locally (e.g., `go build && go run .`).
|
||||||
|
3. Bump `VERSION` (increment BUILD).
|
||||||
|
4. Update `CHANGELOG.md`.
|
||||||
|
5. Commit: `git add -A && git commit -m "description"`
|
||||||
|
6. Tag: `git tag v$(cat VERSION)`
|
||||||
|
7. Push: `git push origin main --tags`
|
||||||
|
8. Deploy: `sudo bash ~/nextwks.sh --update`
|
||||||
|
|
||||||
|
> **Note:** The deploy script is downloaded by users via `curl` from the repo. For production deployment, users run:
|
||||||
|
> ```bash
|
||||||
|
> curl -sL https://git.lohmar.co.uk/lexton-it/NextWks/raw/branch/main/tools/nextwks.sh | sudo bash -s -- --install
|
||||||
|
> ```
|
||||||
|
|
||||||
|
## Build Process
|
||||||
|
|
||||||
|
- Static Go binary: `CGO_ENABLED=0 go build -o nextworkspace .`
|
||||||
|
- Must run on Alpine in the container (no glibc dependency).
|
||||||
|
- Binary runs as PID 1 in the `launcher` container.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Caddy (:80/:443, host ports)
|
||||||
|
├── auth.{DOMAIN} → Authelia :9091 (internal)
|
||||||
|
├── app.{DOMAIN} → Launcher :9000 (internal) with forward auth
|
||||||
|
└── www.{DOMAIN} → static files
|
||||||
|
|
||||||
|
All three containers on nextwks-net (podman bridge).
|
||||||
|
Only Caddy exposes ports to host.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config Template System
|
||||||
|
|
||||||
|
Config files use `{PLACEHOLDER}` syntax. The script substitutes values at deploy time:
|
||||||
|
|
||||||
|
| File | Placeholders |
|
||||||
|
|------|-------------|
|
||||||
|
| `config/caddy/Caddyfile` | `{DOMAIN}`, `{TLS_EMAIL}` |
|
||||||
|
| `config/authelia/configuration.yml` | `{DOMAIN}`, `{JWT_SECRET}`, `{SESSION_SECRET}`, `{SMTP_HOST}`, `{SMTP_PORT}`, `{SMTP_USER}`, `{SMTP_PASS}` |
|
||||||
|
| `config/authelia/users_database.yml` | `{ADMIN_PASSWORD_HASH}`, `{TLS_EMAIL}` |
|
||||||
|
| `compose/stack.yaml` | `{AUTHELIA_SECRET}` (same as `SESSION_SECRET`) |
|
||||||
|
|
||||||
|
## Env Vault (`/opt/backup/.env`)
|
||||||
|
|
||||||
|
Persisted secrets across destroys:
|
||||||
|
```
|
||||||
|
DOMAIN=nextwks.eu
|
||||||
|
TLS_EMAIL=admin@nextwks.eu
|
||||||
|
ADMIN_USERNAME=master
|
||||||
|
ADMIN_PASSWORD=<generated>
|
||||||
|
SMTP_HOST=smtp.openxchange.eu
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=post@nextwks.eu
|
||||||
|
SMTP_PASS=<prompted>
|
||||||
|
JWT_SECRET=<auto-generated>
|
||||||
|
SESSION_SECRET=<auto-generated>
|
||||||
|
ADMIN_PASSWORD_HASH=<bcrypt hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health Check
|
||||||
|
|
||||||
|
After deploy, the script polls `podman exec launcher wget -qO- http://127.0.0.1:9000/health`
|
||||||
|
up to 10 times (2s interval). Expected response: `OK`.
|
||||||
|
|
||||||
|
## Commit Message Style
|
||||||
|
|
||||||
|
- Imperative mood ("Add", "Fix", "Update", "Bump")
|
||||||
|
- Reference the component if relevant ("launcher: add health endpoint")
|
||||||
|
- Keep under 72 chars for the first line
|
||||||
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -1,5 +1,21 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.0.0032 — 2026-07-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `tools/nextwks.sh` — unified install/update/destroy script
|
||||||
|
- `AGENT.md` — workflow instructions for agents
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Replaced `deploy.sh` and `install.sh` with single `tools/nextwks.sh`
|
||||||
|
- Build moved from `/opt/NextWks` (persistent git repo) to `/tmp/nextwks-build` (ephemeral clone)
|
||||||
|
- README.md updated for unified script workflow
|
||||||
|
- `SESSION_SECRET` persisted in `/opt/backup/.env` for idempotent `--destroy`
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `deploy.sh` (replaced by `tools/nextwks.sh --update / --destroy`)
|
||||||
|
- `install.sh` (replaced by `tools/nextwks.sh --install`)
|
||||||
|
|
||||||
## 0.1.0.0007 — 2026-07-08
|
## 0.1.0.0007 — 2026-07-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
51
README.md
51
README.md
|
|
@ -24,44 +24,63 @@ app.nextwks.eu :443 auth.nextwks.eu :443
|
||||||
- **Authelia**: OIDC provider, 2FA, identity store
|
- **Authelia**: OIDC provider, 2FA, identity store
|
||||||
- **Binary**: Go launcher + path-based reverse proxy to upstream apps
|
- **Binary**: Go launcher + path-based reverse proxy to upstream apps
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start (Bare VM)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./install.sh
|
curl -sL https://git.lohmar.co.uk/lexton-it/NextWks/raw/branch/main/tools/nextwks.sh \
|
||||||
|
| sudo bash -s -- --install
|
||||||
```
|
```
|
||||||
|
|
||||||
Prompts for domain, TLS email, and admin credentials. Installs dependencies, generates configs, deploys Caddy + Authelia + launcher.
|
Prompts for domain, TLS email, and admin credentials. Installs deps (Go, Podman, git),
|
||||||
|
clones repo to `/tmp/nextwks-build/`, builds binary, generates configs, deploys stack.
|
||||||
|
|
||||||
## Directory Layout
|
## Directory Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
/opt/nextworkspace/
|
/opt/nextworkspace/ # Runtime (freshly populated on every deploy)
|
||||||
├── config/
|
├── config/
|
||||||
│ ├── caddy/Caddyfile
|
│ ├── caddy/Caddyfile
|
||||||
│ ├── authelia/configuration.yml
|
│ ├── authelia/configuration.yml
|
||||||
│ ├── authelia/users_database.yml
|
│ ├── authelia/users_database.yml
|
||||||
│ └── nextworkspace/{config,apps}.yaml
|
│ └── nextworkspace/{config,apps}.yaml
|
||||||
├── data/
|
├── data/
|
||||||
│ ├── caddy/ (certs + runtime data)
|
│ ├── caddy/ (certs + runtime)
|
||||||
│ └── authelia/ (database)
|
│ └── authelia/ (database)
|
||||||
├── compose/
|
├── compose/stack.yaml
|
||||||
│ ├── caddy.yaml
|
|
||||||
│ └── authelia.yaml
|
|
||||||
├── www/ (landing page)
|
├── www/ (landing page)
|
||||||
└── nextworkspace (binary)
|
├── lng/ (translations)
|
||||||
|
└── nextworkspace (static Go binary)
|
||||||
|
|
||||||
/opt/backup/
|
/opt/backup/ # Secrets vault (survives --destroy)
|
||||||
├── .env (secrets vault)
|
├── .env
|
||||||
└── certficates/ (LE cert backup)
|
└── certificates/
|
||||||
|
|
||||||
|
/tmp/nextwks-build/ # Ephemeral build dir (git clone --depth 1)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deployment
|
## Operations
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./deploy.sh # Smart update (swap binary, restart)
|
# First-time install (download + run — saves itself to ~/nextwks.sh)
|
||||||
sudo ./deploy.sh --destroy # Full rebuild (certs backed up)
|
curl -sL https://git.lohmar.co.uk/lexton-it/NextWks/raw/branch/main/tools/nextwks.sh \
|
||||||
|
| sudo bash -s -- --install
|
||||||
|
|
||||||
|
# Smart update (pull, build, copy, restart)
|
||||||
|
sudo bash ~/nextwks.sh --update
|
||||||
|
|
||||||
|
# Full redeploy (tear down, rebuild from scratch with saved secrets)
|
||||||
|
sudo bash ~/nextwks.sh --destroy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Workflow (Development)
|
||||||
|
|
||||||
|
1. Edit code in your clone.
|
||||||
|
2. Bump `VERSION`, update `CHANGELOG.md`.
|
||||||
|
3. `git commit -m "message" && git tag v$(cat VERSION) && git push origin main --tags`
|
||||||
|
4. On the server: `sudo bash ~/nextwks.sh --update`
|
||||||
|
|
||||||
|
The script clones fresh from git every time — no stale repos, no permissions issues.
|
||||||
|
|
||||||
## Version
|
## Version
|
||||||
|
|
||||||
Current: 0.1.0.0007 — see [CHANGELOG.md](CHANGELOG.md)
|
Current: 0.1.0.0032 — see [CHANGELOG.md](CHANGELOG.md)
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.1.0.0031
|
0.1.0.0032
|
||||||
|
|
|
||||||
141
deploy.sh
141
deploy.sh
|
|
@ -1,141 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
REPO_DIR="/opt/NextWks"
|
|
||||||
TARGET_DIR="/opt/nextworkspace"
|
|
||||||
BACKUP_DIR="/opt/backup"
|
|
||||||
NETWORK_NAME="nextwks-net"
|
|
||||||
HEALTH_CHECK_RETRIES=10
|
|
||||||
HEALTH_CHECK_INTERVAL=2
|
|
||||||
|
|
||||||
# --- Load .env ---
|
|
||||||
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}"
|
|
||||||
|
|
||||||
# --- Mode ---
|
|
||||||
GREENFIELD=false
|
|
||||||
if [ "${1:-}" = "--destroy" ]; then GREENFIELD=true; echo "[MODE] Greenfield deploy (--destroy)"
|
|
||||||
elif [ ! -d "$TARGET_DIR" ]; then GREENFIELD=true; echo "[MODE] Greenfield deploy (target missing)"
|
|
||||||
else echo "[MODE] Smart update (target exists)"; fi
|
|
||||||
|
|
||||||
# --- Pull + build ---
|
|
||||||
cd "$REPO_DIR"
|
|
||||||
echo "[1/5] Pulling latest code..."
|
|
||||||
git config --global --add safe.directory /opt/NextWks 2>/dev/null || true
|
|
||||||
git pull 2>&1 || echo "[WARN] Git pull failed — using existing code"
|
|
||||||
|
|
||||||
echo "[2/5] Building binary (static)..."
|
|
||||||
export PATH=$PATH:/usr/local/go/bin
|
|
||||||
CGO_ENABLED=0 go build -o nextworkspace .
|
|
||||||
|
|
||||||
# --- Greenfield ---
|
|
||||||
if [ "$GREENFIELD" = true ]; then
|
|
||||||
echo "[3/5] Full teardown..."
|
|
||||||
systemctl stop nextworkspace 2>/dev/null || true
|
|
||||||
systemctl disable nextworkspace 2>/dev/null || true
|
|
||||||
rm -f /etc/systemd/system/nextworkspace.service
|
|
||||||
systemctl daemon-reload
|
|
||||||
# Kill any lingering Caddy processes on port 80/443
|
|
||||||
pkill -f "caddy" 2>/dev/null || true
|
|
||||||
# Force clean ALL old pods, containers, and networks
|
|
||||||
for p in $(podman pod ls -q 2>/dev/null); do podman pod rm -f "$p" 2>/dev/null || true; done
|
|
||||||
podman rm -f caddy authelia launcher 2>/dev/null || true
|
|
||||||
podman network rm "$NETWORK_NAME" 2>&1 || true
|
|
||||||
|
|
||||||
if [ -d "$TARGET_DIR" ]; then
|
|
||||||
chattr -R -i "$TARGET_DIR" 2>/dev/null || true
|
|
||||||
rm -rf "$TARGET_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[4/5] Building directories..."
|
|
||||||
mkdir -p "$TARGET_DIR/config/caddy"
|
|
||||||
mkdir -p "$TARGET_DIR/config/authelia"
|
|
||||||
mkdir -p "$TARGET_DIR/data/caddy"
|
|
||||||
mkdir -p "$TARGET_DIR/data/authelia"
|
|
||||||
mkdir -p "$TARGET_DIR/compose"
|
|
||||||
mkdir -p "$TARGET_DIR/www"
|
|
||||||
mkdir -p "$TARGET_DIR/config/nextworkspace"
|
|
||||||
mkdir -p "$TARGET_DIR/logs"
|
|
||||||
chown -R master:master "$TARGET_DIR/data/caddy" 2>/dev/null || true
|
|
||||||
chown -R master:master "$TARGET_DIR/logs" 2>/dev/null || true
|
|
||||||
|
|
||||||
if [ -f "$BACKUP_DIR/.env" ]; then
|
|
||||||
cp "$BACKUP_DIR/.env" "$TARGET_DIR/.env"
|
|
||||||
chmod 644 "$TARGET_DIR/.env"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate Caddyfile
|
|
||||||
sed -e "s|{DOMAIN}|$DOMAIN|g" -e "s|{TLS_EMAIL}|${TLS_EMAIL:-admin@$DOMAIN}|g" \
|
|
||||||
"$SCRIPT_DIR/config/caddy/Caddyfile" > "$TARGET_DIR/config/caddy/Caddyfile"
|
|
||||||
|
|
||||||
# Generate Authelia config
|
|
||||||
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" \
|
|
||||||
"$SCRIPT_DIR/config/authelia/configuration.yml" > "$TARGET_DIR/config/authelia/configuration.yml"
|
|
||||||
|
|
||||||
ADMIN_PASSWORD_HASH="${ADMIN_PASSWORD_HASH:-}"
|
|
||||||
if [ -z "$ADMIN_PASSWORD_HASH" ] && [ -n "${ADMIN_PASSWORD:-}" ] && [ -f "$SCRIPT_DIR/tools/hash-password/main.go" ]; then
|
|
||||||
ADMIN_PASSWORD_HASH=$(cd "$SCRIPT_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" \
|
|
||||||
"$SCRIPT_DIR/config/authelia/users_database.yml" > "$TARGET_DIR/config/authelia/users_database.yml"
|
|
||||||
|
|
||||||
cp nextworkspace "$TARGET_DIR/nextworkspace"
|
|
||||||
if [ -f "$REPO_DIR/VERSION" ]; then cp "$REPO_DIR/VERSION" "$TARGET_DIR/VERSION"; fi
|
|
||||||
if [ -d "$SCRIPT_DIR/config/www" ]; then cp -r "$SCRIPT_DIR/config/www"/* "$TARGET_DIR/www/"; fi
|
|
||||||
cp -r "$SCRIPT_DIR/config/nextworkspace"/* "$TARGET_DIR/config/nextworkspace/" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Copy language files
|
|
||||||
if [ -d "$SCRIPT_DIR/lng" ]; then
|
|
||||||
cp -r "$SCRIPT_DIR/lng" "$TARGET_DIR/"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create podman network and deploy Caddy + Authelia
|
|
||||||
echo "[5/5] Deploying containers on $NETWORK_NAME..."
|
|
||||||
podman network create "$NETWORK_NAME" 2>&1 || true
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# Extract AUTHELIA_SECRET from config
|
|
||||||
AUTHELIA_SECRET=$(grep -oP 'session_secret: \K.*' "$TARGET_DIR/config/authelia/configuration.yml" 2>/dev/null || echo "")
|
|
||||||
if [ -n "$AUTHELIA_SECRET" ] && ! grep -q "AUTHELIA_SECRET" "$BACKUP_DIR/.env" 2>/dev/null; then
|
|
||||||
echo "AUTHELIA_SECRET=$AUTHELIA_SECRET" >> "$BACKUP_DIR/.env"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate compose file with secret, then deploy
|
|
||||||
sed -e "s|{AUTHELIA_SECRET}|$AUTHELIA_SECRET|g" \
|
|
||||||
"$SCRIPT_DIR/compose/stack.yaml" > "$TARGET_DIR/compose/stack.yaml"
|
|
||||||
podman-compose -f "$TARGET_DIR/compose/stack.yaml" down 2>/dev/null || true
|
|
||||||
podman-compose -f "$TARGET_DIR/compose/stack.yaml" up -d 2>&1 || echo "[WARN] Stack deploy had issues"
|
|
||||||
|
|
||||||
# --- Smart update ---
|
|
||||||
else
|
|
||||||
echo "[3/5] Redeploying containers..."
|
|
||||||
podman stop caddy authelia launcher 2>/dev/null || true
|
|
||||||
podman rm caddy authelia launcher 2>/dev/null || true
|
|
||||||
podman-compose -f "$TARGET_DIR/compose/stack.yaml" up -d 2>&1 || echo "[WARN] Stack deploy had issues"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- 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"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo " Attempt $i/$HEALTH_CHECK_RETRIES — not ready yet..."
|
|
||||||
sleep $HEALTH_CHECK_INTERVAL
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[FAIL] Health check failed — launcher did not respond"
|
|
||||||
exit 1
|
|
||||||
143
install.sh
143
install.sh
|
|
@ -1,143 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# NextWorkspace Installer — bootstraps a bare Linux VM
|
|
||||||
# Idempotent: safe to run multiple times.
|
|
||||||
|
|
||||||
TARGET_DIR="/opt/nextworkspace"
|
|
||||||
BACKUP_DIR="/opt/backup"
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
|
|
||||||
# --- Create backup vault and runtime directories ---
|
|
||||||
echo "=== NextWorkspace Setup ==="
|
|
||||||
mkdir -p "$BACKUP_DIR/certificates"
|
|
||||||
mkdir -p "$TARGET_DIR/config/nextworkspace"
|
|
||||||
mkdir -p "$TARGET_DIR/config/caddy"
|
|
||||||
mkdir -p "$TARGET_DIR/config/authelia"
|
|
||||||
mkdir -p "$TARGET_DIR/data/caddy"
|
|
||||||
mkdir -p "$TARGET_DIR/data/authelia"
|
|
||||||
mkdir -p "$TARGET_DIR/compose"
|
|
||||||
mkdir -p "$TARGET_DIR/www"
|
|
||||||
mkdir -p "$TARGET_DIR/logs/caddy"
|
|
||||||
# Ensure data dirs are owned by the runtime user
|
|
||||||
chown -R master:master "$TARGET_DIR/data/caddy" 2>/dev/null || true
|
|
||||||
chown -R master:master "$TARGET_DIR/logs/caddy" 2>/dev/null || true
|
|
||||||
|
|
||||||
# --- Interactive prompts (no defaults — user enters everything) ---
|
|
||||||
read -p "Domain: " DOMAIN
|
|
||||||
while [ -z "$DOMAIN" ]; do
|
|
||||||
read -p "Domain (required): " DOMAIN
|
|
||||||
done
|
|
||||||
|
|
||||||
read -p "TLS email (Let's Encrypt): " TLS_EMAIL
|
|
||||||
while [ -z "$TLS_EMAIL" ]; do
|
|
||||||
read -p "TLS email (required): " TLS_EMAIL
|
|
||||||
done
|
|
||||||
# Basic email validation (must contain @)
|
|
||||||
while echo "$TLS_EMAIL" | grep -qv '@'; do
|
|
||||||
read -p "Invalid email — must contain @: " TLS_EMAIL
|
|
||||||
done
|
|
||||||
|
|
||||||
read -p "Admin username: " ADMIN_USERNAME
|
|
||||||
while [ -z "$ADMIN_USERNAME" ]; do
|
|
||||||
read -p "Admin username (required): " ADMIN_USERNAME
|
|
||||||
done
|
|
||||||
|
|
||||||
# Generate 12-char alphanumeric password (easy to type)
|
|
||||||
ADMIN_PASSWORD=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 12 2>/dev/null || date +%s | head -c 12)
|
|
||||||
|
|
||||||
# --- SMTP prompts ---
|
|
||||||
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 ""
|
|
||||||
if [ -z "$SMTP_PASS" ]; then
|
|
||||||
echo "[ERROR] SMTP password is required."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
read -p "IMAP host [imap.openxchange.eu]: " IMAP_HOST
|
|
||||||
IMAP_HOST="${IMAP_HOST:-imap.openxchange.eu}"
|
|
||||||
|
|
||||||
read -p "IMAP port [993]: " IMAP_PORT
|
|
||||||
IMAP_PORT="${IMAP_PORT:-993}"
|
|
||||||
|
|
||||||
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 ""
|
|
||||||
|
|
||||||
# Write .env file in backup vault (deploy.sh copies it to production)
|
|
||||||
ENV_FILE="$BACKUP_DIR/.env"
|
|
||||||
cat > "$ENV_FILE" <<EOF
|
|
||||||
# NextWorkspace Configuration
|
|
||||||
# This file is auto-generated by install.sh — do not edit manually
|
|
||||||
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
|
|
||||||
IMAP_HOST=$IMAP_HOST
|
|
||||||
IMAP_PORT=$IMAP_PORT
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod 600 "$ENV_FILE"
|
|
||||||
|
|
||||||
# ---- Go ----
|
|
||||||
if command -v go &>/dev/null; then
|
|
||||||
echo "[SKIP] Go already installed: $(go version)"
|
|
||||||
else
|
|
||||||
echo "[INSTALL] 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
|
|
||||||
echo "[OK] Go installed: $(go version)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- System deps ----
|
|
||||||
echo "[INSTALL] git, build-essential, podman, podman-compose..."
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get install -y -qq git build-essential curl podman podman-compose
|
|
||||||
|
|
||||||
# ---- Deploy: use current scripts + tools ---
|
|
||||||
REPO_DIR="/opt/NextWks"
|
|
||||||
mkdir -p "$REPO_DIR"
|
|
||||||
|
|
||||||
# Clone or update repo, then overlay our current code
|
|
||||||
echo "[SETUP] Preparing /opt/NextWks..."
|
|
||||||
if [ -d "$REPO_DIR/.git" ]; then
|
|
||||||
cd "$REPO_DIR" && git pull
|
|
||||||
elif command -v git &>/dev/null; then
|
|
||||||
# Try to clone the remote first, so git history is intact
|
|
||||||
git clone "https://git.lohmar.co.uk/lexton-it/NextWks.git" "$REPO_DIR.tmp" 2>/dev/null && \
|
|
||||||
mv "$REPO_DIR.tmp" "$REPO_DIR" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy current code on top (ensures latest changes)
|
|
||||||
mkdir -p "$REPO_DIR"
|
|
||||||
cp -r "$SCRIPT_DIR"/* "$REPO_DIR/"
|
|
||||||
cp "$SCRIPT_DIR"/.gitignore "$REPO_DIR/" 2>/dev/null || true
|
|
||||||
chmod +x "$REPO_DIR/deploy.sh" "$REPO_DIR/install.sh" 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "[DONE] Bootstrapping complete. Running first deploy..."
|
|
||||||
"$REPO_DIR/deploy.sh" --destroy
|
|
||||||
244
tools/nextwks.sh
Executable file
244
tools/nextwks.sh
Executable file
|
|
@ -0,0 +1,244 @@
|
||||||
|
#!/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
|
||||||
Loading…
Reference in a new issue