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:
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,221 @@
|
||||
<div align="center">
|
||||
|
||||
# ARGUS
|
||||
|
||||
### Enterprise Web Security Platform
|
||||
|
||||
[](LICENSE)
|
||||
[](https://github.com/alleyviper/argus/pkgs/container/argus-secure-api)
|
||||
|
||||
[](https://nginx.org/)
|
||||
[](https://modsecurity.org/)
|
||||
[](https://coreruleset.org/)
|
||||
[]()
|
||||
|
||||
<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 1–4, 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>
|
||||
@@ -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.
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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."
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user