feat: unified launcher, multi-OS hardening, login alerts & auto-updates
Restructure around a single entry point (automations.sh) with a Gum wizard and a self-extracting bundle for repo-less installs. Add scripts/oslib.sh so the provisioning scripts (setup-host, harden-ssh, harden-jumphost, sshuser) run on Alpine/Debian/Alma; seed root keys from globals/. - ntfy SSH-login alerts (user, source IP, key, region, jump target) via pam_exec - daily auto-updates with AUTO_REBOOT=idle (reboots only when no SSH active) and opt-in Alpine stable-branch upgrades - generic + per-deployment cloud-init; Gitea release workflow on tag - README/LICENSE/.gitignore/.gitattributes (force LF); repo URLs -> Gitea
This commit is contained in:
58 files changed
+6225
-284
No files matched your search
@@ -0,0 +1,17 @@
|
||||
# Copy to .env and fill in. docker compose picks .env up automatically.
|
||||
|
||||
# ─── Public hostname ────────────────────────────────────────────────────────
|
||||
# Bare hostname (no scheme) where beszel will be reached.
|
||||
BESZEL_DOMAIN=monitoring.example.com
|
||||
|
||||
# Email for Let's Encrypt registration / expiry notifications.
|
||||
ACME_EMAIL=admin@example.com
|
||||
|
||||
# Allow OIDC sign-in to auto-create user accounts on first login. Set to
|
||||
# "false" if you want to manually pre-create users in the admin UI before
|
||||
# anyone can log in.
|
||||
USER_CREATION=true
|
||||
|
||||
# ─── Image tags ─────────────────────────────────────────────────────────────
|
||||
BESZEL_TAG=latest
|
||||
CADDY_TAG=2-alpine
|
||||
@@ -0,0 +1,34 @@
|
||||
# Caddyfile for beszel stack.
|
||||
#
|
||||
# Auto-issues a Let's Encrypt cert for $BESZEL_DOMAIN and reverse-proxies
|
||||
# to the beszel hub on :8090. Agents and the web UI both go through this.
|
||||
|
||||
{
|
||||
email {$ACME_EMAIL}
|
||||
}
|
||||
|
||||
{$BESZEL_DOMAIN} {
|
||||
encode zstd gzip
|
||||
|
||||
reverse_proxy beszel:8090 {
|
||||
header_up X-Real-IP {http.request.remote.host}
|
||||
# WebSocket / long-lived agent push streams.
|
||||
flush_interval -1
|
||||
transport http {
|
||||
read_timeout 10m
|
||||
write_timeout 10m
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
X-Content-Type-Options "nosniff"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
-Server
|
||||
}
|
||||
|
||||
log {
|
||||
output stdout
|
||||
format console
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# beszel
|
||||
|
||||
[Beszel](https://beszel.dev) monitoring hub behind Caddy. Agents push metrics to
|
||||
the hub; OIDC sign-in is configured in the admin UI after first deploy.
|
||||
|
||||
## Required `.env` values
|
||||
|
||||
| Variable | Notes |
|
||||
|----------|-------|
|
||||
| `BESZEL_DOMAIN` | Public hostname (e.g. `monitoring.example.com`). |
|
||||
| `ACME_EMAIL` | Let's Encrypt registration email. |
|
||||
| `USER_CREATION` | `true` lets OIDC auto-create accounts on first login. |
|
||||
|
||||
See [`.env.example`](.env.example) for image tags.
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
./automations.sh # Deploy on this host → deploy: beszel
|
||||
```
|
||||
|
||||
Or build + run the self-contained artifact:
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
scp deploy.sh root@host:
|
||||
ssh root@host 'bash deploy.sh'
|
||||
# non-interactive:
|
||||
# BESZEL_DOMAIN=monitoring.example.com ACME_EMAIL=me@example.com SKIP_PROMPTS=1 bash deploy.sh
|
||||
```
|
||||
|
||||
Unattended provisioning: [`cloud-init.yml`](cloud-init.yml).
|
||||
|
||||
## Notes
|
||||
|
||||
- No Anubis PoW gate here — it would break agent connections.
|
||||
- For OIDC login, point Beszel at your [pocket-id](../pocket-id/) in the admin UI.
|
||||
- DNS for `BESZEL_DOMAIN` must resolve to the host and 80/443 be reachable
|
||||
before deploy.
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build.sh -- (re)embed docker-compose.yml, Caddyfile, .env.example into
|
||||
# deploy.sh as a base64-encoded tar.gz payload after __ARCHIVE_BELOW__.
|
||||
# Idempotent: strips any existing payload first.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
SCRIPT="$DIR/deploy.sh"
|
||||
MARKER="__ARCHIVE_BELOW__"
|
||||
|
||||
[[ -f "$SCRIPT" ]] || { echo "deploy.sh not found at $SCRIPT" >&2; exit 1; }
|
||||
for f in docker-compose.yml Caddyfile .env.example; do
|
||||
[[ -f "$DIR/$f" ]] || { echo "Missing $DIR/$f" >&2; exit 1; }
|
||||
done
|
||||
|
||||
PAYLOAD=$(tar -czf - -C "$DIR" docker-compose.yml Caddyfile .env.example | base64)
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap 'rm -f "$TMP"' EXIT
|
||||
|
||||
sed "/^${MARKER}\$/,\$d" "$SCRIPT" > "$TMP"
|
||||
{
|
||||
echo "$MARKER"
|
||||
echo "$PAYLOAD"
|
||||
} >> "$TMP"
|
||||
|
||||
mv "$TMP" "$SCRIPT"
|
||||
chmod +x "$SCRIPT"
|
||||
trap - EXIT
|
||||
|
||||
size=$(wc -c < "$SCRIPT")
|
||||
echo "Built $SCRIPT (${size} bytes)"
|
||||
@@ -0,0 +1,32 @@
|
||||
#cloud-config
|
||||
#
|
||||
# Beszel (monitoring hub) — unattended deploy on a fresh Alpine host.
|
||||
#
|
||||
# Fill in REPO_URL and the values in the runcmd block, then paste this as the
|
||||
# instance user-data. DNS for BESZEL_DOMAIN must point at this host and ports
|
||||
# 80/443 must be reachable before boot, or the LE cert request fails.
|
||||
# OIDC sign-in is configured afterwards in the Beszel admin UI.
|
||||
|
||||
packages:
|
||||
- git
|
||||
|
||||
runcmd:
|
||||
- hostnamectl set-hostname beszel || true
|
||||
- |
|
||||
set -e
|
||||
REPO_URL=https://git.anomalous.dev/57_Wolve/automations.git
|
||||
REPO_BRANCH=main
|
||||
git clone --depth 1 --branch "$REPO_BRANCH" "$REPO_URL" /opt/automations
|
||||
cd /opt/automations
|
||||
|
||||
# Optional: install shared admin SSH keys from globals/ for root before deploy
|
||||
# . scripts/lib.sh && load_globals \
|
||||
# && install -d -m700 /root/.ssh && resolve_ssh_keys >> /root/.ssh/authorized_keys
|
||||
|
||||
# Optional: harden SSH first (prints a fresh root key — capture it!)
|
||||
# SKIP_PROMPTS=1 FORCE=1 bash scripts/harden-ssh.sh
|
||||
|
||||
BESZEL_DOMAIN=monitoring.example.com \
|
||||
ACME_EMAIL=admin@example.com \
|
||||
SKIP_PROMPTS=1 \
|
||||
bash deployments/beszel/deploy.sh
|
||||
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# deploy.sh -- deploy the beszel stack (caddy + beszel hub) on Alpine.
|
||||
#
|
||||
# Single-node: runs as root.
|
||||
#
|
||||
# What this does:
|
||||
# 1. Installs docker + docker-cli-compose if missing.
|
||||
# 2. Lays down docker-compose.yml, Caddyfile, .env.example in $STACK_DIR.
|
||||
# 3. Generates .env on first run; existing .env is never overwritten.
|
||||
# 4. Prompts for required values not preset (BESZEL_DOMAIN, ACME_EMAIL).
|
||||
# 5. Opens TCP 80/443 in UFW if active.
|
||||
# 6. Pulls images, brings the stack up, waits for healthchecks.
|
||||
#
|
||||
# Idempotent: re-run to apply config changes / pull new images.
|
||||
#
|
||||
# Self-contained: docker-compose.yml, Caddyfile, .env.example are embedded
|
||||
# as a base64-encoded tar.gz at the bottom of this file. Rebuild with
|
||||
# build.sh after editing the loose source files.
|
||||
#
|
||||
# Usage:
|
||||
# bash deploy.sh # interactive prompts
|
||||
# BESZEL_DOMAIN=monitoring.example.com \
|
||||
# ACME_EMAIL=admin@example.com FORCE=1 bash deploy.sh
|
||||
# STACK_DIR=/opt/beszel bash deploy.sh
|
||||
# SKIP_DOCKER_INSTALL=1 bash deploy.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${STACK_DIR:=/srv/beszel}"
|
||||
: "${SKIP_DOCKER_INSTALL:=0}"
|
||||
: "${FORCE:=0}"
|
||||
: "${SKIP_PROMPTS:=0}" # non-interactive: require values via env, no prompts
|
||||
[[ "$SKIP_PROMPTS" == "1" ]] && FORCE=1
|
||||
: "${BESZEL_DOMAIN:=}"
|
||||
: "${ACME_EMAIL:=}"
|
||||
|
||||
log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; }
|
||||
warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; }
|
||||
die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "Run as root."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# OS detection + Docker install (Alpine / Debian / Alma). This deploy.sh is
|
||||
# self-contained (scp'd standalone), so the OS logic is inlined here instead
|
||||
# of sourced from scripts/oslib.sh.
|
||||
# ---------------------------------------------------------------------------
|
||||
osfam() {
|
||||
local id="" like=""
|
||||
if [[ -r /etc/os-release ]]; then
|
||||
id="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")"
|
||||
like="$(. /etc/os-release 2>/dev/null && echo "${ID_LIKE:-}")"
|
||||
fi
|
||||
case " $id $like " in
|
||||
*" alpine "*) echo alpine ;;
|
||||
*" debian "*|*" ubuntu "*) echo debian ;;
|
||||
*" rhel "*|*" fedora "*|*" centos "*) echo rhel ;;
|
||||
*) echo "${id:-unknown}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
install_docker() {
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
log "Docker already installed: $(docker --version)"
|
||||
else
|
||||
log "Installing Docker (OS: $(osfam))..."
|
||||
case "$(osfam)" in
|
||||
alpine) apk add -q docker docker-cli-compose openrc ;;
|
||||
debian|rhel) command -v curl >/dev/null 2>&1 || \
|
||||
{ command -v apt-get >/dev/null 2>&1 && apt-get install -y -qq curl; } || \
|
||||
{ command -v dnf >/dev/null 2>&1 && dnf install -y -q curl; }
|
||||
curl -fsSL https://get.docker.com | sh ;;
|
||||
*) die "Unsupported OS for auto Docker install. Set SKIP_DOCKER_INSTALL=1 and install Docker yourself." ;;
|
||||
esac
|
||||
fi
|
||||
if command -v rc-update >/dev/null 2>&1; then
|
||||
rc-update add docker default >/dev/null 2>&1 || true
|
||||
rc-service docker status >/dev/null 2>&1 || rc-service docker start
|
||||
elif command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl enable --now docker >/dev/null 2>&1 || systemctl start docker || true
|
||||
fi
|
||||
}
|
||||
|
||||
open_web_ports() {
|
||||
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
|
||||
log "ufw active -- allowing 80,443/tcp..."
|
||||
ufw allow 80/tcp >/dev/null; ufw allow 443/tcp >/dev/null
|
||||
elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
|
||||
log "firewalld active -- allowing http,https..."
|
||||
firewall-cmd -q --add-service=http --permanent
|
||||
firewall-cmd -q --add-service=https --permanent
|
||||
firewall-cmd -q --reload
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Extract embedded archive
|
||||
# ----------------------------------------------------------------------------
|
||||
SCRIPT_DIR=$(mktemp -d -t beszel-deploy.XXXXXX)
|
||||
trap 'rm -rf "$SCRIPT_DIR"' EXIT
|
||||
|
||||
extract_archive() {
|
||||
grep -a -A 9999999 '^__ARCHIVE_BELOW__$' "$0" \
|
||||
| tail -n +2 \
|
||||
| base64 -d \
|
||||
| tar -xz -C "$SCRIPT_DIR"
|
||||
}
|
||||
|
||||
if grep -q -a '^__ARCHIVE_BELOW__$' "$0"; then
|
||||
log "Extracting embedded deployment files..."
|
||||
extract_archive
|
||||
else
|
||||
die "No embedded archive found. Run build.sh to embed deployment files."
|
||||
fi
|
||||
|
||||
for f in docker-compose.yml Caddyfile .env.example; do
|
||||
[[ -f "$SCRIPT_DIR/$f" ]] || die "Embedded archive missing $f"
|
||||
done
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Prompt for required vars
|
||||
# ----------------------------------------------------------------------------
|
||||
prompt() {
|
||||
local varname="$1" message="$2"
|
||||
local -n ref="$varname"
|
||||
if [[ -z "${ref:-}" ]]; then
|
||||
[[ "$SKIP_PROMPTS" == "1" ]] && die "$varname required (set it in the environment; running with SKIP_PROMPTS=1)."
|
||||
read -r -p "$message: " ref
|
||||
[[ -n "$ref" ]] || die "$varname required."
|
||||
fi
|
||||
}
|
||||
|
||||
prompt BESZEL_DOMAIN "Public hostname for beszel (e.g. monitoring.example.com)"
|
||||
prompt ACME_EMAIL "Let's Encrypt email"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Docker
|
||||
# ----------------------------------------------------------------------------
|
||||
if [[ "$SKIP_DOCKER_INSTALL" != "1" ]]; then
|
||||
install_docker
|
||||
fi
|
||||
|
||||
# Open 80/443 on whichever host firewall is active.
|
||||
open_web_ports
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Stack directory + files
|
||||
# ----------------------------------------------------------------------------
|
||||
log "Setting up $STACK_DIR..."
|
||||
install -d -m 0750 "$STACK_DIR"
|
||||
install -m 0640 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml"
|
||||
install -m 0640 "$SCRIPT_DIR/Caddyfile" "$STACK_DIR/Caddyfile"
|
||||
|
||||
ENV_FILE="$STACK_DIR/.env"
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
log "Seeding $ENV_FILE..."
|
||||
install -m 0600 "$SCRIPT_DIR/.env.example" "$ENV_FILE"
|
||||
sed -i \
|
||||
-e "s|^BESZEL_DOMAIN=.*|BESZEL_DOMAIN=${BESZEL_DOMAIN}|" \
|
||||
-e "s|^ACME_EMAIL=.*|ACME_EMAIL=${ACME_EMAIL}|" \
|
||||
"$ENV_FILE"
|
||||
else
|
||||
log ".env exists; leaving it alone."
|
||||
fi
|
||||
|
||||
# Validate
|
||||
missing=()
|
||||
for var in BESZEL_DOMAIN ACME_EMAIL; do
|
||||
grep -E "^${var}=.+$" "$ENV_FILE" >/dev/null || missing+=("$var")
|
||||
done
|
||||
(( ${#missing[@]} == 0 )) || die "Missing values in $ENV_FILE: ${missing[*]}"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Bring up the stack
|
||||
# ----------------------------------------------------------------------------
|
||||
if [[ "$FORCE" != "1" ]]; then
|
||||
cat <<EOF
|
||||
|
||||
About to pull images and start the stack from $STACK_DIR.
|
||||
|
||||
Caddy will request a Let's Encrypt cert for ${BESZEL_DOMAIN}. DNS for
|
||||
that name must already point at this host, and ports 80/443 must be
|
||||
reachable from the internet.
|
||||
|
||||
Continue? [y/N]
|
||||
EOF
|
||||
read -r ans
|
||||
[[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]] || { warn "Aborted."; exit 0; }
|
||||
fi
|
||||
|
||||
cd "$STACK_DIR"
|
||||
log "Pulling images..."
|
||||
docker compose pull
|
||||
|
||||
log "Starting stack..."
|
||||
docker compose up -d --remove-orphans
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Wait for health
|
||||
# ----------------------------------------------------------------------------
|
||||
log "Waiting for services to become healthy (up to 120s)..."
|
||||
deadline=$(( $(date +%s) + 120 ))
|
||||
while (( $(date +%s) < deadline )); do
|
||||
status=$(docker compose ps --format '{{.Service}} {{.Health}}' 2>/dev/null || true)
|
||||
unhealthy=$(echo "$status" | awk '$2 != "healthy" && $2 != "" {print $1}')
|
||||
if [[ -z "$unhealthy" && -n "$status" ]]; then
|
||||
log "All services healthy."
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo
|
||||
log "Stack status:"
|
||||
docker compose ps
|
||||
echo
|
||||
cat <<EOF
|
||||
================================================================
|
||||
DEPLOYED
|
||||
|
||||
URL: https://${BESZEL_DOMAIN}
|
||||
Stack dir: ${STACK_DIR}
|
||||
|
||||
First-time setup:
|
||||
|
||||
1. Open https://${BESZEL_DOMAIN} -- you'll be asked to create the first
|
||||
admin account. Do this immediately so no one else can.
|
||||
|
||||
2. (Optional) Configure OIDC against pocket-id:
|
||||
- Open https://${BESZEL_DOMAIN}/_/#/settings
|
||||
- Toggle off "Hide collection create and edit controls"
|
||||
- Edit the "users" collection -> Options tab -> enable OAuth2
|
||||
- Add OIDC provider, redirect URI:
|
||||
https://${BESZEL_DOMAIN}/api/oauth2-redirect
|
||||
- Toggle controls back on
|
||||
Then in pocket-id admin UI, register a new OIDC client with that
|
||||
redirect URI and paste the credentials into Beszel.
|
||||
|
||||
3. (Optional) Once OIDC works, lock out password login: edit
|
||||
docker-compose.yml, uncomment DISABLE_PASSWORD_AUTH=true, then
|
||||
docker compose up -d.
|
||||
|
||||
Add a monitored host:
|
||||
Beszel UI -> "Add System" -> follow the agent install snippet.
|
||||
The agent connects back to this hub at https://${BESZEL_DOMAIN}.
|
||||
|
||||
Manage:
|
||||
docker compose logs -f
|
||||
docker compose pull && docker compose up -d # update
|
||||
docker compose down # stop, keep volumes
|
||||
docker compose down -v # stop, WIPE data
|
||||
|
||||
Or just re-run this script -- it's idempotent.
|
||||
================================================================
|
||||
EOF
|
||||
|
||||
# IMPORTANT: do not put any code below this exit. Everything after the
|
||||
# __ARCHIVE_BELOW__ marker is the embedded tar.gz payload (base64).
|
||||
exit 0
|
||||
__ARCHIVE_BELOW__
|
||||
H4sIAAAAAAAAA+1Y/27jxhH233qKAX1AfOiRIm3LTli4qCwJjQBfrFg6XNIiEFbkSlqY5DK7S8s6
|
||||
w0Afok/YJ8nMkhIp99CgwOVyKThnHMX9MTsz++03s4xldM+VG8k0l5p72zQ5+uTio1ycnx/5F35w
|
||||
2Qvs0/cD245yduFfHAW94PIiuPTPe/6RH5xh8xH4n96U/5RCG6YAjnqX8/cyeeCfY80vSI5hwfUH
|
||||
ngCGIboH14WIxfEWTmY309fwp13vuljAiebqgStIZSaMVCJbvfY6x51jmMlcJnK1DfE3wLWSGxyJ
|
||||
c9mKZ0aD+5dSZ3h+fkYvpcrwa/8b307/TkI/KxZCh7sZeaHXkHKjRKRB0prfzmaT7nu+mBJcDbAs
|
||||
BgYT+R5WzHDYyCKJUdNCcXYPZs1TD67tKl9p6E/GoAu1ZBEHoWGzZma3ToGItybcjocD6oxkthSr
|
||||
QvEY8DgYN+Z5IreoUclitSbNlV5gcSoyeDeGk4k16Zppjoo0NwYDo1+/gUwaeBAMePYAD0yhI6rS
|
||||
D0uRcA+mnFuN5SIeuuwMR5Ob2x9HQ4ecSVAxLHEWDeKPLDK4STzXXqeTsZSHVSA7nQeZFCnXYQeq
|
||||
JjdmhtGrjfuLt9KGsNOh7RRROa/cIPwBIFKMTli1vHoa9IfDH+ez/t9C99RlSS4y/mzHoR7D8E3N
|
||||
S2vsBNujOJ0pE0KRJVxrVxuZ5zy2fblURpcLAbjgfO0jEJz6HTFCONm1HDfaukWcO7bNouGMwBoL
|
||||
zRYJbtfJwML2+3fjASJPcQSN5vNcycdtpepjcgx5grhYyyRGkC0KRDTccZdnpBShwjPcq0cee1ZH
|
||||
I86ltV7XrkrbGXa5ibo2Bo1GJfdjG1vRpf9fdFS70i2fthOBI5TMUoTqbsnr0fTvo5v58PZtf/xd
|
||||
CM6rp4OW513Y+oO3o/kIm27soPq1GoGQ41ms5zKrnanQRC9rzhKzjtY8ut/1G9zTEP7hDN4OnTfg
|
||||
bFbc0NP9+dal59qYPOx2g9NLz8d/QXjqB99UznSdnyolIjOIOZaEcObrnWKRclmg7t6uRdHBxzDD
|
||||
WdVg0TTPuRIyDiHAqXukH0AWt0ttV3G36toHx4I3YeTCx6HbcP2/YReTZIrME4IlQoQfeQ2Ob132
|
||||
LaE5HwdK81hW5s33IPjIPh/DsAQ2UZQiI7s503ojFfJeYdYEfWQGZDRiLg8GaBlOBwwkGm5Estez
|
||||
lcVXaGuD2FhW0h2ejQdBsEceI4bZMdobkPiqNkJzmr3XZOkIG/BcJUu7kqTTobQBVWRebfl42r++
|
||||
Gc0n/en0/e3dcN5/N/sWYWhUwetj3U8SuSkNMZJ8km6E7G1Kj4FFkSyIovdrYIIR+1XeTUd388Hd
|
||||
qD8b35bn4LDFpcUqrH8CMNPOdlkuuqWuT4tnmvr75f89V/2Ga/xK/Recn5/W9d8FtgdB0Gvrv88i
|
||||
x7BHgK01mtVgWRv16WwKrQuusea64QZrqlEWqW1uIOLK2GmvDvKQrc+qHOxSDkb4oyY86EQ0jZIS
|
||||
T7c9XB70y4qMJtKYDV9QcbVAKoKVbBRggsqfp5I3UyYSeHrVyG6dZ+w8tOUZqtFZJGMOH7SJYfVB
|
||||
5J2K7huFQrMyrWZVBII0OS9y+MG9QwJwxxN4Io7wFP8Zo2LwmUrDvbXcJZiS4+p6tYv0la3cRDwQ
|
||||
AZOrZY2rDXJeqr39pGWCzfMdr4Ab7HuMYpmm6gls1qnNK91g8bxiHsAMmR70bpQwfN/d7C2tfe7s
|
||||
iJLSQa15SgW4cWe7ld0pjwrUtQUnZY8u+nF1FvTw+ub7f0YujJIi5tNiMZS4MZl29np+cAeYcNFp
|
||||
d7bNuXubGyEzTT1OJnUmlst67B1fcqXwRjqRiYi2DS8cXZqDNw/MBC4VZ5gyJObosqXWgXbSRaXp
|
||||
G2aPhmMYhrygajrGX3XspUrxboCpUsuEV7Off0du/hziYfXh4eUizX+7FPBr/N+7PKv5/9Lyf+/y
|
||||
tOX/zyHI/zLfEjUTEiz/Yi5I8Dh7ENtvQ1B9G4JcRPe6HIZcSCUbHhcRsSTZIicfw7//9c/yDybF
|
||||
Ag8vEB9S7Vr3fOF/6MQ1U7w2/CSToLF0TPlrug2qffLaUIwWnIgXu/F+eJBzrupPJLuz5WEUKUgj
|
||||
m7QoZR5mUsVXAgmOETViuuCPuVBb+oYglhhiS5hep850V7Za/+sL5Y2iWotV5lJl/z8U1zDFVGUk
|
||||
KnKWLNHcAbGkih82LKMOwNtPQduNdwfe1Khf3iEwNOgifRBh2VZmeP/AWwdxMJXwB6X6FVXqh+gZ
|
||||
020ODFvpPwxwXuKovnhelffOzv4zytXuK8r/eWJppZVWWmmllVZaaaWVVlpppZVWWmnli5BfAO7B
|
||||
8Y8AKAAA
|
||||
@@ -0,0 +1,65 @@
|
||||
# beszel stack -- caddy (TLS) + beszel hub (server monitoring).
|
||||
#
|
||||
# Topology:
|
||||
# Browser + agents -> caddy:443 -> beszel:8090
|
||||
#
|
||||
# No Anubis: agents push metrics over HTTP/WebSocket and a PoW gate would
|
||||
# break them. Beszel's API surface is what agents use.
|
||||
#
|
||||
# OIDC is configured post-deploy through the Beszel admin UI (PocketBase
|
||||
# settings), not via env vars or config file. See the deploy.sh "DEPLOYED"
|
||||
# block for the exact steps.
|
||||
|
||||
name: beszel
|
||||
|
||||
volumes:
|
||||
beszel-data:
|
||||
caddy-data:
|
||||
caddy-config:
|
||||
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:${CADDY_TAG:-2-alpine}
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
# - "443:443/udp" # HTTP/3 -- disabled (Caddy QUIC + reverse_proxy
|
||||
# placeholder bug). Re-enable when fixed.
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
environment:
|
||||
BESZEL_DOMAIN: "${BESZEL_DOMAIN}"
|
||||
ACME_EMAIL: "${ACME_EMAIL}"
|
||||
depends_on:
|
||||
- beszel
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:2019/config/"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
beszel:
|
||||
image: henrygd/beszel:${BESZEL_TAG:-latest}
|
||||
container_name: beszel
|
||||
restart: unless-stopped
|
||||
command: serve --http "0.0.0.0:8090"
|
||||
volumes:
|
||||
- beszel-data:/beszel_data
|
||||
environment:
|
||||
# Disable username/password auth -- force OIDC. Comment out until
|
||||
# you've configured an OIDC provider in the admin UI, otherwise you
|
||||
# lock yourself out on first run.
|
||||
# DISABLE_PASSWORD_AUTH: "true"
|
||||
# Allow OIDC to auto-create user accounts on first login.
|
||||
USER_CREATION: "${USER_CREATION:-true}"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8090/api/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
Reference in new issue
Block a user