Files
tech f5d4852ad8 fix: hex password generation, safe DATABASE_URL, consistent /opt/argus default
Root cause of a real production failure on the first live install
(wap-proxy, 2026-07-25): DB_PASSWORD was generated with
`openssl rand -base64 24`, which can produce '/', '+', or '=' --
docker-compose.yml then naively interpolated the raw password into
postgres://postgres:${DB_PASSWORD}@db:5432/..., and a generated
password containing '/' broke the connection string outright. The API
never became healthy; log ingestion failed completely.

- install.sh now generates with `openssl rand -hex 32` (always
  [0-9a-f], can't produce this class of character). Same fix applied
  everywhere else openssl-rand-base64-24 was referenced.
- docker-compose.yml no longer builds DATABASE_URL by string
  interpolation -- DB_PASSWORD is passed as its own var and the API
  assembles the connection string safely internally using
  net/url.UserPassword (proper percent-encoding), a second,
  independent layer so the installer doesn't rely on the password
  generator alone. See the matching argus-appliance commit for the
  Go-side change and its regression test.
- backup.sh/healthcheck.sh/restore.sh/uninstall.sh still defaulted
  ARGUS_INSTALL_DIR to $HOME/argus, inconsistent with install.sh/
  update.sh's own /opt/argus default (changed in an earlier commit
  this session) -- confirmed live on the same install: healthcheck.sh
  and uninstall.sh reported "no installation found" when run from the
  real, correct directory. All five scripts now agree on /opt/argus.

Corresponding argus-appliance fix (config.go's buildDatabaseURL, v3.73.1)
already built and pushed to git-cloud.weboria.eu/weboria/argus-api.
2026-07-25 17:43:08 +00:00

178 lines
8.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# ARGUS Enterprise Web Security Platform — installer. Fetches the production
# compose stack, generates secrets, starts the platform, and prints your
# admin credentials.
#
# curl -fsSL https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/install.sh | bash
#
set -euo pipefail
REPO_RAW_BASE="https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main"
INSTALL_DIR="${ARGUS_INSTALL_DIR:-/opt/argus}"
NETWORK_NAME="argus-network"
# Production images are pulled from Weboria's own registry, built into
# git-cloud.weboria.eu (Gitea's container registry) — docker-compose.yml's
# image: lines reference git-cloud.weboria.eu/weboria/argus-{api,ui,proxy}
# directly. Anonymous docker pull works with no login required.
# ── Output helpers ────────────────────────────────────────────────────────────
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
info() { printf ' %s\n' "$1"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
warn() { printf ' \033[33m!\033[0m %s\n' "$1"; }
fail() { printf ' \033[31m✗\033[0m %s\n' "$1" >&2; exit 1; }
bold "ARGUS Installer"
echo
# ── 1. OS detection (informational — Linux is the supported target) ──────────
OS="$(uname -s)"
case "$OS" in
Linux) ok "Detected Linux" ;;
Darwin) warn "Detected macOS — supported for local evaluation via Docker Desktop, not for production deployment." ;;
*) warn "Unrecognized OS ($OS) — proceeding, but only Linux is officially supported." ;;
esac
# ── 2. Docker / Compose availability ──────────────────────────────────────────
command -v docker >/dev/null 2>&1 || fail "Docker is not installed. Install Docker first: https://docs.docker.com/engine/install/"
docker info >/dev/null 2>&1 || fail "Docker is installed but not running (or this user lacks permission). Start Docker / add your user to the docker group, then re-run."
docker compose version >/dev/null 2>&1 || fail "Docker Compose v2 (the 'docker compose' plugin) is required. See https://docs.docker.com/compose/install/"
ok "Docker and Docker Compose are available"
# ── 3. Fetch deployment files ──────────────────────────────────────────────────
mkdir -p "$INSTALL_DIR/docker-socket-proxy"
cd "$INSTALL_DIR"
fetch() {
curl -fsSL "$REPO_RAW_BASE/$1" -o "$2" || fail "Failed to download $1"
}
fetch "docker-compose.yml" "docker-compose.yml"
fetch "docker-socket-proxy/haproxy.cfg.template" "docker-socket-proxy/haproxy.cfg.template"
for script in update.sh uninstall.sh healthcheck.sh backup.sh restore.sh; do
fetch "$script" "$script"
chmod +x "$script"
done
# The repo stores this as env.example (no leading dot) rather than .env.example
# -- Weboria's own WAF blocks any request path matching .env* as a standard
# credential-harvesting-probe rule, which also caught this legitimate static
# file when served from Gitea. Renaming sidesteps it without needing a WAF
# exception. Saved locally as .env either way.
if [ ! -f .env ]; then
fetch "env.example" ".env"
ok "Fetched deployment files into $INSTALL_DIR"
else
ok "Fetched compose file (kept your existing .env)"
fi
# ── 4. Generate secrets (first install only — never overwrite an existing .env) ──
# hex, not base64: base64 can produce '/', '+', '=', any of which breaks the
# raw postgres://user:PASSWORD@host connection string if it ever ends up
# interpolated unescaped (confirmed live on the first real production
# install — see config.go's buildDatabaseURL for the second, independent
# defensive layer this pairs with). Hex output is always [0-9a-f], which
# can never produce that class of bug, at 4 bits/char vs base64's ~6 —
# 32 hex chars (128 bits) is used in place of 24 base64 chars (~144 bits)
# to keep entropy comparable rather than matching digit-count cosmetically.
if grep -q '^DB_PASSWORD=change-me-in-production' .env 2>/dev/null; then
DB_PASSWORD="$(openssl rand -hex 32)"
sed -i.bak "s#^DB_PASSWORD=.*#DB_PASSWORD=${DB_PASSWORD}#" .env && rm -f .env.bak
ok "Generated a secure database password"
fi
# ── 5. Docker network ─────────────────────────────────────────────────────────
if docker network inspect "$NETWORK_NAME" >/dev/null 2>&1; then
ok "Docker network '$NETWORK_NAME' already exists"
else
docker network create "$NETWORK_NAME" >/dev/null
ok "Created Docker network '$NETWORK_NAME'"
fi
# ── 6. Pull and start ──────────────────────────────────────────────────────────
bold "Pulling images and starting ARGUS..."
docker compose pull
docker compose up -d
echo
# ── 7. Wait for the API to become healthy ─────────────────────────────────────
API_HOST_PORT="$(grep -oP '^API_HOST_PORT=\K.*' .env 2>/dev/null || true)"
API_HOST_PORT="${API_HOST_PORT:-9080}"
bold "Waiting for ARGUS to finish starting (this can take a few minutes on first boot)..."
READY=false
for _ in $(seq 1 90); do
if curl -fsS "http://127.0.0.1:${API_HOST_PORT}/health" >/dev/null 2>&1; then
READY=true
break
fi
sleep 5
done
if [ "$READY" != "true" ]; then
warn "ARGUS did not report healthy within 7.5 minutes."
warn "Check container status with: docker compose -f $INSTALL_DIR/docker-compose.yml ps"
warn "and logs with: docker compose -f $INSTALL_DIR/docker-compose.yml logs api"
exit 1
fi
ok "ARGUS is up"
# ── 8. Rotate the default admin credentials ───────────────────────────────────
ADMIN_USER="admin"
ADMIN_PASSWORD="Ax9!$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 20)"
API_BASE="http://127.0.0.1:${API_HOST_PORT}/api/v1"
LOGIN_RESP="$(curl -fsS -X POST "${API_BASE}/auth/login" \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' 2>/dev/null || true)"
TOKEN="$(printf '%s' "${LOGIN_RESP}" | grep -oP '"token"\s*:\s*"\K[^"]+' || true)"
CREDS_ROTATED=false
if [ -n "$TOKEN" ]; then
CHANGE_RESP="$(curl -fsS -o /dev/null -w '%{http_code}' -X POST "${API_BASE}/auth/change-credentials" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${TOKEN}" \
-d "{\"current_password\":\"admin\",\"new_username\":\"${ADMIN_USER}\",\"new_password\":\"${ADMIN_PASSWORD}\",\"new_password_confirm\":\"${ADMIN_PASSWORD}\"}" 2>/dev/null || echo "000")"
if [ "$CHANGE_RESP" = "200" ]; then
CREDS_ROTATED=true
ok "Rotated the default admin password"
fi
fi
if [ "$CREDS_ROTATED" != "true" ]; then
warn "Could not auto-rotate admin credentials (already changed on a prior run?). Log in with your existing credentials, or admin/admin on a genuinely fresh install."
fi
# ── 9. Summary ─────────────────────────────────────────────────────────────────
UI_PORT="$(grep -oP '^UI_PORT=\K.*' .env 2>/dev/null || true)"
UI_PORT="${UI_PORT:-81}"
echo
bold "ARGUS Enterprise Web Security Platform has been successfully installed."
echo
info "Access URL: https://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):${UI_PORT} (accept the self-signed certificate)"
if [ "$CREDS_ROTATED" = "true" ]; then
info "Username: ${ADMIN_USER}"
info "Password: ${ADMIN_PASSWORD}"
warn "Save this password now — it is not stored anywhere and cannot be recovered."
else
info "Log in with your existing admin credentials."
fi
echo
bold "Next steps:"
info "1. Open the administration panel at the Access URL above."
if [ "$CREDS_ROTATED" != "true" ]; then
info "2. Complete the first-run setup wizard (regional settings, guided protection checklist)."
info "3. Change the initial administrator password if you haven't already."
else
info "2. Complete the first-run setup wizard — regional settings and a guided review of every"
info " protection module (your admin password was already rotated above)."
fi
info "4. Configure domains and reverse proxy hosts under Proxy Hosts."
info "5. Review the ANIS threat intelligence connection under Threat Intel — enabled by"
info " default against the public Community Edition hub, no license key required."
info "6. Review security policies in the Security Policy Center."
echo
info "Configuration: ${INSTALL_DIR}/.env and ${INSTALL_DIR}/docker-compose.yml"
info "Upgrade: cd ${INSTALL_DIR} && docker compose pull && docker compose up -d"
info "Backup: cd ${INSTALL_DIR} && ./backup.sh"
info "Docs/support: see README.md in this directory"
echo