feat(distribution): production install/update/backup scripts + public-repo sync (#106)

Builds the customer-facing distribution: a curated distribution/ directory
containing only what an end customer needs (production docker-compose.yml
with no build: sections, install.sh/update.sh/uninstall.sh/healthcheck.sh/
backup.sh/restore.sh, a customer-facing CHANGELOG.md, and README/LICENSE/
NOTICE/THIRD_PARTY_LICENSES) — pushed as the initial content of the new
public alleyviper/argus repo.

install.sh: detects OS, validates Docker/Compose, fetches all deployment
files, generates a secure DB password, creates the required Docker
network, pulls images, starts the stack, waits for health, and rotates
the default admin/admin credentials via the auth API — printing the
generated password once at the end.

update.sh/healthcheck.sh/backup.sh/restore.sh/uninstall.sh formalize what
was previously ad-hoc README snippets into real, safe-by-default scripts
(uninstall.sh keeps data unless --remove-data is explicitly passed and
confirmed; restore.sh requires typed confirmation and documents that it
targets a fresh install, not a live merge).

Added a sync-distribution job to release.yml: after a successful release,
mirrors distribution/ into the public repo and creates a matching
(customer-facing, commit-log-free) release marker there. Needs a one-time
setup step — a fine-grained PAT scoped to alleyviper/argus added as the
ARGUS_PUBLIC_REPO_TOKEN secret — the job cleanly no-ops until that's added.

Not yet done: GHCR package visibility (argus-secure-api/-ui/-nginx) is
still private, which blocks a genuine end-to-end curl-install test from a
clean, unauthenticated environment — deferred at the user's request until
they flip it manually (GitHub does not expose this via API).
This commit is contained in:
alleyviper
2026-07-19 18:06:02 +01:00
committed by GitHub
commit 44f662afc0
14 changed files with 1213 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
# ARGUS — Environment Configuration
# Copy this file to .env and modify as needed. install.sh does this for you
# automatically, including generating a secure DB_PASSWORD.
# ===========================================
# Required Settings
# ===========================================
# Database password — install.sh generates this for you.
# Manual value: openssl rand -base64 24
DB_PASSWORD=change-me-in-production
# ===========================================
# Optional Settings
# ===========================================
TZ=UTC
DB_USER=postgres
DB_NAME=argus
# ===========================================
# ANIS — Threat Intelligence Integration
# ===========================================
# ANIS is a separate product, deployed independently (self-hosted or via a
# community instance). These vars configure how THIS ARGUS instance talks to
# an already-running ANIS service — they do not start one.
#
# Set ANIS_ENABLED=true and ANIS_URL to turn on reporting WAF auto-bans to
# ANIS and pulling its community threat-intelligence feed.
ANIS_ENABLED=false
# ANIS_URL=https://anis.example.com:8090
ANIS_LICENSE_KEY=
# Must match the ANIS instance's own ANIS_ADMIN_KEY. Generate: openssl rand -hex 32
ANIS_ADMIN_KEY=
ANIS_SHARE_ATTACKERS=false
# ===========================================
# Docker Compatibility (Synology DSM, etc.)
# ===========================================
# Leave empty for auto-detection. Only set if auto-detection fails.
# Synology DSM typically needs: 1.41 or 1.43
# DOCKER_API_VERSION=1.41
# ===========================================
# Network & Port Settings
# ===========================================
# Admin panel port (default: 81)
# UI_PORT=81
# Nginx listen ports — change if 80/443 are already in use
# (e.g. Synology Web Station)
# NGINX_HTTP_PORT=8080
# NGINX_HTTPS_PORT=8443
# API host port — exposed for nginx-to-API communication.
# MUST NOT conflict with NGINX_HTTP_PORT.
# API_HOST_PORT=9080
# DNS Security Engine listen address — host-only (127.0.0.1) by default.
# Set to an internal interface IP to route real client DNS traffic through
# it. Never 0.0.0.0 unless this host is on a fully trusted network.
# DNS_LISTEN_HOST=127.0.0.1
+31
View File
@@ -0,0 +1,31 @@
# Changelog
All notable changes to ARGUS are documented here, in plain language for operators.
Versioning follows [Semantic Versioning](https://semver.org/).
## [3.29.0] — 2026-07-19
### Security
- Hardened the Challenge Engine's internal signing so a rare startup condition can no
longer weaken bot-challenge verification.
- Updated bundled build tooling to close several third-party dependency advisories.
## [3.28.0] — 2026-07-19
### Added
- DNS Protection settings now include contextual help explaining what each detector and
threshold does, so you can tune it with confidence instead of guessing.
## [3.27.0] — 2026-07-19
### Added
- Complete user-management workflow: edit user details, admin-initiated password resets,
MFA reset, account lock/unlock, session management, and login history — all from one
panel per user.
- Redesigned Attack Origin visualization: a single live global map (previously two separate,
inconsistent views) showing real-time attack traffic with per-event detail on demand.
## [3.26.0] and earlier
Earlier release history predates this distribution's changelog. See the GitHub Releases
page for this repository going forward — every release from here on is documented here.
+17
View File
@@ -0,0 +1,17 @@
ARGUS Secure — Proprietary Software License
Copyright (c) 2025-2026 AI4SEC / lmelo. All rights reserved.
** PLACEHOLDER — NOT A FINAL LICENSE **
This repository contains proprietary, commercial software. A complete commercial license
agreement has not yet been finalized and this file is a placeholder pending legal review.
Until a final license is adopted, no rights are granted to use, copy, modify, merge, publish,
distribute, sublicense, or sell copies of this software, in whole or in part, except as
separately agreed in writing with the copyright holder.
This software incorporates third-party open-source components under their own licenses;
see THIRD_PARTY_LICENSES and NOTICE for details. Nothing in this placeholder alters the terms
of those third-party components.
TODO before release: replace this file with final commercial/enterprise license text.
+18
View File
@@ -0,0 +1,18 @@
ARGUS Secure
Copyright (c) 2025-2026 AI4SEC / lmelo
This product includes software developed by third parties under their own open-source
licenses. Full license texts and attributions are listed in THIRD_PARTY_LICENSES.
Notable components:
- nginx (BSD-2-Clause) — Copyright (C) 2002-2024 Igor Sysoev, (C) 2011-2024 Nginx, Inc.
- ModSecurity (Apache-2.0) — Copyright (C) 2002-2024 Trustwave Holdings, Inc.
- OWASP Core Rule Set (Apache-2.0) — Copyright (C) 2006-2024 Trustwave Holdings, Inc.,
OWASP Core Rule Set contributors
- TimescaleDB Community Edition (Timescale License) — see THIRD_PARTY_LICENSES for
redistribution terms
- MaxMind GeoLite2 (GeoLite2 EULA) — "This product includes GeoLite2 data created by
MaxMind, available from https://www.maxmind.com"
- Valkey (BSD-3-Clause)
This repository's own source code is proprietary; see LICENSE.
+221
View File
@@ -0,0 +1,221 @@
<div align="center">
# ARGUS
### Enterprise Web Security Platform
[![License](https://img.shields.io/badge/License-Proprietary-lightgrey?style=for-the-badge)](LICENSE)
[![Docker](https://img.shields.io/badge/Docker-ready-2496ED?style=for-the-badge&logo=docker&logoColor=white)](https://github.com/alleyviper/argus/pkgs/container/argus-secure-api)
[![Nginx](https://img.shields.io/badge/Nginx-1.30-009639?style=for-the-badge&logo=nginx&logoColor=white)](https://nginx.org/)
[![ModSecurity](https://img.shields.io/badge/ModSecurity-v3-red?style=for-the-badge)](https://modsecurity.org/)
[![OWASP CRS](https://img.shields.io/badge/OWASP_CRS-v4.26-orange?style=for-the-badge)](https://coreruleset.org/)
[![HTTP/3](https://img.shields.io/badge/HTTP/3-QUIC-blue?style=for-the-badge)]()
<p align="center">
<strong>A real-time security operations platform for the modern web edge.<br/>
Reverse proxy, Web Application Firewall, bot defense, DNS protection, and live threat
investigation — in one appliance.</strong>
</p>
<p align="center">
<a href="#-key-features">✨ Features</a> •
<a href="#-installation">🚀 Installation</a> •
<a href="#-operations">🛠 Operations</a> •
<a href="#-api">📚 API</a>
</p>
---
</div>
## ✨ Key Features
### 🛡️ Web Application Firewall
ModSecurity v3 with the OWASP Core Rule Set. Paranoia levels 14, per-host rule exceptions,
detection-only or blocking modes.
### 🌐 Attack Origin Visualization
A live global map of inbound attacks — real-time arcs from source to your infrastructure,
severity-coded, with instant drill-down into any event's full detail.
### 🔎 Incident Investigation
Pivot from any IP, log line, or dashboard event straight into that entity's complete history —
affected hosts, triggered rules, geography, and live threat-intelligence reputation — without
losing your place.
### 🤖 Bot & AI-Crawler Defense
Blocks known-bad bots and AI scrapers automatically, with a search-engine allowlist so legitimate
crawlers are never affected. Escalating challenge modes (browser check, proof-of-work, visual
CAPTCHA) for suspicious traffic.
### 🧬 DNS Security Engine
An embedded forwarding DNS resolver with real-time threat detection — DGA domains, DNS
tunneling, fast-flux infrastructure, cache-poisoning attempts — and automatic mitigation via
sinkholing.
### 🌍 GeoIP & Cloud-Provider Access Control
Allow or block traffic by country or hosting provider, with live visual feedback.
### 🔒 Automated SSL
Let's Encrypt with automatic renewal, including wildcard certificates via DNS-01 challenge
(Cloudflare, DuckDNS, Dynu, and more).
### ⚡ Rate Limiting & Auto-Ban
Configurable per-IP/per-URI rate limits, automatic banning on repeated WAF violations, and
optional community threat-intelligence-driven blocking.
### 👥 Full User Management
Role-based access, per-user session control, forced password resets, account lock/unlock, MFA,
and a complete login history — everything a security team needs to administer access safely.
### 📊 Real-Time Operations Dashboard
Live traffic, security events, and system health, updated continuously — no manual refresh.
### 🔀 Load Balancing & Stream Proxying
Multiple backend servers with health checks, plus TCP/UDP stream proxying with optional SNI
routing.
### 🔮 Modern Protocol Support
HTTP/3 (QUIC) and post-quantum-ready TLS (ML-KEM hybrid key exchange).
---
## 🚀 Installation
### Requirements
- A Linux host with Docker 24.0+ and the Docker Compose plugin (`docker compose`)
- Ports 80/443 available for the reverse proxy, and 81 for the admin panel (all configurable)
### One-line install
```bash
curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/install.sh | bash
```
This downloads the deployment files, generates a secure database password, creates the
required Docker network, pulls the production images, starts ARGUS, and prints your admin
login at the end. Installs to `~/argus` by default — set `ARGUS_INSTALL_DIR` first to change
that.
### Manual install
If you'd rather see every step:
```bash
mkdir -p ~/argus/docker-socket-proxy && cd ~/argus
curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/docker-compose.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/.env.example -o .env
curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/docker-socket-proxy/haproxy.cfg.template -o docker-socket-proxy/haproxy.cfg.template
# Generate a secure database password
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$(openssl rand -base64 24)/" .env
# ARGUS expects this network to already exist
docker network create argus-network
docker compose pull
docker compose up -d
```
Then log in at `https://localhost:81` with `admin` / `admin` and change the password
immediately — the one-line installer does this rotation for you automatically.
**Do not expose the admin panel (port 81) to the internet.** Keep it on your LAN/VPN, or put
it behind its own proxy host with access lists and 2FA enabled.
---
## 🛠 Operations
Five scripts cover day-to-day operation — download them once alongside `docker-compose.yml`
(the one-line installer already does this for you), then run from `~/argus` (or wherever you
installed):
| Script | Purpose |
|--------|---------|
| `./update.sh` | Pull the latest images and restart, with a before/after version check |
| `./healthcheck.sh` | Report the status of every component and the admin panel |
| `./backup.sh [dir]` | Back up the database and application data into one archive |
| `./restore.sh <archive>` | Restore a backup onto a fresh installation |
| `./uninstall.sh [--remove-data]` | Stop ARGUS; data is kept unless you explicitly opt in to removing it |
### Upgrading
```bash
cd ~/argus
./update.sh
```
New versions are published to GHCR as soon as they're released — `update.sh` always pulls the
current stable release. This is the only supported upgrade path; there is no separate migration
step to run.
### Backups
```bash
cd ~/argus
./backup.sh # writes argus-backup-<timestamp>.tar.gz to the current directory
./restore.sh <archive> # onto a fresh installation only — see the script's own header
```
### Resetting the admin password
```bash
docker compose exec api ./server reset-password
```
Add `--username <name>` to target a specific account, or `--password '...'` to set a known
value instead of generating one. See `docker compose exec api ./server reset-password --help`
for the rest of the options (clearing 2FA, etc.).
### Threat intelligence integration (optional)
ARGUS can connect to **ANIS**, a separately-deployed threat-intelligence hub, to share confirmed
attacker IPs and receive a community blocklist in return. It's off by default — set
`ANIS_ENABLED=true` and `ANIS_URL` in `.env` to turn it on. See the comments in `.env.example`
for the full set of options.
---
## 📚 API
ARGUS exposes a full REST API for automation. Authenticate with a JWT (`POST
/api/v1/auth/login`) for interactive use, or an API token (`Authorization: Bearer ng_<token>`,
created from the admin panel) for CI/CD and scripted access. Interactive API documentation is
served at `https://localhost:81/api/docs` once ARGUS is running.
---
## ⚙️ Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `DB_PASSWORD` | Database password | *(generated by the installer)* |
| `TZ` | Timezone | `UTC` |
| `UI_PORT` | Admin panel port | `81` |
| `NGINX_HTTP_PORT` / `NGINX_HTTPS_PORT` | Reverse-proxy listen ports | `80` / `443` |
| `API_HOST_PORT` | Internal API port exposed to the host | `9080` |
| `DNS_LISTEN_HOST` | DNS Security Engine bind address | `127.0.0.1` |
| `ANIS_ENABLED` / `ANIS_URL` | Optional threat-intel hub integration | `false` / *(empty)* |
Full list with descriptions in `.env.example`.
---
## 📄 License
This is proprietary, commercial software — see [LICENSE](LICENSE). Third-party open-source
components retain their own licenses — see [THIRD_PARTY_LICENSES](THIRD_PARTY_LICENSES) and
[NOTICE](NOTICE).
## 💬 Support
[GitHub Issues](https://github.com/alleyviper/argus/issues) — bug reports and feature requests.
---
<div align="center">
<sub>© 2025-2026 ARGUS — AI4SEC.</sub>
</div>
+112
View File
@@ -0,0 +1,112 @@
# Third-Party Licenses
ARGUS Secure uses the following third-party software:
## Core Components
### TimescaleDB
- **License**: Timescale License (TSL)
- **Website**: https://github.com/timescale/timescaledb
- **License URL**: https://github.com/timescale/timescaledb/blob/main/tsl/LICENSE-TIMESCALE
- **Note**: TimescaleDB Community Edition is used for time-series data storage and compression.
The Timescale License carries redistribution conditions beyond plain open source — reviewed
before bundling with a commercial, proprietary product; see legal review note below.
### Nginx
- **License**: BSD-2-Clause
- **Website**: https://nginx.org/
- **Copyright**: (C) 2002-2024 Igor Sysoev, (C) 2011-2024 Nginx, Inc.
### ModSecurity
- **License**: Apache License 2.0
- **Website**: https://github.com/owasp-modsecurity/ModSecurity
- **Copyright**: (C) 2002-2024 Trustwave Holdings, Inc.
### OWASP Core Rule Set (CRS)
- **License**: Apache License 2.0
- **Website**: https://coreruleset.org/
- **Copyright**: (C) 2006-2024 Trustwave Holdings, Inc., OWASP Core Rule Set contributors
### MaxMind GeoIP2 / GeoLite2
- **License**: GeoLite2 End User License Agreement
- **Website**: https://www.maxmind.com/
- **Attribution**: This product includes GeoLite2 data created by MaxMind, available from
https://www.maxmind.com. Redistribution terms are stricter than plain open source — reviewed
before bundling; see legal review note below.
### Valkey
- **License**: BSD-3-Clause
- **Website**: https://valkey.io/
## Backend (Go) Dependencies
| Package | License | Website |
|---------|---------|---------|
| github.com/labstack/echo/v4 | MIT | https://echo.labstack.com/ |
| github.com/go-acme/lego/v4 | MIT | https://go-acme.github.io/lego/ |
| github.com/lib/pq | MIT | https://github.com/lib/pq |
| github.com/google/uuid | BSD-3-Clause | https://github.com/google/uuid |
| github.com/redis/go-redis/v9 | BSD-2-Clause | https://github.com/redis/go-redis |
| github.com/oschwald/geoip2-golang | ISC | https://github.com/oschwald/geoip2-golang |
| github.com/cloudflare/cloudflare-go | BSD-3-Clause | https://github.com/cloudflare/cloudflare-go |
| golang.org/x/crypto | BSD-3-Clause | https://golang.org/x/crypto |
## Frontend (React) Dependencies
| Package | License | Website |
|---------|---------|---------|
| React | MIT | https://react.dev/ |
| @tanstack/react-query | MIT | https://tanstack.com/query |
| react-router-dom | MIT | https://reactrouter.com/ |
| recharts | MIT | https://recharts.org/ |
| i18next | MIT | https://www.i18next.com/ |
| Tailwind CSS | MIT | https://tailwindcss.com/ |
| Vite | MIT | https://vitejs.dev/ |
## Nginx Modules
| Module | License | Website |
|--------|---------|---------|
| ModSecurity-nginx | Apache 2.0 | https://github.com/owasp-modsecurity/ModSecurity-nginx |
| ngx_brotli | BSD-2-Clause | https://github.com/google/ngx_brotli |
| headers-more-nginx-module | BSD-2-Clause | https://github.com/openresty/headers-more-nginx-module |
| ngx_http_geoip2_module | BSD-2-Clause | https://github.com/leev/ngx_http_geoip2_module |
---
## Apache License 2.0 Notice
The following components are licensed under the Apache License 2.0:
- ModSecurity (Copyright 2002-2024 Trustwave Holdings, Inc.)
- OWASP Core Rule Set (Copyright 2006-2024 Trustwave Holdings, Inc.)
- ModSecurity-nginx (Copyright 2015-2024 Trustwave Holdings, Inc.)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use these files except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---
## GeoLite2 End User License Agreement
This product includes GeoLite2 data created by MaxMind, available from
https://www.maxmind.com. GeoLite2 databases are offered under the
GeoLite2 End User License Agreement. For the full license text, see:
https://www.maxmind.com/en/geolite2/eula
---
## Pending legal review before commercial distribution
TimescaleDB (Community Edition, TSL) and MaxMind GeoLite2 (EULA) both carry redistribution
conditions beyond plain MIT/BSD/Apache terms. Confirm these terms are compatible with the
proprietary commercial license in `LICENSE` before shipping a release build.
Executable
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
#
# ARGUS backup — dumps the database and application data (certificates,
# uploaded config) into a single, timestamped archive.
#
# cd ~/argus && ./backup.sh [output-directory]
#
set -euo pipefail
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
OUT_DIR="${1:-$PWD}"
STAMP="$(date +%Y%m%d-%H%M%S)"
ARCHIVE="${OUT_DIR}/argus-backup-${STAMP}.tar.gz"
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
fail() { printf ' \033[31m✗\033[0m %s\n' "$1" >&2; exit 1; }
[ -f "$INSTALL_DIR/docker-compose.yml" ] || fail "No ARGUS installation found at $INSTALL_DIR (set ARGUS_INSTALL_DIR if you installed elsewhere)."
cd "$INSTALL_DIR"
bold "Backing up ARGUS..."
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
docker compose exec -T db pg_dump -U "${DB_USER:-postgres}" "${DB_NAME:-argus}" | gzip > "$WORKDIR/db.sql.gz"
ok "Database dumped"
docker run --rm -v argus_api_data:/data -v "$WORKDIR":/backup alpine \
tar czf /backup/api-data.tar.gz -C /data . >/dev/null 2>&1
ok "Application data archived"
mkdir -p "$OUT_DIR"
tar czf "$ARCHIVE" -C "$WORKDIR" db.sql.gz api-data.tar.gz
ok "Backup written to ${ARCHIVE}"
echo
printf ' %s\n' "Restore with: ./restore.sh ${ARCHIVE}"
+229
View File
@@ -0,0 +1,229 @@
services:
# TimescaleDB (PostgreSQL with time-series optimization)
db:
image: timescale/timescaledb:latest-pg17
container_name: argus-db
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
environment:
TZ: ${TZ:-UTC}
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
POSTGRES_DB: ${DB_NAME:-argus}
command: postgres -c timezone=${TZ:-UTC} -c log_timezone=${TZ:-UTC} -c shared_preload_libraries=timescaledb -c timescaledb.telemetry_level=off -c max_locks_per_transaction=512
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
# Valkey (Redis-compatible) for caching
valkey:
image: valkey/valkey:9-alpine
container_name: argus-valkey
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
environment:
TZ: ${TZ:-UTC}
command: >
valkey-server
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--appendonly yes
--appendfsync everysec
volumes:
- valkey_data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
# Restricts the api service's Docker Engine API access to exactly what it
# needs (list/inspect/exec/restart existing containers for log collection
# and nginx reload) — it cannot create new containers or touch the host
# beyond that. See docker-socket-proxy/README.md for the exact allowed set.
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:latest
container_name: argus-docker-socket-proxy
restart: unless-stopped
security_opt:
- "no-new-privileges:true"
environment:
CONTAINERS: 1
IMAGES: 1
VOLUMES: 1
SYSTEM: 1
EXEC: 1
VERSION: 1
POST: 1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./docker-socket-proxy/haproxy.cfg.template:/usr/local/etc/haproxy/haproxy.cfg.template:ro
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Go API Server
api:
image: ghcr.io/alleyviper/argus-secure-api:latest
container_name: argus-api
restart: unless-stopped
# Container hardening: no-new-privileges blocks setuid privilege
# escalation; cap_drop ALL removes every Linux capability, re-adding only
# what's needed (DAC_OVERRIDE for the shared /etc/nginx volume owned by
# the nginx user, NET_BIND_SERVICE for the DNS Security Engine's :53).
security_opt:
- "no-new-privileges:true"
cap_drop:
- ALL
cap_add:
- DAC_OVERRIDE
- NET_BIND_SERVICE
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
TZ: ${TZ:-UTC}
PORT: "8080"
DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-argus}?sslmode=disable
REDIS_URL: redis://valkey:6379/0
ENVIRONMENT: ${ENVIRONMENT:-production}
NGINX_CONTAINER: argus-proxy
NGINX_SKIP_TEST: "false"
NGINX_STATUS_URL: "http://host.docker.internal:${NGINX_HTTP_PORT:-80}/nginx_status"
NGINX_ACCESS_LOG: "/etc/nginx/logs/access_raw.log"
BACKUP_PATH: ${BACKUP_PATH:-/app/data/backups}
DOCKER_API_VERSION: ${DOCKER_API_VERSION:-}
DOCKER_HOST: tcp://docker-socket-proxy:2375
NGINX_HTTP_PORT: ${NGINX_HTTP_PORT:-}
NGINX_HTTPS_PORT: ${NGINX_HTTPS_PORT:-}
API_HOST_PORT: ${API_HOST_PORT:-9080}
API_HOST: ${API_HOST:-}
# ANIS community intelligence — optional, off by default. Set these to
# connect this instance to an already-running ANIS hub.
ANIS_ENABLED: ${ANIS_ENABLED:-false}
ANIS_URL: ${ANIS_URL:-}
ANIS_LICENSE_KEY: ${ANIS_LICENSE_KEY:-}
ANIS_ADMIN_KEY: ${ANIS_ADMIN_KEY:-}
ANIS_SHARE_ATTACKERS: ${ANIS_SHARE_ATTACKERS:-false}
DNS_SECURITY_LISTEN_ADDR: ":53"
ports:
- "127.0.0.1:${API_HOST_PORT:-9080}:8080"
# DNS Security Engine. Host-only by default — a bare forwarding
# resolver reachable from the network is exactly the profile abused
# for DNS amplification attacks against third parties. Set
# DNS_LISTEN_HOST to an internal interface IP to route real client
# DNS traffic through it (never 0.0.0.0 on an untrusted network).
- "${DNS_LISTEN_HOST:-127.0.0.1}:53:53/udp"
- "${DNS_LISTEN_HOST:-127.0.0.1}:53:53/tcp"
volumes:
- nginx_data:/etc/nginx:rw
- api_data:/app/data:rw
depends_on:
db:
condition: service_healthy
valkey:
condition: service_started
docker-socket-proxy:
condition: service_started
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 360s
# React UI (Admin Panel), served over HTTPS
ui:
image: ghcr.io/alleyviper/argus-secure-ui:latest
container_name: argus-ui
restart: unless-stopped
security_opt:
- "no-new-privileges:true"
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
environment:
TZ: ${TZ:-UTC}
ports:
- "${UI_PORT:-81}:443"
volumes:
- ui_data:/app/ssl:rw
depends_on:
- api
# Nginx reverse proxy — host network mode, so real client IPs are visible
# directly without needing PROXY protocol.
nginx:
image: ghcr.io/alleyviper/argus-secure-nginx:latest
container_name: argus-proxy
restart: always
network_mode: host
# Not cap_drop'd: runs nginx in host-network mode with a root master that
# drops workers to the nginx user, needing CHOWN/SETUID/SETGID plus binds
# on privileged 80/443.
security_opt:
- "no-new-privileges:true"
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
environment:
TZ: ${TZ:-UTC}
ulimits:
nofile:
soft: 65535
hard: 65535
volumes:
- nginx_data:/etc/nginx:rw
depends_on:
- api
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:${NGINX_HTTP_PORT:-80}/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s
volumes:
postgres_data:
name: argus_postgres_data
valkey_data:
name: argus_valkey_data
nginx_data:
name: argus_nginx_data
api_data:
name: argus_api_data
ui_data:
name: argus_ui_data
networks:
default:
name: argus-network
external: true
+97
View File
@@ -0,0 +1,97 @@
global
log stdout format raw daemon "${LOG_LEVEL}"
pidfile /run/haproxy.pid
maxconn 4000
# Turn on stats unix socket
server-state-file /var/lib/haproxy/server-state
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 10m
timeout server 10m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
# Allow seamless reloads
load-server-state-from-file global
# Use provided example error pages
errorfile 400 /usr/local/etc/haproxy/errors/400.http
errorfile 403 /usr/local/etc/haproxy/errors/403.http
errorfile 408 /usr/local/etc/haproxy/errors/408.http
errorfile 500 /usr/local/etc/haproxy/errors/500.http
errorfile 502 /usr/local/etc/haproxy/errors/502.http
errorfile 503 /usr/local/etc/haproxy/errors/503.http
errorfile 504 /usr/local/etc/haproxy/errors/504.http
backend dockerbackend
server dockersocket $SOCKET_PATH
backend docker-events
server dockersocket $SOCKET_PATH
timeout server 0
frontend dockerfrontend
bind ${BIND_CONFIG}
http-request deny unless METH_GET || { env(POST) -m bool }
# --- ARGUS-specific hardening, layered on top of the stock tecnativa
# image (see docker/docker-socket-proxy/README.md) ---
#
# The stock template's per-category CONTAINERS=1 gate is a path-prefix
# match on the whole /containers tree — once POST is globally unlocked
# (required here for restart/exec/pull/tag), CONTAINERS=1 alone would
# also let POST /containers/create through, which defeats the entire
# point of this proxy (an attacker who compromises the API could create
# a brand-new container with a host bind mount and escape to the host,
# the same class of privesc that raw docker.sock access has always
# allowed). Explicitly deny the dangerous, unused-by-ARGUS container
# lifecycle endpoints *before* the generic CONTAINERS allow rule below,
# regardless of which category env vars are set:
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/create(\?.*)?$ }
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/prune(\?.*)?$ }
http-request deny if METH_DELETE { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+$ }
# Allowed endpoints
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/((stop)|(restart)|(kill)) } { env(ALLOW_RESTARTS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/start } { env(ALLOW_START) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/stop } { env(ALLOW_STOP) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/auth } { env(AUTH) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/build } { env(BUILD) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/commit } { env(COMMIT) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/configs } { env(CONFIGS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/distribution } { env(DISTRIBUTION) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events } { env(EVENTS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/exec } { env(EXEC) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/grpc } { env(GRPC) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/images } { env(IMAGES) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/info } { env(INFO) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/networks } { env(NETWORKS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/nodes } { env(NODES) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/_ping } { env(PING) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/plugins } { env(PLUGINS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/secrets } { env(SECRETS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/services } { env(SERVICES) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/session } { env(SESSION) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/swarm } { env(SWARM) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/system } { env(SYSTEM) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/tasks } { env(TASKS) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/version } { env(VERSION) -m bool }
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/volumes } { env(VOLUMES) -m bool }
http-request deny
default_backend dockerbackend
use_backend docker-events if { path,url_dec -m reg -i ^(/v[\d\.]+)?/events }
Executable
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
#
# ARGUS health check — reports the status of every component.
#
# cd ~/argus && ./healthcheck.sh
#
set -euo pipefail
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
bad() { printf ' \033[31m✗\033[0m %s\n' "$1"; }
fail() { printf ' \033[31m✗\033[0m %s\n' "$1" >&2; exit 1; }
[ -f "$INSTALL_DIR/docker-compose.yml" ] || fail "No ARGUS installation found at $INSTALL_DIR (set ARGUS_INSTALL_DIR if you installed elsewhere)."
cd "$INSTALL_DIR"
bold "ARGUS Health Check"
echo
EXIT_CODE=0
bold "Containers"
for svc in db valkey docker-socket-proxy api ui nginx; do
STATE="$(docker compose ps --format json "$svc" 2>/dev/null | grep -oP '"State"\s*:\s*"\K[^"]+' || echo "")"
HEALTH="$(docker compose ps --format json "$svc" 2>/dev/null | grep -oP '"Health"\s*:\s*"\K[^"]+' || echo "")"
if [ "$STATE" = "running" ] && { [ -z "$HEALTH" ] || [ "$HEALTH" = "healthy" ]; }; then
ok "$svc — running${HEALTH:+ ($HEALTH)}"
elif [ -z "$STATE" ]; then
bad "$svc — not found"
EXIT_CODE=1
else
bad "$svc$STATE${HEALTH:+ ($HEALTH)}"
EXIT_CODE=1
fi
done
echo
bold "API"
API_HOST_PORT="$(grep -oP '^API_HOST_PORT=\K.*' .env 2>/dev/null || true)"
API_HOST_PORT="${API_HOST_PORT:-9080}"
HEALTH_JSON="$(curl -fsS "http://127.0.0.1:${API_HOST_PORT}/health" 2>/dev/null || true)"
if [ -n "$HEALTH_JSON" ]; then
VERSION="$(printf '%s' "$HEALTH_JSON" | grep -oP '"version"\s*:\s*"\K[^"]+' || echo "?")"
DB_STATUS="$(printf '%s' "$HEALTH_JSON" | grep -oP '"database"\s*:\s*"\K[^"]+' || echo "?")"
CACHE_STATUS="$(printf '%s' "$HEALTH_JSON" | grep -oP '"cache"\s*:\s*"\K[^"]+' || echo "?")"
ok "API reachable — version ${VERSION}, database: ${DB_STATUS}, cache: ${CACHE_STATUS}"
else
bad "API not reachable on http://127.0.0.1:${API_HOST_PORT}/health"
EXIT_CODE=1
fi
echo
UI_PORT="$(grep -oP '^UI_PORT=\K.*' .env 2>/dev/null || true)"
UI_PORT="${UI_PORT:-81}"
bold "Admin panel"
if curl -fsSk -o /dev/null "https://127.0.0.1:${UI_PORT}" 2>/dev/null; then
ok "Reachable at https://localhost:${UI_PORT}"
else
bad "Not reachable at https://localhost:${UI_PORT}"
EXIT_CODE=1
fi
echo
if [ "$EXIT_CODE" = 0 ]; then
bold "All checks passed."
else
bold "One or more checks failed — see above. 'docker compose logs <service>' for details."
fi
exit "$EXIT_CODE"
Executable
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env bash
#
# ARGUS installer — fetches the production compose stack, generates secrets,
# starts the platform, and prints your admin credentials.
#
# curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/install.sh | bash
#
set -euo pipefail
REPO_RAW_BASE="https://raw.githubusercontent.com/alleyviper/argus/main"
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
NETWORK_NAME="argus-network"
# ── 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
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) ──
if grep -q '^DB_PASSWORD=change-me-in-production' .env 2>/dev/null; then
DB_PASSWORD="$(openssl rand -base64 24 | tr -d '\n')"
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 is installed and running."
echo
info "Admin Panel: https://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
info "Configuration: ${INSTALL_DIR}/.env and ${INSTALL_DIR}/docker-compose.yml"
info "Upgrade: cd ${INSTALL_DIR} && docker compose pull && docker compose up -d"
info "Backups: see https://github.com/alleyviper/argus#backups"
echo
Executable
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
#
# ARGUS restore — loads a backup created by backup.sh into a FRESH ARGUS
# installation (empty database, no prior data). To restore onto a machine
# that already has data on it, uninstall.sh --remove-data && install.sh
# first, then run this.
#
# cd ~/argus && ./restore.sh argus-backup-20260719-120000.tar.gz
#
set -euo pipefail
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
ARCHIVE="${1:-}"
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; }
[ -n "$ARCHIVE" ] || fail "Usage: ./restore.sh <path-to-backup.tar.gz>"
[ -f "$ARCHIVE" ] || fail "No such file: $ARCHIVE"
[ -f "$INSTALL_DIR/docker-compose.yml" ] || fail "No ARGUS installation found at $INSTALL_DIR (set ARGUS_INSTALL_DIR if you installed elsewhere)."
cd "$INSTALL_DIR"
warn "This assumes a freshly installed, empty ARGUS instance."
warn "Restoring onto an instance that already has data will fail or corrupt it."
printf " Type YES to continue: "
read -r CONFIRM
[ "$CONFIRM" = "YES" ] || fail "Aborted."
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
tar xzf "$ARCHIVE" -C "$WORKDIR"
[ -f "$WORKDIR/db.sql.gz" ] && [ -f "$WORKDIR/api-data.tar.gz" ] || fail "Not a valid ARGUS backup archive (missing db.sql.gz / api-data.tar.gz)."
bold "Stopping the API (keeping the database up)..."
docker compose stop api ui nginx >/dev/null
bold "Restoring database..."
gunzip -c "$WORKDIR/db.sql.gz" | docker compose exec -T db psql -U "${DB_USER:-postgres}" "${DB_NAME:-argus}" >/dev/null
ok "Database restored"
bold "Restoring application data..."
docker run --rm -v argus_api_data:/data -v "$WORKDIR":/backup alpine \
sh -c "rm -rf /data/* && tar xzf /backup/api-data.tar.gz -C /data" >/dev/null 2>&1
ok "Application data restored"
bold "Starting ARGUS..."
docker compose up -d
echo
ok "Restore complete"
info "Give it a minute to finish starting, then check: ./healthcheck.sh"
Executable
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
#
# ARGUS uninstaller — stops and removes ARGUS containers.
#
# By default this KEEPS your data (database, certificates, uploaded config) —
# it only stops and removes containers, so a plain re-install (install.sh)
# picks up right where you left off.
#
# cd ~/argus && ./uninstall.sh # containers only, data kept
# ./uninstall.sh --remove-data # also destroys all data (asks to confirm)
# ./uninstall.sh --remove-data --yes # non-interactive, for scripted use
#
set -euo pipefail
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
REMOVE_DATA=false
ASSUME_YES=false
for arg in "$@"; do
case "$arg" in
--remove-data) REMOVE_DATA=true ;;
--yes|-y) ASSUME_YES=true ;;
*) echo "Unknown option: $arg" >&2; exit 1 ;;
esac
done
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 Uninstaller"
echo
[ -f "$INSTALL_DIR/docker-compose.yml" ] || fail "No ARGUS installation found at $INSTALL_DIR (set ARGUS_INSTALL_DIR if you installed elsewhere)."
cd "$INSTALL_DIR"
if [ "$REMOVE_DATA" = "true" ] && [ "$ASSUME_YES" != "true" ]; then
warn "This will permanently delete your ARGUS database, certificates, and all"
warn "uploaded configuration. This cannot be undone."
printf " Type YES to confirm: "
read -r CONFIRM
[ "$CONFIRM" = "YES" ] || fail "Aborted — data was not touched."
fi
if [ "$REMOVE_DATA" = "true" ]; then
bold "Stopping ARGUS and removing all data..."
docker compose down -v
ok "Containers and data volumes removed"
else
bold "Stopping ARGUS (data preserved)..."
docker compose down
ok "Containers removed — database and certificates are untouched"
info "Re-run install.sh (or 'docker compose up -d' here) to bring ARGUS back with the same data."
fi
echo
info "Configuration files remain at ${INSTALL_DIR} — remove that directory yourself if you want a completely clean slate."
Executable
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
#
# ARGUS updater — pulls the latest published images and restarts the stack.
#
# cd ~/argus && ./update.sh
# (or, if you don't have it locally yet)
# curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/update.sh | bash
#
set -euo pipefail
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
info() { printf ' %s\n' "$1"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
fail() { printf ' \033[31m✗\033[0m %s\n' "$1" >&2; exit 1; }
bold "ARGUS Updater"
echo
[ -f "$INSTALL_DIR/docker-compose.yml" ] || fail "No ARGUS installation found at $INSTALL_DIR (set ARGUS_INSTALL_DIR if you installed elsewhere)."
cd "$INSTALL_DIR"
API_HOST_PORT="$(grep -oP '^API_HOST_PORT=\K.*' .env 2>/dev/null || true)"
API_HOST_PORT="${API_HOST_PORT:-9080}"
BEFORE_VERSION="$(curl -fsS "http://127.0.0.1:${API_HOST_PORT}/health" 2>/dev/null | grep -oP '"version"\s*:\s*"\K[^"]+' || echo "unknown")"
info "Current version: ${BEFORE_VERSION}"
bold "Pulling latest images..."
docker compose pull
bold "Restarting..."
docker compose up -d
echo
bold "Waiting for ARGUS to become healthy..."
READY=false
for _ in $(seq 1 60); 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
fail "ARGUS did not come back healthy within 5 minutes. Check: docker compose -f $INSTALL_DIR/docker-compose.yml logs"
fi
AFTER_VERSION="$(curl -fsS "http://127.0.0.1:${API_HOST_PORT}/health" 2>/dev/null | grep -oP '"version"\s*:\s*"\K[^"]+' || echo "unknown")"
ok "ARGUS is healthy — now running version ${AFTER_VERSION}"
if [ "$BEFORE_VERSION" != "$AFTER_VERSION" ]; then
info "Updated: ${BEFORE_VERSION}${AFTER_VERSION}"
else
info "Already up to date."
fi