Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bdfd99c4d | ||
|
|
913615903b |
@@ -0,0 +1,67 @@
|
|||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# On by default against the public Community Edition hub — an empty license
|
||||||
|
# key already grants the community (free) tier, no signup required. Point
|
||||||
|
# ANIS_URL at your own self-hosted ANIS instead if you run one, or set
|
||||||
|
# ANIS_ENABLED=false to turn this off entirely.
|
||||||
|
|
||||||
|
ANIS_ENABLED=true
|
||||||
|
ANIS_URL=https://anis.weboria.eu
|
||||||
|
# License key from ANIS dashboard -> Licenses -> Create License.
|
||||||
|
# Leave blank to use the community (free) tier.
|
||||||
|
ANIS_LICENSE_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
|
||||||
+2
-7
@@ -3,12 +3,6 @@
|
|||||||
All notable changes to ARGUS are documented here, in plain language for operators.
|
All notable changes to ARGUS are documented here, in plain language for operators.
|
||||||
Versioning follows [Semantic Versioning](https://semver.org/).
|
Versioning follows [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
> **This file lags behind the currently shipped version** (the entries below stop at 3.29.0;
|
|
||||||
> `./healthcheck.sh` or `GET /health` will show you what's actually running). Keeping it current
|
|
||||||
> on every release needs to become part of the standard process — not backfilled retroactively
|
|
||||||
> with invented descriptions of past changes. Updates always ship via `docker compose pull`
|
|
||||||
> regardless of what this file says (see the [README](README.md#operations)).
|
|
||||||
|
|
||||||
## [3.29.0] — 2026-07-19
|
## [3.29.0] — 2026-07-19
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
@@ -33,4 +27,5 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [3.26.0] and earlier
|
## [3.26.0] and earlier
|
||||||
|
|
||||||
Earlier release history predates this distribution's changelog.
|
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.
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
ARGUS Enterprise Web Security Platform — Commercial License
|
ARGUS Secure — Proprietary Software License
|
||||||
Copyright (c) 2025-2026 Weboria. All rights reserved.
|
Copyright (c) 2025-2026 AI4SEC / lmelo. All rights reserved.
|
||||||
|
|
||||||
** PLACEHOLDER — NOT A FINAL LICENSE **
|
** PLACEHOLDER — NOT A FINAL LICENSE **
|
||||||
|
|
||||||
This repository contains proprietary, commercial software developed by Weboria. A complete
|
This repository contains proprietary, commercial software. A complete commercial license
|
||||||
commercial license agreement has not yet been finalized and this file is a placeholder pending
|
agreement has not yet been finalized and this file is a placeholder pending legal review.
|
||||||
legal review — this is a real, open item, not boilerplate.
|
|
||||||
|
|
||||||
Until a final license is adopted, no rights are granted to use, copy, modify, merge, publish,
|
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
|
distribute, sublicense, or sell copies of this software, in whole or in part, except as
|
||||||
separately agreed in writing with Weboria.
|
separately agreed in writing with the copyright holder.
|
||||||
|
|
||||||
This software incorporates third-party open-source components under their own licenses;
|
This software incorporates third-party open-source components under their own licenses;
|
||||||
see THIRD_PARTY_LICENSES.md and NOTICE for details. Nothing in this placeholder alters the
|
see THIRD_PARTY_LICENSES and NOTICE for details. Nothing in this placeholder alters the terms
|
||||||
terms of those third-party components.
|
of those third-party components.
|
||||||
|
|
||||||
TODO before release: replace this file with final commercial/enterprise license text.
|
TODO before release: replace this file with final commercial/enterprise license text.
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
ARGUS Enterprise Web Security Platform
|
ARGUS Secure
|
||||||
|
Copyright (c) 2025-2026 AI4SEC / lmelo
|
||||||
|
|
||||||
Copyright © 2025-2026 Weboria
|
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.
|
||||||
ARGUS contains third-party open-source software.
|
|
||||||
The licenses and notices for these components are available in:
|
|
||||||
THIRD_PARTY_LICENSES.md
|
|
||||||
|
|
||||||
Notable components:
|
Notable components:
|
||||||
- nginx (BSD-2-Clause) — Copyright (C) 2002-2024 Igor Sysoev, (C) 2011-2024 Nginx, Inc.
|
- 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.
|
- 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 (Apache-2.0) — Copyright (C) 2006-2024 Trustwave Holdings, Inc.,
|
||||||
OWASP Core Rule Set contributors
|
OWASP Core Rule Set contributors
|
||||||
- TimescaleDB (Timescale License / Apache 2.0 depending on component) — distributed as an
|
- TimescaleDB Community Edition (Timescale License) — see THIRD_PARTY_LICENSES for
|
||||||
embedded time-series storage component as part of the ARGUS Enterprise Appliance, not
|
redistribution terms
|
||||||
offered as a standalone database service. See THIRD_PARTY_LICENSES.md for detail.
|
- MaxMind GeoLite2 (GeoLite2 EULA) — "This product includes GeoLite2 data created by
|
||||||
|
MaxMind, available from https://www.maxmind.com"
|
||||||
- Valkey (BSD-3-Clause)
|
- Valkey (BSD-3-Clause)
|
||||||
- PostgreSQL (PostgreSQL License), Alpine Linux, BusyBox, Go runtime, Node.js (build-time
|
|
||||||
only), OpenSSL, curl — see THIRD_PARTY_LICENSES.md
|
|
||||||
|
|
||||||
The following ARGUS components are proprietary software developed by Weboria and are not
|
This repository's own source code is proprietary; see LICENSE.
|
||||||
covered by the third-party licenses above: ARGUS Core Platform, ARGUS Management Interface,
|
|
||||||
ARGUS Security Automation Engine, ARGUS API Layer, ARGUS Threat Analysis Components, ARGUS
|
|
||||||
Licensing System, ARGUS Enterprise Features, and Weboria Integrations. These are distributed
|
|
||||||
under the ARGUS Commercial License — see LICENSE.
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
# ARGUS Enterprise Web Security Platform
|
# ARGUS
|
||||||
|
|
||||||
### A real-time security operations platform for the modern web edge
|
### Enterprise Web Security Platform
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[]()
|
[](https://github.com/alleyviper/argus/pkgs/container/argus-secure-api)
|
||||||
|
|
||||||
[](https://nginx.org/)
|
[](https://nginx.org/)
|
||||||
[](https://modsecurity.org/)
|
[](https://modsecurity.org/)
|
||||||
@@ -13,18 +13,16 @@
|
|||||||
[]()
|
[]()
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>Enterprise-grade protection for the modern web edge — in the same league as Fortinet,
|
<strong>A real-time security operations platform for the modern web edge.<br/>
|
||||||
Palo Alto, and F5, purpose-built for how applications are actually attacked today.</strong><br/>
|
Reverse proxy, Web Application Firewall, bot defense, DNS protection, and live threat
|
||||||
<strong>Reverse proxy. Web Application Firewall. Bot and AI-crawler defense. DNS security.
|
investigation — in one appliance.</strong>
|
||||||
Real-time threat intelligence. Live attack visualization. Incident investigation. One
|
|
||||||
appliance, one dashboard, zero blind spots.</strong>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="#-key-features">✨ Features</a> •
|
<a href="#-key-features">✨ Features</a> •
|
||||||
<a href="#-installation">🚀 Installation</a> •
|
<a href="#-installation">🚀 Installation</a> •
|
||||||
<a href="#-operations">🛠 Operations</a> •
|
<a href="#-operations">🛠 Operations</a> •
|
||||||
<a href="#-licensing--support">📄 Licensing & Support</a>
|
<a href="#-api">📚 API</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -34,96 +32,52 @@
|
|||||||
## ✨ Key Features
|
## ✨ Key Features
|
||||||
|
|
||||||
### 🛡️ Web Application Firewall
|
### 🛡️ Web Application Firewall
|
||||||
ModSecurity v3 running the full OWASP Core Rule Set, tunable across paranoia levels 1–4 per
|
ModSecurity v3 with the OWASP Core Rule Set. Paranoia levels 1–4, per-host rule exceptions,
|
||||||
site — start permissive on a new application, tighten as you learn its traffic. Detection-only
|
detection-only or blocking modes.
|
||||||
or blocking mode per host, per-rule exceptions for known false positives, and a Rule Health
|
|
||||||
view that tracks which rules are actually earning their keep versus generating noise, so tuning
|
|
||||||
is based on evidence, not guesswork.
|
|
||||||
|
|
||||||
### 🧠 ANIS Threat Intelligence
|
|
||||||
ARGUS ships connected by default to **ANIS (Argus Network Intelligence Server)**, Weboria's
|
|
||||||
community-powered reputation network: every ARGUS deployment that opts in contributes
|
|
||||||
confirmed-attacker IPs and receives the aggregated blocklist back in return, so your appliance
|
|
||||||
gets smarter from day one using signal from every other protected site — no license key
|
|
||||||
required for the free Community tier. Every dashboard, investigation view, and auto-ban
|
|
||||||
decision is enriched with live ANIS reputation scoring. A license key upgrades the tier —
|
|
||||||
manage it entirely from Threat Intel → ANIS Connection in the dashboard, or turn the
|
|
||||||
integration off entirely from the same page.
|
|
||||||
|
|
||||||
### 🌐 Attack Origin Visualization
|
### 🌐 Attack Origin Visualization
|
||||||
A live global map of inbound attacks — real-time arcs from source to your infrastructure,
|
A live global map of inbound attacks — real-time arcs from source to your infrastructure,
|
||||||
color-coded by severity or by attack type, with a running feed of top attacking countries and IPs
|
severity-coded, with instant drill-down into any event's full detail.
|
||||||
and a fullscreen "SOC wall" mode for a dedicated display. Pause, rewind, and replay historical
|
|
||||||
traffic, or click straight through to any single event's full detail.
|
|
||||||
|
|
||||||
### 🔎 Incident Investigation
|
### 🔎 Incident Investigation
|
||||||
Pivot from any IP, log line, or dashboard event straight into that entity's complete history —
|
Pivot from any IP, log line, or dashboard event straight into that entity's complete history —
|
||||||
every affected host it touched, every rule it triggered, its geography and ASN, and its live
|
affected hosts, triggered rules, geography, and live threat-intelligence reputation — without
|
||||||
ANIS reputation score — all in one place, without losing your position in whatever you were
|
losing your place.
|
||||||
looking at.
|
|
||||||
|
|
||||||
### 🤖 Bot & AI-Crawler Defense
|
### 🤖 Bot & AI-Crawler Defense
|
||||||
Automatically blocks known-bad bots and scrapers, with a maintained allowlist so legitimate
|
Blocks known-bad bots and AI scrapers automatically, with a search-engine allowlist so legitimate
|
||||||
search engines are never caught in the net. Purpose-built detection for AI crawlers (GPTBot,
|
crawlers are never affected. Escalating challenge modes (browser check, proof-of-work, visual
|
||||||
ClaudeBot, and similar) lets you choose exactly which ones are welcome. Escalating challenge
|
CAPTCHA) for suspicious traffic.
|
||||||
modes — silent browser verification, proof-of-work, visual CAPTCHA — apply exactly as much
|
|
||||||
friction as suspicious traffic actually warrants, backed by a live risk-scoring engine so the
|
|
||||||
decision is never a coin flip.
|
|
||||||
|
|
||||||
### 🧬 DNS Security Engine
|
### 🧬 DNS Security Engine
|
||||||
Passive DNS intelligence starts working the moment your first site is protected — no network
|
An embedded forwarding DNS resolver with real-time threat detection — DGA domains, DNS
|
||||||
reconfiguration required — scoring every domain your traffic touches for DGA patterns,
|
tunneling, fast-flux infrastructure, cache-poisoning attempts — and automatic mitigation via
|
||||||
fast-flux infrastructure, and other red flags, and cross-referencing that against real WAF
|
sinkholing.
|
||||||
activity for a much stronger signal than either check alone. An optional embedded resolver adds
|
|
||||||
network-wide DNS tunneling detection, NXDOMAIN-flood protection, and automatic sinkholing for
|
|
||||||
sites that want DNS-layer coverage too.
|
|
||||||
|
|
||||||
### 🌍 GeoIP & Cloud-Provider Access Control
|
### 🌍 GeoIP & Cloud-Provider Access Control
|
||||||
Allow or block traffic by country or hosting provider, with live visual feedback as you tune
|
Allow or block traffic by country or hosting provider, with live visual feedback.
|
||||||
the policy — see exactly what would have been blocked before you commit to it.
|
|
||||||
|
|
||||||
### 🔒 Automated SSL
|
### 🔒 Automated SSL
|
||||||
Let's Encrypt with automatic renewal, including wildcard certificates via DNS-01 challenge
|
Let's Encrypt with automatic renewal, including wildcard certificates via DNS-01 challenge
|
||||||
across major DNS providers (Cloudflare, DuckDNS, Dynu, and more) — certificates that never
|
(Cloudflare, DuckDNS, Dynu, and more).
|
||||||
expire on your watch.
|
|
||||||
|
|
||||||
### ⚡ Rate Limiting & Auto-Ban
|
### ⚡ Rate Limiting & Auto-Ban
|
||||||
Configurable per-IP/per-URI rate limits, automatic banning on repeated WAF violations, and
|
Configurable per-IP/per-URI rate limits, automatic banning on repeated WAF violations, and
|
||||||
community-intelligence-driven blocking via ANIS — layered defenses that reinforce each other
|
optional community threat-intelligence-driven blocking.
|
||||||
rather than working in isolation.
|
|
||||||
|
|
||||||
### 👥 Enterprise User Management
|
### 👥 Full User Management
|
||||||
Role-based access control, per-user session management, forced password resets, account
|
Role-based access, per-user session control, forced password resets, account lock/unlock, MFA,
|
||||||
lock/unlock as a reversible incident-response tool, mandatory MFA for administrators, and a
|
and a complete login history — everything a security team needs to administer access safely.
|
||||||
complete audit trail of every login and admin action — everything a security team needs to
|
|
||||||
administer access with confidence.
|
|
||||||
|
|
||||||
### 📊 Real-Time Operations Dashboard
|
### 📊 Real-Time Operations Dashboard
|
||||||
Live traffic, security events, and system health updated continuously — a true operations
|
Live traffic, security events, and system health, updated continuously — no manual refresh.
|
||||||
console, not a page you have to remember to refresh. Guided, role-based dashboard profiles (SOC
|
|
||||||
Analyst, Security Administrator, Network Administrator, Executive Overview) put the right view
|
|
||||||
in front of the right person automatically.
|
|
||||||
|
|
||||||
### 🎯 Guided Security Policy Center
|
|
||||||
Every protection module — WAF, DNS, GeoIP, Bot Protection, Rate Limiting, SSL/TLS, and more —
|
|
||||||
comes with one-click policy tiers from Home to Maximum Protection, a plain-language impact
|
|
||||||
preview before you apply anything, and an honest posture score that tells you exactly what's
|
|
||||||
configured, what's missing, and why it matters. No security expertise required to deploy with
|
|
||||||
confidence.
|
|
||||||
|
|
||||||
### 🔀 Load Balancing & Stream Proxying
|
### 🔀 Load Balancing & Stream Proxying
|
||||||
Multiple backend servers with active health checks, plus TCP/UDP stream proxying with optional
|
Multiple backend servers with health checks, plus TCP/UDP stream proxying with optional SNI
|
||||||
SNI routing for non-HTTP services.
|
routing.
|
||||||
|
|
||||||
### 📄 Reports Center
|
|
||||||
Generate a report over any time range — executive summary, WAF activity, bot protection, GeoIP
|
|
||||||
distribution, backups, certificates, or overall infrastructure health — as JSON, CSV, or HTML,
|
|
||||||
through a guided wizard. PDF export, scheduling, and compliance-framework templates are not
|
|
||||||
available yet.
|
|
||||||
|
|
||||||
### 🔮 Modern Protocol Support
|
### 🔮 Modern Protocol Support
|
||||||
HTTP/3 (QUIC) and post-quantum-ready TLS (ML-KEM hybrid key exchange) — ready for where the web
|
HTTP/3 (QUIC) and post-quantum-ready TLS (ML-KEM hybrid key exchange).
|
||||||
is headed, not just where it's been.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -131,20 +85,18 @@ is headed, not just where it's been.
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- A Linux host with Docker Engine 24.0+ and the Docker Compose plugin (`docker compose`)
|
- A Linux host with Docker 24.0+ and the Docker Compose plugin (`docker compose`)
|
||||||
- 2 vCPU / 4 GB RAM minimum for evaluation; see production sizing guidance in `docs/` for
|
|
||||||
real traffic volumes
|
|
||||||
- Ports 80/443 available for the reverse proxy, and 81 for the admin panel (all configurable)
|
- Ports 80/443 available for the reverse proxy, and 81 for the admin panel (all configurable)
|
||||||
|
|
||||||
### One-line install
|
### One-line install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/install.sh | bash
|
curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
This downloads the deployment files, generates secure database/application secrets, creates the
|
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
|
required Docker network, pulls the production images, starts ARGUS, and prints your admin
|
||||||
login at the end. Installs to `/opt/argus` by default — set `ARGUS_INSTALL_DIR` first to change
|
login at the end. Installs to `~/argus` by default — set `ARGUS_INSTALL_DIR` first to change
|
||||||
that.
|
that.
|
||||||
|
|
||||||
### Manual install
|
### Manual install
|
||||||
@@ -152,13 +104,13 @@ that.
|
|||||||
If you'd rather see every step:
|
If you'd rather see every step:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /opt/argus/docker-socket-proxy && cd /opt/argus
|
mkdir -p ~/argus/docker-socket-proxy && cd ~/argus
|
||||||
curl -fsSL https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/docker-compose.yml -o docker-compose.yml
|
curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/docker-compose.yml -o docker-compose.yml
|
||||||
curl -fsSL https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/env.example -o .env
|
curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/.env.example -o .env
|
||||||
curl -fsSL https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/docker-socket-proxy/haproxy.cfg.template -o docker-socket-proxy/haproxy.cfg.template
|
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
|
# Generate a secure database password
|
||||||
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$(openssl rand -hex 32)/" .env
|
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$(openssl rand -base64 24)/" .env
|
||||||
|
|
||||||
# ARGUS expects this network to already exist
|
# ARGUS expects this network to already exist
|
||||||
docker network create argus-network
|
docker network create argus-network
|
||||||
@@ -167,30 +119,19 @@ docker compose pull
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Then log in at `https://<your-server-address>:81` with `admin` / `admin` and change the password
|
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.
|
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
|
**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.
|
it behind its own proxy host with access lists and 2FA enabled.
|
||||||
|
|
||||||
### Next steps after install
|
|
||||||
|
|
||||||
1. Open the administration panel at the access URL printed at the end of install.
|
|
||||||
2. Complete the first-run setup wizard — regional settings and a guided review of every
|
|
||||||
protection module.
|
|
||||||
3. Confirm your administrator password was rotated (the installer does this automatically).
|
|
||||||
4. Configure domains and reverse proxy hosts under **Proxy Hosts**.
|
|
||||||
5. Review the ANIS threat intelligence connection under **Threat Intel** — on by default
|
|
||||||
against the public Community Edition hub.
|
|
||||||
6. Review security policies in the **Security Policy Center**.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠 Operations
|
## 🛠 Operations
|
||||||
|
|
||||||
All lifecycle management is Docker-based — five scripts cover day-to-day operation, downloaded
|
Five scripts cover day-to-day operation — download them once alongside `docker-compose.yml`
|
||||||
once alongside `docker-compose.yml` (the one-line installer already does this for you), then run
|
(the one-line installer already does this for you), then run from `~/argus` (or wherever you
|
||||||
from `/opt/argus` (or wherever you installed):
|
installed):
|
||||||
|
|
||||||
| Script | Purpose |
|
| Script | Purpose |
|
||||||
|--------|---------|
|
|--------|---------|
|
||||||
@@ -200,24 +141,21 @@ from `/opt/argus` (or wherever you installed):
|
|||||||
| `./restore.sh <archive>` | Restore a backup onto a fresh installation |
|
| `./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 |
|
| `./uninstall.sh [--remove-data]` | Stop ARGUS; data is kept unless you explicitly opt in to removing it |
|
||||||
|
|
||||||
You never manually download application files or modify containers directly — every update is
|
|
||||||
delivered as a new published image version.
|
|
||||||
|
|
||||||
### Upgrading
|
### Upgrading
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/argus
|
cd ~/argus
|
||||||
docker compose pull
|
./update.sh
|
||||||
docker compose up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
(`./update.sh` wraps the same two commands with a before/after version check.) This is the only
|
New versions are published to GHCR as soon as they're released — `update.sh` always pulls the
|
||||||
supported upgrade path; there is no separate migration step to run.
|
current stable release. This is the only supported upgrade path; there is no separate migration
|
||||||
|
step to run.
|
||||||
|
|
||||||
### Backups
|
### Backups
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/argus
|
cd ~/argus
|
||||||
./backup.sh # writes argus-backup-<timestamp>.tar.gz to the current directory
|
./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
|
./restore.sh <archive> # onto a fresh installation only — see the script's own header
|
||||||
```
|
```
|
||||||
@@ -232,13 +170,12 @@ Add `--username <name>` to target a specific account, or `--password '...'` to s
|
|||||||
value instead of generating one. See `docker compose exec api ./server reset-password --help`
|
value instead of generating one. See `docker compose exec api ./server reset-password --help`
|
||||||
for the rest of the options (clearing 2FA, etc.).
|
for the rest of the options (clearing 2FA, etc.).
|
||||||
|
|
||||||
### Threat intelligence integration
|
### Threat intelligence integration (optional)
|
||||||
|
|
||||||
ARGUS connects to **ANIS**, Weboria's threat-intelligence hub, to share confirmed attacker IPs
|
ARGUS can connect to **ANIS**, a separately-deployed threat-intelligence hub, to share confirmed
|
||||||
and receive a community blocklist in return. Enabled by default against the public Community
|
attacker IPs and receive a community blocklist in return. It's off by default — set
|
||||||
Edition hub — no license key required. Set `ANIS_ENABLED=false` in `.env` to turn this off
|
`ANIS_ENABLED=true` and `ANIS_URL` in `.env` to turn it on. See the comments in `.env.example`
|
||||||
entirely. A license key upgrades the tier — set it from Threat Intel → ANIS Connection in the
|
for the full set of options.
|
||||||
dashboard, not from `.env`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -247,7 +184,7 @@ dashboard, not from `.env`.
|
|||||||
ARGUS exposes a full REST API for automation. Authenticate with a JWT (`POST
|
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>`,
|
/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
|
created from the admin panel) for CI/CD and scripted access. Interactive API documentation is
|
||||||
served at `https://<your-server-address>:81/api/docs` once ARGUS is running.
|
served at `https://localhost:81/api/docs` once ARGUS is running.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -261,23 +198,24 @@ served at `https://<your-server-address>:81/api/docs` once ARGUS is running.
|
|||||||
| `NGINX_HTTP_PORT` / `NGINX_HTTPS_PORT` | Reverse-proxy listen ports | `80` / `443` |
|
| `NGINX_HTTP_PORT` / `NGINX_HTTPS_PORT` | Reverse-proxy listen ports | `80` / `443` |
|
||||||
| `API_HOST_PORT` | Internal API port exposed to the host | `9080` |
|
| `API_HOST_PORT` | Internal API port exposed to the host | `9080` |
|
||||||
| `DNS_LISTEN_HOST` | DNS Security Engine bind address | `127.0.0.1` |
|
| `DNS_LISTEN_HOST` | DNS Security Engine bind address | `127.0.0.1` |
|
||||||
| `ANIS_ENABLED` | Threat-intel hub integration | `true` |
|
| `ANIS_ENABLED` / `ANIS_URL` | Optional threat-intel hub integration | `false` / *(empty)* |
|
||||||
|
|
||||||
Full list with descriptions in `env.example`. ANIS's URL is fixed (`https://anis.weboria.eu`,
|
Full list with descriptions in `.env.example`.
|
||||||
not configurable) and its license key is dashboard-only — neither is an env var.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📄 Licensing & Support
|
## 📄 License
|
||||||
|
|
||||||
ARGUS is commercial software licensed by Weboria — see [LICENSE](LICENSE). Third-party
|
This is proprietary, commercial software — see [LICENSE](LICENSE). Third-party open-source
|
||||||
open-source components retain their own licenses — see
|
components retain their own licenses — see [THIRD_PARTY_LICENSES](THIRD_PARTY_LICENSES) and
|
||||||
[THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md) and [NOTICE](NOTICE).
|
[NOTICE](NOTICE).
|
||||||
|
|
||||||
For licensing questions or support, contact the **Weboria Support Portal**.
|
## 💬 Support
|
||||||
|
|
||||||
|
[GitHub Issues](https://github.com/alleyviper/argus/issues) — bug reports and feature requests.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<sub>© 2025-2026 Weboria. ARGUS Enterprise Web Security Platform.</sub>
|
<sub>© 2025-2026 ARGUS — AI4SEC.</sub>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,16 @@
|
|||||||
# Third-Party Licenses
|
# Third-Party Licenses
|
||||||
|
|
||||||
ARGUS uses the following third-party software.
|
ARGUS Secure uses the following third-party software:
|
||||||
|
|
||||||
## Core Components
|
## Core Components
|
||||||
|
|
||||||
### TimescaleDB
|
### TimescaleDB
|
||||||
- **License**: Timescale License (TSL) / Apache 2.0 depending on component
|
- **License**: Timescale License (TSL)
|
||||||
- **Website**: https://www.timescale.com/
|
- **Website**: https://github.com/timescale/timescaledb
|
||||||
|
- **License URL**: https://github.com/timescale/timescaledb/blob/main/tsl/LICENSE-TIMESCALE
|
||||||
ARGUS uses TimescaleDB as an embedded time-series storage component for security events,
|
- **Note**: TimescaleDB Community Edition is used for time-series data storage and compression.
|
||||||
telemetry, and operational metrics.
|
The Timescale License carries redistribution conditions beyond plain open source — reviewed
|
||||||
|
before bundling with a commercial, proprietary product; see legal review note below.
|
||||||
TimescaleDB is distributed as part of the ARGUS Enterprise Appliance and is not offered as a
|
|
||||||
standalone database service. Customers receive ARGUS as a complete security appliance;
|
|
||||||
TimescaleDB remains subject to its original license terms.
|
|
||||||
|
|
||||||
### PostgreSQL
|
|
||||||
- **License**: PostgreSQL License (permissive, BSD/MIT-style)
|
|
||||||
- **Website**: https://www.postgresql.org/
|
|
||||||
|
|
||||||
TimescaleDB is distributed as a PostgreSQL extension; PostgreSQL itself is the underlying
|
|
||||||
database engine.
|
|
||||||
|
|
||||||
### Nginx
|
### Nginx
|
||||||
- **License**: BSD-2-Clause
|
- **License**: BSD-2-Clause
|
||||||
@@ -37,46 +27,17 @@ database engine.
|
|||||||
- **Website**: https://coreruleset.org/
|
- **Website**: https://coreruleset.org/
|
||||||
- **Copyright**: (C) 2006-2024 Trustwave Holdings, Inc., OWASP Core Rule Set contributors
|
- **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
|
### Valkey
|
||||||
- **License**: BSD-3-Clause
|
- **License**: BSD-3-Clause
|
||||||
- **Website**: https://valkey.io/
|
- **Website**: https://valkey.io/
|
||||||
|
|
||||||
### Alpine Linux
|
|
||||||
- **License**: Various (base OS, mostly MIT/BSD-style per-package licenses)
|
|
||||||
- **Website**: https://alpinelinux.org/
|
|
||||||
|
|
||||||
Alpine Linux is the base image for the ARGUS API, UI build, and proxy container images.
|
|
||||||
|
|
||||||
### BusyBox
|
|
||||||
- **License**: GPL-2.0
|
|
||||||
- **Website**: https://busybox.net/
|
|
||||||
|
|
||||||
Bundled as part of the Alpine Linux base images above.
|
|
||||||
|
|
||||||
### Go Runtime
|
|
||||||
- **License**: BSD-3-Clause
|
|
||||||
- **Website**: https://go.dev/
|
|
||||||
|
|
||||||
The ARGUS API is built with Go; the compiled binary statically includes the Go runtime.
|
|
||||||
|
|
||||||
### Node.js (build-time only)
|
|
||||||
- **License**: MIT (Node.js core) plus per-package licenses of the npm dependencies below
|
|
||||||
- **Website**: https://nodejs.org/
|
|
||||||
|
|
||||||
Used to build the ARGUS web UI; not present in the running API/proxy containers.
|
|
||||||
|
|
||||||
### OpenSSL / TLS libraries
|
|
||||||
- **License**: Apache License 2.0 (OpenSSL 3.x) / various
|
|
||||||
- **Website**: https://www.openssl.org/
|
|
||||||
|
|
||||||
Used by the nginx proxy image for TLS termination.
|
|
||||||
|
|
||||||
### curl
|
|
||||||
- **License**: MIT/X derivate (curl license)
|
|
||||||
- **Website**: https://curl.se/
|
|
||||||
|
|
||||||
Used in container healthchecks and installer scripts.
|
|
||||||
|
|
||||||
## Backend (Go) Dependencies
|
## Backend (Go) Dependencies
|
||||||
|
|
||||||
| Package | License | Website |
|
| Package | License | Website |
|
||||||
@@ -86,6 +47,7 @@ Used in container healthchecks and installer scripts.
|
|||||||
| github.com/lib/pq | MIT | https://github.com/lib/pq |
|
| github.com/lib/pq | MIT | https://github.com/lib/pq |
|
||||||
| github.com/google/uuid | BSD-3-Clause | https://github.com/google/uuid |
|
| 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/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 |
|
| 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 |
|
| golang.org/x/crypto | BSD-3-Clause | https://golang.org/x/crypto |
|
||||||
|
|
||||||
@@ -134,25 +96,17 @@ limitations under the License.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Third-party components have been reviewed for inclusion in ARGUS Enterprise deployments.
|
## GeoLite2 End User License Agreement
|
||||||
Each component remains subject to its original license terms.
|
|
||||||
|
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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Weboria Proprietary Components
|
## Pending legal review before commercial distribution
|
||||||
|
|
||||||
The following ARGUS components are proprietary software developed by Weboria:
|
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
|
||||||
- ARGUS Core Platform
|
proprietary commercial license in `LICENSE` before shipping a release build.
|
||||||
- ARGUS Management Interface
|
|
||||||
- ARGUS Security Automation Engine
|
|
||||||
- ARGUS API Layer
|
|
||||||
- ARGUS Threat Analysis Components
|
|
||||||
- ARGUS Licensing System
|
|
||||||
- ARGUS Enterprise Features
|
|
||||||
- Weboria Integrations
|
|
||||||
|
|
||||||
These components are distributed under the ARGUS Commercial License.
|
|
||||||
|
|
||||||
Unauthorized copying, modification, redistribution, reverse engineering, or resale is
|
|
||||||
prohibited.
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWy1RyNllxNrNXsU1Ld/muB/EEpdg
|
|
||||||
uOPDHZfqYGC6eZgFT79LdG05syTHTFS84MMNcMN0pBikc3n9rwImKeEb+A==
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
# ARGUS backup — dumps the database and application data (certificates,
|
# ARGUS backup — dumps the database and application data (certificates,
|
||||||
# uploaded config) into a single, timestamped archive.
|
# uploaded config) into a single, timestamped archive.
|
||||||
#
|
#
|
||||||
# cd /opt/argus && ./backup.sh [output-directory]
|
# cd ~/argus && ./backup.sh [output-directory]
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
INSTALL_DIR="${ARGUS_INSTALL_DIR:-/opt/argus}"
|
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
|
||||||
OUT_DIR="${1:-$PWD}"
|
OUT_DIR="${1:-$PWD}"
|
||||||
STAMP="$(date +%Y%m%d-%H%M%S)"
|
STAMP="$(date +%Y%m%d-%H%M%S)"
|
||||||
ARCHIVE="${OUT_DIR}/argus-backup-${STAMP}.tar.gz"
|
ARCHIVE="${OUT_DIR}/argus-backup-${STAMP}.tar.gz"
|
||||||
@@ -24,7 +24,7 @@ bold "Backing up ARGUS..."
|
|||||||
WORKDIR="$(mktemp -d)"
|
WORKDIR="$(mktemp -d)"
|
||||||
trap 'rm -rf "$WORKDIR"' EXIT
|
trap 'rm -rf "$WORKDIR"' EXIT
|
||||||
|
|
||||||
docker compose exec -T db pg_dump -U postgres argus | gzip > "$WORKDIR/db.sql.gz"
|
docker compose exec -T db pg_dump -U "${DB_USER:-postgres}" "${DB_NAME:-argus}" | gzip > "$WORKDIR/db.sql.gz"
|
||||||
ok "Database dumped"
|
ok "Database dumped"
|
||||||
|
|
||||||
docker run --rm -v argus_api_data:/data -v "$WORKDIR":/backup alpine \
|
docker run --rm -v argus_api_data:/data -v "$WORKDIR":/backup alpine \
|
||||||
|
|||||||
+15
-86
@@ -11,18 +11,14 @@ services:
|
|||||||
max-file: "3"
|
max-file: "3"
|
||||||
environment:
|
environment:
|
||||||
TZ: ${TZ:-UTC}
|
TZ: ${TZ:-UTC}
|
||||||
# Internal database credentials, not customer-configurable — nothing
|
POSTGRES_USER: ${DB_USER:-postgres}
|
||||||
# outside this compose stack ever connects to Postgres directly, so
|
|
||||||
# there's no real reason to let these vary. Only the password (below)
|
|
||||||
# needs to be a real secret; the username/db name are just labels.
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
|
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
|
||||||
POSTGRES_DB: argus
|
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
|
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:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@@ -73,14 +69,6 @@ services:
|
|||||||
EXEC: 1
|
EXEC: 1
|
||||||
VERSION: 1
|
VERSION: 1
|
||||||
POST: 1
|
POST: 1
|
||||||
# Restart (stop/restart/kill on an *existing* container) only --
|
|
||||||
# create/prune stay denied by the haproxy template's own explicit
|
|
||||||
# deny-list regardless of POST, and start/stop stay off (ALLOW_START/
|
|
||||||
# ALLOW_STOP unset) since nothing needs them yet. This comment block's
|
|
||||||
# own claim of "restart" capability (above) was aspirational until
|
|
||||||
# this line existed -- appliance#43's Diagnostics recovery actions are
|
|
||||||
# the first real caller.
|
|
||||||
ALLOW_RESTARTS: 1
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ./docker-socket-proxy/haproxy.cfg.template:/usr/local/etc/haproxy/haproxy.cfg.template:ro
|
- ./docker-socket-proxy/haproxy.cfg.template:/usr/local/etc/haproxy/haproxy.cfg.template:ro
|
||||||
@@ -92,12 +80,7 @@ services:
|
|||||||
|
|
||||||
# Go API Server
|
# Go API Server
|
||||||
api:
|
api:
|
||||||
# ARGUS_VERSION is set by update.sh before `docker compose pull`, pinned
|
image: ghcr.io/alleyviper/argus-secure-api:latest
|
||||||
# to a specific already-fully-promoted version tag rather than the
|
|
||||||
# floating :latest (ADR-0051 -- argus-appliance repo). Manual/non-
|
|
||||||
# update.sh use (plain `docker compose pull`, no env var set) is
|
|
||||||
# unaffected: the default is :latest, identical to before this change.
|
|
||||||
image: git-cloud.weboria.eu/weboria/argus-api:${ARGUS_VERSION:-latest}
|
|
||||||
container_name: argus-api
|
container_name: argus-api
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
# Container hardening: no-new-privileges blocks setuid privilege
|
# Container hardening: no-new-privileges blocks setuid privilege
|
||||||
@@ -121,14 +104,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
TZ: ${TZ:-UTC}
|
TZ: ${TZ:-UTC}
|
||||||
PORT: "8080"
|
PORT: "8080"
|
||||||
# DATABASE_URL is not set here — the API builds it internally from
|
DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-argus}?sslmode=disable
|
||||||
# DB_PASSWORD using net/url (proper percent-encoding), instead of this
|
|
||||||
# file naively interpolating the password into a raw connection
|
|
||||||
# string. A raw '${DB_PASSWORD}@...' interpolation breaks outright if
|
|
||||||
# the password ever contains '@', ':', '/', or similar (confirmed
|
|
||||||
# live: an openssl-rand-base64-generated password containing '/'
|
|
||||||
# produced an unparseable URL and the API never became healthy).
|
|
||||||
DB_PASSWORD: ${DB_PASSWORD:-postgres}
|
|
||||||
REDIS_URL: redis://valkey:6379/0
|
REDIS_URL: redis://valkey:6379/0
|
||||||
ENVIRONMENT: ${ENVIRONMENT:-production}
|
ENVIRONMENT: ${ENVIRONMENT:-production}
|
||||||
NGINX_CONTAINER: argus-proxy
|
NGINX_CONTAINER: argus-proxy
|
||||||
@@ -143,19 +119,13 @@ services:
|
|||||||
API_HOST_PORT: ${API_HOST_PORT:-9080}
|
API_HOST_PORT: ${API_HOST_PORT:-9080}
|
||||||
API_HOST: ${API_HOST:-}
|
API_HOST: ${API_HOST:-}
|
||||||
# ANIS community intelligence — on by default against the public
|
# ANIS community intelligence — on by default against the public
|
||||||
# Community Edition hub (community tier, no signup required). There is
|
# Community Edition hub (empty license key = community tier, no signup
|
||||||
# no self-hosted-ANIS deployment model: the URL is fixed, not
|
# required). Set ANIS_URL to your own self-hosted ANIS instead if you
|
||||||
# customer-configurable. A license key upgrades the tier, but is only
|
# run one, or ANIS_ENABLED=false to turn this off entirely.
|
||||||
# ever set from Threat Intel -> ANIS Connection in the dashboard
|
ANIS_ENABLED: ${ANIS_ENABLED:-true}
|
||||||
# (write-only there — never round-trips back to the client), not from
|
ANIS_URL: ${ANIS_URL:-https://anis.weboria.eu}
|
||||||
# this file, so it's seeded empty (community tier) here.
|
ANIS_LICENSE_KEY: ${ANIS_LICENSE_KEY:-}
|
||||||
# (No ANIS_ADMIN_KEY or ANIS_SHARE_ATTACKERS here: both confirmed dead
|
ANIS_SHARE_ATTACKERS: ${ANIS_SHARE_ATTACKERS:-false}
|
||||||
# via grep, zero references anywhere in internal/config.go or the ANIS
|
|
||||||
# client. A prior version of this file carried ANIS_ADMIN_KEY and
|
|
||||||
# falsely claimed ARGUS "fails closed at startup" without it.)
|
|
||||||
ANIS_ENABLED: ${ANIS_ENABLED:-true}
|
|
||||||
ANIS_URL: https://anis.weboria.eu
|
|
||||||
ANIS_LICENSE_KEY: ""
|
|
||||||
DNS_SECURITY_LISTEN_ADDR: ":53"
|
DNS_SECURITY_LISTEN_ADDR: ":53"
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:${API_HOST_PORT:-9080}:8080"
|
- "127.0.0.1:${API_HOST_PORT:-9080}:8080"
|
||||||
@@ -183,50 +153,9 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
start_period: 360s
|
start_period: 360s
|
||||||
|
|
||||||
# api-watchdog (2026-09-12, see docker/watchdog/README.md in the
|
|
||||||
# argus-appliance repo): closes a real production gap -- `restart:
|
|
||||||
# unless-stopped` above only ever reacts to the api container actually
|
|
||||||
# EXITING. A real incident (CT 101, 2026-09-11/12) had it hang/wedge
|
|
||||||
# internally for ~11 hours while `docker ps` still reported it `Up`; the
|
|
||||||
# HEALTHCHECK above correctly kept detecting the hang the entire time,
|
|
||||||
# but nothing was watching that status and acting on it. This polls the
|
|
||||||
# same already-scoped docker-socket-proxy (zero new grants) and restarts
|
|
||||||
# argus-api after several consecutive unhealthy checks.
|
|
||||||
#
|
|
||||||
# Built locally rather than pulled/pinned/cosign-verified like api/ui/
|
|
||||||
# nginx above: there is not yet a released, signed argus-api-watchdog
|
|
||||||
# image in the registry (tracked as a follow-up to extend ADR-0051's
|
|
||||||
# verification set to a 4th component). The build context ships in this
|
|
||||||
# repo so it stays fully reproducible from source, not a hand-built
|
|
||||||
# local image nobody can regenerate.
|
|
||||||
api-watchdog:
|
|
||||||
build:
|
|
||||||
context: ./watchdog
|
|
||||||
container_name: argus-api-watchdog
|
|
||||||
restart: unless-stopped
|
|
||||||
security_opt:
|
|
||||||
- "no-new-privileges:true"
|
|
||||||
cap_drop:
|
|
||||||
- ALL
|
|
||||||
logging:
|
|
||||||
driver: "json-file"
|
|
||||||
options:
|
|
||||||
max-size: "10m"
|
|
||||||
max-file: "3"
|
|
||||||
environment:
|
|
||||||
WATCHDOG_TARGET_CONTAINER: argus-api
|
|
||||||
WATCHDOG_INTERVAL_SECONDS: "30"
|
|
||||||
WATCHDOG_UNHEALTHY_THRESHOLD: "5"
|
|
||||||
depends_on:
|
|
||||||
docker-socket-proxy:
|
|
||||||
condition: service_started
|
|
||||||
api:
|
|
||||||
condition: service_started
|
|
||||||
|
|
||||||
# React UI (Admin Panel), served over HTTPS
|
# React UI (Admin Panel), served over HTTPS
|
||||||
ui:
|
ui:
|
||||||
# See the api service's own comment above -- same ADR-0051 pinning.
|
image: ghcr.io/alleyviper/argus-secure-ui:latest
|
||||||
image: git-cloud.weboria.eu/weboria/argus-ui:${ARGUS_VERSION:-latest}
|
|
||||||
container_name: argus-ui
|
container_name: argus-ui
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
security_opt:
|
security_opt:
|
||||||
@@ -252,8 +181,7 @@ services:
|
|||||||
# Nginx reverse proxy — host network mode, so real client IPs are visible
|
# Nginx reverse proxy — host network mode, so real client IPs are visible
|
||||||
# directly without needing PROXY protocol.
|
# directly without needing PROXY protocol.
|
||||||
nginx:
|
nginx:
|
||||||
# See the api service's own comment above -- same ADR-0051 pinning.
|
image: ghcr.io/alleyviper/argus-secure-nginx:latest
|
||||||
image: git-cloud.weboria.eu/weboria/argus-proxy:${ARGUS_VERSION:-latest}
|
|
||||||
container_name: argus-proxy
|
container_name: argus-proxy
|
||||||
restart: always
|
restart: always
|
||||||
network_mode: host
|
network_mode: host
|
||||||
@@ -269,6 +197,7 @@ services:
|
|||||||
max-file: "5"
|
max-file: "5"
|
||||||
environment:
|
environment:
|
||||||
TZ: ${TZ:-UTC}
|
TZ: ${TZ:-UTC}
|
||||||
|
UI_PORT: ${UI_PORT:-81}
|
||||||
ulimits:
|
ulimits:
|
||||||
nofile:
|
nofile:
|
||||||
soft: 65535
|
soft: 65535
|
||||||
|
|||||||
-64
@@ -1,64 +0,0 @@
|
|||||||
# ARGUS Enterprise Web Security Platform — Installation Configuration
|
|
||||||
#
|
|
||||||
# install.sh generates this file for you automatically, including a secure
|
|
||||||
# database password. Most installs need nothing beyond the defaults below.
|
|
||||||
#
|
|
||||||
# Everything else — WAF policy, DNS security, threat intelligence tuning,
|
|
||||||
# proxy hosts, users, security policies — is configured from the ARGUS
|
|
||||||
# dashboard after your first login, not from this file.
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# Database (do not edit)
|
|
||||||
# ===========================================
|
|
||||||
# install.sh replaces this with a securely generated value on first install
|
|
||||||
# and never touches it again. There's nothing to fill in here yourself.
|
|
||||||
DB_PASSWORD=change-me-in-production
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# Timezone
|
|
||||||
# ===========================================
|
|
||||||
# Should match the timezone you select in the dashboard's first-run setup
|
|
||||||
# wizard (Regional Settings) — the wizard checks for a mismatch and will
|
|
||||||
# warn you if this file and your dashboard selection disagree.
|
|
||||||
TZ=UTC
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# Threat Intelligence (ANIS)
|
|
||||||
# ===========================================
|
|
||||||
# ARGUS connects to ANIS, Weboria's threat-intelligence network, by default
|
|
||||||
# — no license key needed for the free Community tier. There is no
|
|
||||||
# self-hosted-ANIS deployment model, so there's no URL to configure here.
|
|
||||||
# A license key upgrades the tier, but is only ever set from
|
|
||||||
# Threat Intel -> ANIS Connection in the dashboard — never from this file.
|
|
||||||
ANIS_ENABLED=true
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# Network Ports
|
|
||||||
# ===========================================
|
|
||||||
# Only change these if the defaults conflict with something else already
|
|
||||||
# running on this host.
|
|
||||||
|
|
||||||
# Admin panel port (default: 81)
|
|
||||||
# UI_PORT=81
|
|
||||||
|
|
||||||
# Reverse-proxy listen ports — change if 80/443 are already in use
|
|
||||||
# NGINX_HTTP_PORT=8080
|
|
||||||
# NGINX_HTTPS_PORT=8443
|
|
||||||
|
|
||||||
# Internal port used for nginx<->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 only if you intend to route real client
|
|
||||||
# DNS traffic through ARGUS. Never 0.0.0.0 unless this host is on a fully
|
|
||||||
# trusted network — an open forwarding resolver is exactly the profile
|
|
||||||
# abused for DNS amplification attacks against third parties.
|
|
||||||
# DNS_LISTEN_HOST=127.0.0.1
|
|
||||||
|
|
||||||
# ===========================================
|
|
||||||
# Advanced / Troubleshooting
|
|
||||||
# ===========================================
|
|
||||||
# Only needed on some NAS platforms (e.g. Synology DSM) where Docker's API
|
|
||||||
# version needs to be pinned manually. Leave commented out otherwise.
|
|
||||||
# DOCKER_API_VERSION=1.41
|
|
||||||
+2
-2
@@ -2,11 +2,11 @@
|
|||||||
#
|
#
|
||||||
# ARGUS health check — reports the status of every component.
|
# ARGUS health check — reports the status of every component.
|
||||||
#
|
#
|
||||||
# cd /opt/argus && ./healthcheck.sh
|
# cd ~/argus && ./healthcheck.sh
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
INSTALL_DIR="${ARGUS_INSTALL_DIR:-/opt/argus}"
|
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
|
||||||
|
|
||||||
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
|
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
|
||||||
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
|
ok() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
|
||||||
|
|||||||
+19
-76
@@ -1,20 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# ARGUS Enterprise Web Security Platform — installer. Fetches the production
|
# ARGUS installer — fetches the production compose stack, generates secrets,
|
||||||
# compose stack, generates secrets, starts the platform, and prints your
|
# starts the platform, and prints your admin credentials.
|
||||||
# admin credentials.
|
|
||||||
#
|
#
|
||||||
# curl -fsSL https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/install.sh | bash
|
# curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/install.sh | bash
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
REPO_RAW_BASE="https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main"
|
REPO_RAW_BASE="https://raw.githubusercontent.com/alleyviper/argus/main"
|
||||||
INSTALL_DIR="${ARGUS_INSTALL_DIR:-/opt/argus}"
|
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
|
||||||
NETWORK_NAME="argus-network"
|
NETWORK_NAME="argus-network"
|
||||||
# Production images are pulled from Weboria's own registry, built into
|
|
||||||
# git-cloud.weboria.eu (Gitea's container registry) — docker-compose.yml's
|
|
||||||
# image: lines reference git-cloud.weboria.eu/weboria/argus-{api,ui,proxy}
|
|
||||||
# directly. Anonymous docker pull works with no login required.
|
|
||||||
|
|
||||||
# ── Output helpers ────────────────────────────────────────────────────────────
|
# ── Output helpers ────────────────────────────────────────────────────────────
|
||||||
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
|
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
|
||||||
@@ -54,29 +49,16 @@ for script in update.sh uninstall.sh healthcheck.sh backup.sh restore.sh; do
|
|||||||
fetch "$script" "$script"
|
fetch "$script" "$script"
|
||||||
chmod +x "$script"
|
chmod +x "$script"
|
||||||
done
|
done
|
||||||
# The repo stores this as env.example (no leading dot) rather than .env.example
|
|
||||||
# -- Weboria's own WAF blocks any request path matching .env* as a standard
|
|
||||||
# credential-harvesting-probe rule, which also caught this legitimate static
|
|
||||||
# file when served from Gitea. Renaming sidesteps it without needing a WAF
|
|
||||||
# exception. Saved locally as .env either way.
|
|
||||||
if [ ! -f .env ]; then
|
if [ ! -f .env ]; then
|
||||||
fetch "env.example" ".env"
|
fetch ".env.example" ".env"
|
||||||
ok "Fetched deployment files into $INSTALL_DIR"
|
ok "Fetched deployment files into $INSTALL_DIR"
|
||||||
else
|
else
|
||||||
ok "Fetched compose file (kept your existing .env)"
|
ok "Fetched compose file (kept your existing .env)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 4. Generate secrets (first install only — never overwrite an existing .env) ──
|
# ── 4. Generate secrets (first install only — never overwrite an existing .env) ──
|
||||||
# hex, not base64: base64 can produce '/', '+', '=', any of which breaks the
|
|
||||||
# raw postgres://user:PASSWORD@host connection string if it ever ends up
|
|
||||||
# interpolated unescaped (confirmed live on the first real production
|
|
||||||
# install — see config.go's buildDatabaseURL for the second, independent
|
|
||||||
# defensive layer this pairs with). Hex output is always [0-9a-f], which
|
|
||||||
# can never produce that class of bug, at 4 bits/char vs base64's ~6 —
|
|
||||||
# 32 hex chars (128 bits) is used in place of 24 base64 chars (~144 bits)
|
|
||||||
# to keep entropy comparable rather than matching digit-count cosmetically.
|
|
||||||
if grep -q '^DB_PASSWORD=change-me-in-production' .env 2>/dev/null; then
|
if grep -q '^DB_PASSWORD=change-me-in-production' .env 2>/dev/null; then
|
||||||
DB_PASSWORD="$(openssl rand -hex 32)"
|
DB_PASSWORD="$(openssl rand -base64 24 | tr -d '\n')"
|
||||||
sed -i.bak "s#^DB_PASSWORD=.*#DB_PASSWORD=${DB_PASSWORD}#" .env && rm -f .env.bak
|
sed -i.bak "s#^DB_PASSWORD=.*#DB_PASSWORD=${DB_PASSWORD}#" .env && rm -f .env.bak
|
||||||
ok "Generated a secure database password"
|
ok "Generated a secure database password"
|
||||||
fi
|
fi
|
||||||
@@ -115,63 +97,24 @@ if [ "$READY" != "true" ]; then
|
|||||||
fi
|
fi
|
||||||
ok "ARGUS is up"
|
ok "ARGUS is up"
|
||||||
|
|
||||||
# ── 8. Rotate the default admin credentials ───────────────────────────────────
|
# ── 8. Summary ─────────────────────────────────────────────────────────────────
|
||||||
ADMIN_USER="admin"
|
# Credentials are no longer auto-rotated here — a fresh install always lands
|
||||||
ADMIN_PASSWORD="Ax9!$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 20)"
|
# on admin/admin, and ARGUS itself forces a mandatory First-Run Setup Wizard
|
||||||
API_BASE="http://127.0.0.1:${API_HOST_PORT}/api/v1"
|
# on that first login (credential change, regional settings, guided feature
|
||||||
|
# configuration, installation validation) before the dashboard is reachable.
|
||||||
LOGIN_RESP="$(curl -fsS -X POST "${API_BASE}/auth/login" \
|
# Pre-rotating here just made that flow inconsistent across install paths and
|
||||||
-H 'Content-Type: application/json' \
|
# risked silently leaving defaults active if the rotation call failed.
|
||||||
-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="$(grep -oP '^UI_PORT=\K.*' .env 2>/dev/null || true)"
|
||||||
UI_PORT="${UI_PORT:-81}"
|
UI_PORT="${UI_PORT:-81}"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
bold "ARGUS Enterprise Web Security Platform has been successfully installed."
|
bold "ARGUS is installed and running."
|
||||||
echo
|
echo
|
||||||
info "Access URL: https://$(hostname -I 2>/dev/null | awk '{print $1}' || echo localhost):${UI_PORT} (accept the self-signed certificate)"
|
info "Admin Panel: https://localhost:${UI_PORT} (accept the self-signed certificate)"
|
||||||
if [ "$CREDS_ROTATED" = "true" ]; then
|
info "Default Login: admin / admin"
|
||||||
info "Username: ${ADMIN_USER}"
|
warn "You will be required to set a real username/password and complete the First-Run Setup Wizard on first login."
|
||||||
info "Password: ${ADMIN_PASSWORD}"
|
|
||||||
warn "Save this password now — it is not stored anywhere and cannot be recovered."
|
|
||||||
else
|
|
||||||
info "Log in with your existing admin credentials."
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
bold "Next steps:"
|
|
||||||
info "1. Open the administration panel at the Access URL above."
|
|
||||||
if [ "$CREDS_ROTATED" != "true" ]; then
|
|
||||||
info "2. Complete the first-run setup wizard (regional settings, guided protection checklist)."
|
|
||||||
info "3. Change the initial administrator password if you haven't already."
|
|
||||||
else
|
|
||||||
info "2. Complete the first-run setup wizard — regional settings and a guided review of every"
|
|
||||||
info " protection module (your admin password was already rotated above)."
|
|
||||||
fi
|
|
||||||
info "4. Configure domains and reverse proxy hosts under Proxy Hosts."
|
|
||||||
info "5. Review the ANIS threat intelligence connection under Threat Intel — enabled by"
|
|
||||||
info " default against the public Community Edition hub, no license key required."
|
|
||||||
info "6. Review security policies in the Security Policy Center."
|
|
||||||
echo
|
echo
|
||||||
info "Configuration: ${INSTALL_DIR}/.env and ${INSTALL_DIR}/docker-compose.yml"
|
info "Configuration: ${INSTALL_DIR}/.env and ${INSTALL_DIR}/docker-compose.yml"
|
||||||
info "Upgrade: cd ${INSTALL_DIR} && docker compose pull && docker compose up -d"
|
info "Upgrade: cd ${INSTALL_DIR} && docker compose pull && docker compose up -d"
|
||||||
info "Backup: cd ${INSTALL_DIR} && ./backup.sh"
|
info "Backups: see https://github.com/alleyviper/argus#backups"
|
||||||
info "Docs/support: see README.md in this directory"
|
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{"version": "3.79.105", "released_at": "2026-09-12", "images": {"api": "sha256:61370698e111b329a7972313a9c1ac546696cf14153cb5d07062a5916baeb368", "ui": "sha256:e038ec1f908dac77abc6ee555c017d90c9e1f112794d1ea2b83532ce83335a84", "proxy": "sha256:fe2d0e98356198469e314c5b5afd4ff89bf0b099da8ef7e5f7db153677f99c13"}}
|
|
||||||
+3
-3
@@ -5,11 +5,11 @@
|
|||||||
# that already has data on it, uninstall.sh --remove-data && install.sh
|
# that already has data on it, uninstall.sh --remove-data && install.sh
|
||||||
# first, then run this.
|
# first, then run this.
|
||||||
#
|
#
|
||||||
# cd /opt/argus && ./restore.sh argus-backup-20260719-120000.tar.gz
|
# cd ~/argus && ./restore.sh argus-backup-20260719-120000.tar.gz
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
INSTALL_DIR="${ARGUS_INSTALL_DIR:-/opt/argus}"
|
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
|
||||||
ARCHIVE="${1:-}"
|
ARCHIVE="${1:-}"
|
||||||
|
|
||||||
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
|
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
|
||||||
@@ -38,7 +38,7 @@ bold "Stopping the API (keeping the database up)..."
|
|||||||
docker compose stop api ui nginx >/dev/null
|
docker compose stop api ui nginx >/dev/null
|
||||||
|
|
||||||
bold "Restoring database..."
|
bold "Restoring database..."
|
||||||
gunzip -c "$WORKDIR/db.sql.gz" | docker compose exec -T db psql -U postgres argus >/dev/null
|
gunzip -c "$WORKDIR/db.sql.gz" | docker compose exec -T db psql -U "${DB_USER:-postgres}" "${DB_NAME:-argus}" >/dev/null
|
||||||
ok "Database restored"
|
ok "Database restored"
|
||||||
|
|
||||||
bold "Restoring application data..."
|
bold "Restoring application data..."
|
||||||
|
|||||||
+2
-2
@@ -6,13 +6,13 @@
|
|||||||
# it only stops and removes containers, so a plain re-install (install.sh)
|
# it only stops and removes containers, so a plain re-install (install.sh)
|
||||||
# picks up right where you left off.
|
# picks up right where you left off.
|
||||||
#
|
#
|
||||||
# cd /opt/argus && ./uninstall.sh # containers only, data kept
|
# cd ~/argus && ./uninstall.sh # containers only, data kept
|
||||||
# ./uninstall.sh --remove-data # also destroys all data (asks to confirm)
|
# ./uninstall.sh --remove-data # also destroys all data (asks to confirm)
|
||||||
# ./uninstall.sh --remove-data --yes # non-interactive, for scripted use
|
# ./uninstall.sh --remove-data --yes # non-interactive, for scripted use
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
INSTALL_DIR="${ARGUS_INSTALL_DIR:-/opt/argus}"
|
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
|
||||||
REMOVE_DATA=false
|
REMOVE_DATA=false
|
||||||
ASSUME_YES=false
|
ASSUME_YES=false
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# ARGUS Enterprise Web Security Platform — updater.
|
# ARGUS updater — pulls the latest published images and restarts the stack.
|
||||||
#
|
#
|
||||||
# cd /opt/argus && ./update.sh
|
# cd ~/argus && ./update.sh
|
||||||
# (or, if you don't have it locally yet)
|
# (or, if you don't have it locally yet)
|
||||||
# curl -fsSL https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/update.sh | bash
|
# curl -fsSL https://raw.githubusercontent.com/alleyviper/argus/main/update.sh | bash
|
||||||
#
|
#
|
||||||
# Requires: docker (with the buildx plugin, bundled by default in current
|
|
||||||
# Docker/docker.io packages) and cosign (https://docs.sigstore.dev/cosign/
|
|
||||||
# installation/) on PATH. Both are used only to VERIFY what's about to be
|
|
||||||
# installed, before anything is pulled or restarted -- see below.
|
|
||||||
#
|
|
||||||
# ADR-0051 (argus-appliance repo): this pulls a specific, pinned version --
|
|
||||||
# never :latest. :latest is three independently-mutable registry tags with
|
|
||||||
# no cross-image transaction; a version resolved from latest-version.json
|
|
||||||
# is a single atomically-committed pointer that the release pipeline only
|
|
||||||
# ever writes AFTER every image it names has finished promoting AND passed
|
|
||||||
# scripts/verify-release.sh's full release-gate (argus-appliance repo's
|
|
||||||
# release.yml `release-gate` job) -- so the mere presence of a version in
|
|
||||||
# this manifest already means it passed CI-side verification. This script
|
|
||||||
# does NOT re-trust that alone: 2026-08-15 incident (see argus-appliance's
|
|
||||||
# docs/adr/0051-...md incident note) established that neither this script
|
|
||||||
# nor the Appliance GUI may ever treat a version as installable without
|
|
||||||
# independently re-verifying it here, at the point of actual install --
|
|
||||||
# a bug in the gate, a compromised pointer, or a torn registry push must
|
|
||||||
# still be caught here, not merely trusted upstream.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
INSTALL_DIR="${ARGUS_INSTALL_DIR:-/opt/argus}"
|
INSTALL_DIR="${ARGUS_INSTALL_DIR:-$HOME/argus}"
|
||||||
MANIFEST_URL="https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/latest-version.json"
|
|
||||||
COSIGN_PUBKEY_URL="https://git-cloud.weboria.eu/weboria/argus-installer/raw/branch/main/argus-images.pub"
|
|
||||||
IMAGE_PREFIX="git-cloud.weboria.eu/weboria/argus"
|
|
||||||
|
|
||||||
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
|
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
|
||||||
info() { printf ' %s\n' "$1"; }
|
info() { printf ' %s\n' "$1"; }
|
||||||
@@ -49,99 +27,7 @@ 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")"
|
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}"
|
info "Current version: ${BEFORE_VERSION}"
|
||||||
|
|
||||||
bold "Resolving target version..."
|
bold "Pulling latest images..."
|
||||||
MANIFEST="$(curl -fsS "$MANIFEST_URL" 2>/dev/null)" \
|
|
||||||
|| fail "Could not reach the update manifest ($MANIFEST_URL). Nothing was changed -- your installation is untouched. Check your network connection and try again."
|
|
||||||
|
|
||||||
TARGET_VERSION="$(printf '%s' "$MANIFEST" | grep -oP '"version"\s*:\s*"\K[^"]+' || true)"
|
|
||||||
[ -n "$TARGET_VERSION" ] || fail "The update manifest was malformed (no 'version' field). Nothing was changed -- your installation is untouched."
|
|
||||||
info "Target version: ${TARGET_VERSION}"
|
|
||||||
|
|
||||||
if [ "$BEFORE_VERSION" = "$TARGET_VERSION" ]; then
|
|
||||||
ok "Already up to date (${TARGET_VERSION})."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Fail-safe re-verification, BEFORE anything is pulled or restarted.
|
|
||||||
#
|
|
||||||
# The manifest's mere existence already implies it passed the CI-side
|
|
||||||
# release gate (see header comment) -- but this script does not stop
|
|
||||||
# there. It independently re-derives, from the registry and the committed
|
|
||||||
# public key alone (no Gitea credentials needed or used), that what it's
|
|
||||||
# about to install is really what the release pipeline promoted: the
|
|
||||||
# live image digest must match what the manifest recorded, AND that
|
|
||||||
# digest must carry a valid cosign signature. Any failure, missing tool,
|
|
||||||
# missing data, or ambiguity aborts here -- nothing is pulled, nothing is
|
|
||||||
# restarted, the running installation is untouched.
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
bold "Verifying ${TARGET_VERSION} before installing anything..."
|
|
||||||
|
|
||||||
command -v docker >/dev/null 2>&1 || fail "docker not found on PATH -- cannot verify the release. Nothing was changed."
|
|
||||||
command -v cosign >/dev/null 2>&1 || fail "cosign not found on PATH (see https://docs.sigstore.dev/cosign/installation/) -- signature verification is required before install, not optional. Nothing was changed."
|
|
||||||
|
|
||||||
COSIGN_PUBKEY_FILE="$(mktemp)"
|
|
||||||
trap 'rm -f "$COSIGN_PUBKEY_FILE"' EXIT
|
|
||||||
curl -fsS "$COSIGN_PUBKEY_URL" -o "$COSIGN_PUBKEY_FILE" 2>/dev/null \
|
|
||||||
|| fail "Could not fetch the image signing public key ($COSIGN_PUBKEY_URL). Nothing was changed -- your installation is untouched."
|
|
||||||
[ -s "$COSIGN_PUBKEY_FILE" ] || fail "Fetched signing public key was empty. Nothing was changed."
|
|
||||||
|
|
||||||
manifest_image_digest() {
|
|
||||||
printf '%s' "$MANIFEST" | python3 -c "
|
|
||||||
import json, sys
|
|
||||||
try:
|
|
||||||
print(json.load(sys.stdin).get('images', {}).get('$1', ''))
|
|
||||||
except Exception:
|
|
||||||
print('')
|
|
||||||
" 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY_FAILED=false
|
|
||||||
for svc_component in "api:api" "ui:ui" "nginx:proxy"; do
|
|
||||||
svc="${svc_component%%:*}"
|
|
||||||
component="${svc_component##*:}"
|
|
||||||
IMG="${IMAGE_PREFIX}-${component}:${TARGET_VERSION}"
|
|
||||||
|
|
||||||
RECORDED_DIGEST="$(manifest_image_digest "$component")"
|
|
||||||
if [ -z "$RECORDED_DIGEST" ] || [ "$RECORDED_DIGEST" = "null" ]; then
|
|
||||||
info " ✗ ${svc}: manifest has no recorded digest for ${TARGET_VERSION} -- cannot verify"
|
|
||||||
VERIFY_FAILED=true
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
ACTUAL_DIGEST="$(docker buildx imagetools inspect "$IMG" --format '{{json .Manifest.Digest}}' 2>/dev/null | tr -d '"' || true)"
|
|
||||||
if [ -z "$ACTUAL_DIGEST" ] || [ "$ACTUAL_DIGEST" = "null" ]; then
|
|
||||||
info " ✗ ${svc}: could not resolve ${IMG} in the registry"
|
|
||||||
VERIFY_FAILED=true
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [ "$ACTUAL_DIGEST" != "$RECORDED_DIGEST" ]; then
|
|
||||||
info " ✗ ${svc}: registry digest ${ACTUAL_DIGEST} does not match manifest-recorded ${RECORDED_DIGEST}"
|
|
||||||
VERIFY_FAILED=true
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! cosign verify --key "$COSIGN_PUBKEY_FILE" "${IMAGE_PREFIX}-${component}@${ACTUAL_DIGEST}" >/dev/null 2>&1; then
|
|
||||||
info " ✗ ${svc}: signature verification failed for ${ACTUAL_DIGEST}"
|
|
||||||
VERIFY_FAILED=true
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
info " ✓ ${svc}: digest ${ACTUAL_DIGEST} matches manifest and signature verified"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$VERIFY_FAILED" = "true" ]; then
|
|
||||||
fail "Release verification failed for ${TARGET_VERSION} -- treating this update as unavailable. Nothing was pulled or changed. This is not a transient error to retry blindly; investigate before trying again."
|
|
||||||
fi
|
|
||||||
ok "Release ${TARGET_VERSION} independently verified (digest + signature) for every service."
|
|
||||||
|
|
||||||
# Pinned, not :latest -- see the header comment. Exported only for this
|
|
||||||
# script's own `docker compose` invocations below, not written to .env, so
|
|
||||||
# a later manual `docker compose pull` by the operator still defaults to
|
|
||||||
# :latest exactly as it always has.
|
|
||||||
export ARGUS_VERSION="$TARGET_VERSION"
|
|
||||||
|
|
||||||
bold "Pulling ${TARGET_VERSION} (pinned, not :latest)..."
|
|
||||||
docker compose pull
|
docker compose pull
|
||||||
|
|
||||||
bold "Restarting..."
|
bold "Restarting..."
|
||||||
@@ -163,38 +49,9 @@ if [ "$READY" != "true" ]; then
|
|||||||
fi
|
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")"
|
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}"
|
||||||
# The API being healthy only proves the API container itself came up on
|
if [ "$BEFORE_VERSION" != "$AFTER_VERSION" ]; then
|
||||||
# some version -- it says nothing about whether the UI and proxy images
|
info "Updated: ${BEFORE_VERSION} → ${AFTER_VERSION}"
|
||||||
# actually match. A torn pull (network hiccup mid-pull, disk full on one
|
else
|
||||||
# image, etc.) would otherwise be reported as a clean success. Check every
|
info "Already up to date."
|
||||||
# service's actual running image tag against the target explicitly.
|
|
||||||
bold "Verifying every service is running ${TARGET_VERSION}..."
|
|
||||||
INCONSISTENT=false
|
|
||||||
for svc in api ui nginx; do
|
|
||||||
CONTAINER="$(docker compose ps -q "$svc" 2>/dev/null || true)"
|
|
||||||
if [ -z "$CONTAINER" ]; then
|
|
||||||
info " ✗ ${svc}: container not found"
|
|
||||||
INCONSISTENT=true
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
RUNNING_IMAGE="$(docker inspect --format '{{.Config.Image}}' "$CONTAINER" 2>/dev/null || echo "")"
|
|
||||||
RUNNING_TAG="${RUNNING_IMAGE##*:}"
|
|
||||||
if [ "$RUNNING_TAG" = "$TARGET_VERSION" ]; then
|
|
||||||
info " ✓ ${svc}: ${RUNNING_TAG}"
|
|
||||||
else
|
|
||||||
info " ✗ ${svc}: ${RUNNING_TAG:-unknown} (expected ${TARGET_VERSION})"
|
|
||||||
INCONSISTENT=true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$INCONSISTENT" = "true" ]; then
|
|
||||||
fail "Update did not complete cleanly -- not every service is running ${TARGET_VERSION}. Your services were NOT automatically rolled back; investigate (docker compose ps, docker compose logs) before retrying."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$AFTER_VERSION" != "$TARGET_VERSION" ]; then
|
|
||||||
fail "Every service's image is ${TARGET_VERSION}, but the API's own /health still reports ${AFTER_VERSION} -- it may not have finished restarting. Check: docker compose -f $INSTALL_DIR/docker-compose.yml logs api"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ok "ARGUS is healthy and every service is confirmed running ${AFTER_VERSION}"
|
|
||||||
info "Updated: ${BEFORE_VERSION} → ${AFTER_VERSION}"
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
FROM alpine:3.20
|
|
||||||
|
|
||||||
RUN apk add --no-cache curl jq
|
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
USER nobody
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# api-watchdog
|
|
||||||
|
|
||||||
Recovers from a real production incident class (2026-09-11/12, CT 101): the `api` container's
|
|
||||||
process hung/wedged internally while `docker ps` still reported it `Up` -- it never actually
|
|
||||||
exited, so Docker's own `restart: unless-stopped` policy (which only reacts to a container
|
|
||||||
exiting) never fired. The container's `HEALTHCHECK` (`wget --spider http://localhost:8080/health`)
|
|
||||||
correctly kept detecting the hang the entire time -- `docker inspect`'s `.State.Health.Status`
|
|
||||||
genuinely flipped to `unhealthy` -- but nothing was watching that status and acting on it, so the
|
|
||||||
outage lasted roughly 11 hours until a human happened to notice and ran `docker restart` by hand.
|
|
||||||
|
|
||||||
## What it does
|
|
||||||
|
|
||||||
Polls `docker-socket-proxy` (the same already-existing, scoped Docker API proxy the `api` service
|
|
||||||
itself uses -- see `docker/docker-socket-proxy/README.md`) for `argus-api`'s health status every
|
|
||||||
`WATCHDOG_INTERVAL_SECONDS` (default 30s). After `WATCHDOG_UNHEALTHY_THRESHOLD` (default 5)
|
|
||||||
*consecutive* `unhealthy` readings (~2.5 minutes sustained, not one blip), it issues
|
|
||||||
`POST /containers/argus-api/restart` through the same proxy and resets its counter. A `healthy`
|
|
||||||
reading at any point resets the counter immediately.
|
|
||||||
|
|
||||||
Deliberately talks to `docker-socket-proxy`, never a raw `/var/run/docker.sock` mount -- zero new
|
|
||||||
grants needed (`CONTAINERS=1`/`ALLOW_RESTARTS=1` are already on for the `api` service's own
|
|
||||||
diagnostics recovery actions), and this container never gets docker.sock access at all.
|
|
||||||
|
|
||||||
## Why not just shorten `retries`/rely on `restart: unless-stopped` alone
|
|
||||||
|
|
||||||
`restart: unless-stopped` restarts a container Docker itself observes as exited -- it has no
|
|
||||||
concept of "unhealthy," and this incident's whole failure mode was a container that never
|
|
||||||
exited. Docker Engine does not ship a built-in "restart on unhealthy" action; some setups solve
|
|
||||||
this with the third-party `willfarrell/autoheal` image, but that requires mounting the real
|
|
||||||
docker.sock into a new container. Reusing the already-present, already-audited
|
|
||||||
`docker-socket-proxy` instead keeps this fix inside ARGUS's existing security boundary.
|
|
||||||
|
|
||||||
## Tuning
|
|
||||||
|
|
||||||
- `WATCHDOG_TARGET_CONTAINER` (default `argus-api`)
|
|
||||||
- `WATCHDOG_INTERVAL_SECONDS` (default `30`, matches the api healthcheck's own interval)
|
|
||||||
- `WATCHDOG_UNHEALTHY_THRESHOLD` (default `5`) -- consecutive unhealthy checks before restarting
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Sandbox-tested (`docker-compose.local-sandbox.yml`) by temporarily forcing the `api` service's
|
|
||||||
healthcheck to always fail (`test: ["CMD", "false"]`), confirming: the watchdog logs 5 consecutive
|
|
||||||
`unhealthy` checks, then a real `POST .../restart` through the proxy, and `argus-api`'s container
|
|
||||||
start time visibly advances. Reverted the forced-failure healthcheck override afterward -- this
|
|
||||||
watchdog is not itself part of that sandbox scenario's cleanup.
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# api-watchdog: closes a real gap found during a 2026-09-11/12 production
|
|
||||||
# incident (see docker/watchdog/README.md) -- Docker's own `restart:
|
|
||||||
# unless-stopped` policy only ever acts on a container actually EXITING; it
|
|
||||||
# does nothing for a container that hangs/wedges while `docker ps` still
|
|
||||||
# reports it "Up". The api service's HEALTHCHECK (wget against /health)
|
|
||||||
# correctly kept detecting the hang for the incident's entire ~11-hour
|
|
||||||
# duration -- `docker inspect`'s `.State.Health.Status` genuinely flips to
|
|
||||||
# "unhealthy" -- but nothing was ever watching that status and acting on it,
|
|
||||||
# so the outage went unrecovered until a human happened to notice and ran
|
|
||||||
# `docker restart` by hand.
|
|
||||||
#
|
|
||||||
# Talks to the EXISTING docker-socket-proxy (already scoped with
|
|
||||||
# CONTAINERS=1 + ALLOW_RESTARTS=1 for the api service's own diagnostics
|
|
||||||
# recovery actions -- see docker/docker-socket-proxy/README.md) rather than
|
|
||||||
# mounting the raw host docker.sock into a new container: zero new grants
|
|
||||||
# needed, no new attack surface, same security boundary the api service
|
|
||||||
# itself already relies on.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
DOCKER_API="${WATCHDOG_DOCKER_API:-http://docker-socket-proxy:2375}"
|
|
||||||
TARGET="${WATCHDOG_TARGET_CONTAINER:-argus-api}"
|
|
||||||
INTERVAL="${WATCHDOG_INTERVAL_SECONDS:-30}"
|
|
||||||
THRESHOLD="${WATCHDOG_UNHEALTHY_THRESHOLD:-5}"
|
|
||||||
|
|
||||||
log() {
|
|
||||||
printf '[watchdog] %s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
log "starting -- target=$TARGET interval=${INTERVAL}s threshold=$THRESHOLD checks (~$((INTERVAL * THRESHOLD))s sustained unhealthy before acting)"
|
|
||||||
|
|
||||||
consecutive=0
|
|
||||||
while true; do
|
|
||||||
sleep "$INTERVAL"
|
|
||||||
|
|
||||||
body="$(curl -sf --max-time 10 "$DOCKER_API/containers/$TARGET/json" 2>/dev/null || true)"
|
|
||||||
if [ -z "$body" ]; then
|
|
||||||
log "WARNING: could not reach docker-socket-proxy or inspect $TARGET -- leaving consecutive count unchanged"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
status="$(printf '%s' "$body" | jq -r '.State.Health.Status // "none"' 2>/dev/null || echo "parse_error")"
|
|
||||||
|
|
||||||
case "$status" in
|
|
||||||
healthy)
|
|
||||||
if [ "$consecutive" -gt 0 ]; then
|
|
||||||
log "recovered on its own after $consecutive unhealthy check(s) -- resetting counter"
|
|
||||||
fi
|
|
||||||
consecutive=0
|
|
||||||
;;
|
|
||||||
unhealthy)
|
|
||||||
consecutive=$((consecutive + 1))
|
|
||||||
log "unhealthy check $consecutive/$THRESHOLD"
|
|
||||||
if [ "$consecutive" -ge "$THRESHOLD" ]; then
|
|
||||||
log "sustained unhealthy for $consecutive consecutive checks -- restarting $TARGET"
|
|
||||||
if curl -sf --max-time 30 -X POST "$DOCKER_API/containers/$TARGET/restart?t=30" >/dev/null 2>&1; then
|
|
||||||
log "restart request accepted"
|
|
||||||
else
|
|
||||||
log "WARNING: restart request failed -- will keep retrying every $INTERVAL"
|
|
||||||
fi
|
|
||||||
consecutive=0
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
starting | none)
|
|
||||||
# "starting" = still inside start_period (normal on boot/redeploy);
|
|
||||||
# "none" = the target has no HEALTHCHECK at all -- either way, not
|
|
||||||
# this watchdog's concern.
|
|
||||||
consecutive=0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log "unexpected/unparseable health status ($status) -- leaving consecutive count unchanged"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
Reference in New Issue
Block a user