-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
177 lines (173 loc) · 9.3 KB
/
Copy pathdocker-compose.yml
File metadata and controls
177 lines (173 loc) · 9.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
services:
server:
image: ghcr.io/sppidy/shellfleet/server:latest
build:
context: .
dockerfile: Dockerfile.server
ports:
# Browser UI / API: loopback only — nginx (the public ingress)
# proxies to 127.0.0.1:8080, so the HTTP API must not be reachable
# on other interfaces.
- "127.0.0.1:8080:8080"
# Agent mTLS listener. Agents connect DIRECTLY to this port
# (bypassing nginx) and present a client cert verified against
# AGENT_MTLS_CA. Bound on all interfaces so remote agents can reach
# it. In production /agent/ws is served ONLY here, not on 8080.
# Skipped in dev mode (JWT_SECRET=dev), which keeps /agent/ws on 8080.
- "${AGENT_MTLS_PORT:-8443}:${AGENT_MTLS_PORT:-8443}"
environment:
# GitHub OAuth Configuration
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
- OAUTH_REDIRECT_URL=${OAUTH_REDIRECT_URL:-https://dashboard.example.com/auth/callback}
- UI_URL=${UI_URL:-https://dashboard.example.com/}
# JWT_SECRET signs every session cookie. Anyone who knows this value
# can forge sessions, so we refuse to start without an explicit value.
# Generate one with `openssl rand -hex 32` and put it in .env.
- "JWT_SECRET=${JWT_SECRET:?JWT_SECRET must be set, run openssl rand -hex 32}"
# Comma-separated GitHub logins allowed to sign in. No default —
# publishing a default would mean every fresh deploy starts wide
# open to whoever the default user is.
- "ALLOWED_GITHUB_USERS=${ALLOWED_GITHUB_USERS:?ALLOWED_GITHUB_USERS must be set, comma-separated GitHub logins permitted to sign in}"
# Only these direct peer CIDRs may supply forwarded client-IP headers.
# Set this to the exact Docker/ingress gateway CIDR for your deployment.
- "TRUSTED_PROXY_CIDRS=${TRUSTED_PROXY_CIDRS:?TRUSTED_PROXY_CIDRS must be set to the exact nginx or ingress peer CIDR}"
# Optional legacy shared agent token for one-shot bootstrapping.
# Leave unset to disable; do NOT set it to "legacy_secret_off".
- AGENT_SECRET=${AGENT_SECRET:-}
- TOKENS_PATH=/data/approved_tokens.json
# ─── Outbound webhook fan-out ──────────────────────────────
# Set the sinks you want; unset ones don't fire. Every event
# type (apt update / health probe / backup / disconnect /
# connect) routes to whichever of these are set. See
# docs/WEBHOOKS.md if you want per-event-type routing — that
# uses `<UPDATE_|HEALTH_|BACKUP_|DISCONNECT_|CONNECT_>`
# prefixed env vars; add the corresponding passthroughs to
# this list when you need them.
- WEBHOOK_URL=${WEBHOOK_URL:-}
- WEBHOOK_FORMAT=${WEBHOOK_FORMAT:-json}
- WEBHOOK_EXPOSE_AGENT_ID=${WEBHOOK_EXPOSE_AGENT_ID:-false}
- WEBHOOK_INCLUDE_OUTPUT=${WEBHOOK_INCLUDE_OUTPUT:-false}
- SLACK_WEBHOOK_URL=${SLACK_WEBHOOK_URL:-}
- DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL:-}
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
# While an agent stays offline past the disconnect grace, re-fire an
# `agent.still_offline` reminder (via the DISCONNECT_* sinks) every
# STALE_AGENT_REALERT_SECS so a silently-stranded agent stays visible.
# Default 3600 (1h); set 0 to disable the recurring reminder.
- STALE_AGENT_REALERT_SECS=${STALE_AGENT_REALERT_SECS:-3600}
# Comma-separated extra origins permitted to upgrade /ui/ws.
# The UI_URL origin is always allowed automatically. Use this if
# you have additional dashboards (e.g. dev tunnels) that need the
# WS without becoming the canonical URL.
- WS_ALLOWED_ORIGINS=${WS_ALLOWED_ORIGINS:-}
# In-tree backup machinery is OFF by default. Most operators back
# up at the hypervisor level (Proxmox snapshots etc.) and don't
# need a redundant in-VM tar. Set BACKUPS_ENABLED=true to expose
# the /api/backups routes, the scheduler, and the per-agent
# Backups tab in the UI. Existing backup_jobs rows are preserved
# across toggles.
- BACKUPS_ENABLED=${BACKUPS_ENABLED:-false}
# Anonymous usage telemetry (default ON): reports only counts,
# version, CE/EE edition, and enabled-feature names — no PII. Set
# SHELLFLEET_TELEMETRY=off to disable (or toggle on the admin page).
# The collector endpoint is hardcoded in the server; there is no URL knob.
- SHELLFLEET_TELEMETRY=${SHELLFLEET_TELEMETRY:-on}
# Required when telemetry is on. The server HMAC-signs every report and
# the collector rejects unsigned or stale payloads.
- SHELLFLEET_TELEMETRY_HMAC_KEY=${SHELLFLEET_TELEMETRY_HMAC_KEY:-}
# EE session recording (off by default): when on AND the EE sidecar is
# active AND the license includes "recordings", CE mirrors every terminal
# session to EE for playback on the /recordings page. Best-effort — never
# blocks or breaks a live terminal.
- EE_RECORD_TERMINALS=${EE_RECORD_TERMINALS:-off}
# Directional CE↔EE authentication. Each JSON object maps a key id to a
# base64-encoded independent 32-byte key. Both processes accept every
# key in each ring and sign with the named active key, enabling overlap
# during rotation without restoring the legacy shared bearer secret.
- CE_TO_EE_HMAC_KEYS_JSON=${CE_TO_EE_HMAC_KEYS_JSON:-}
- CE_TO_EE_HMAC_ACTIVE_KEY_ID=${CE_TO_EE_HMAC_ACTIVE_KEY_ID:-}
- EE_TO_CE_HMAC_KEYS_JSON=${EE_TO_CE_HMAC_KEYS_JSON:-}
- EE_TO_CE_HMAC_ACTIVE_KEY_ID=${EE_TO_CE_HMAC_ACTIVE_KEY_ID:-}
# Agent mutual TLS. The server presents SERVER_TLS_CERT and
# REQUIRES a client cert verified against AGENT_MTLS_CA. Agents
# connect directly to AGENT_MTLS_PORT (default 8443), bypassing
# nginx. Leave unset in dev mode (JWT_SECRET=dev) to skip the
# mTLS listener and serve /agent/ws on the plain port.
#
# Cert provisioning (run on a secure host, then place the files in
# $SERVER_TLS_DIR, default /etc/shellfleet/tls):
# # agent CA
# openssl genrsa -out agent-ca.key 4096
# openssl req -x509 -new -key agent-ca.key -days 3650 -subj "/CN=ShellFleet Agent CA" -out agent-ca.pem
# # server cert (CN/SAN = the host agents reach SERVER_WS_URL on)
# openssl genrsa -out server-key.pem 4096
# openssl req -new -key server-key.pem -subj "/CN=dashboard.example.com" -out server.csr
# openssl x509 -req -in server.csr -CA agent-ca.pem -CAkey agent-ca.key -CAcreateserial -days 825 -out server.pem
# # per-agent client cert (repeat per agent)
# openssl genrsa -out agent-01.key 2048
# openssl req -new -key agent-01.key -subj "/CN=agent-01" -out agent-01.csr
# openssl x509 -req -in agent-01.csr -CA agent-ca.pem -CAkey agent-ca.key -CAcreateserial -days 825 -out agent-01.pem
# The agent gets AGENT_MTLS_CERT_PATH=agent-01.pem,
# AGENT_MTLS_KEY_PATH=agent-01.key, SERVER_TLS_CA_PATH=agent-ca.pem.
- SERVER_TLS_CERT_PATH=/etc/shellfleet/tls/server.pem
- SERVER_TLS_KEY_PATH=/etc/shellfleet/tls/server-key.pem
- AGENT_MTLS_CA_PATH=/etc/shellfleet/tls/agent-ca.pem
- AGENT_MTLS_PORT=${AGENT_MTLS_PORT:-8443}
volumes:
- server_data:/data
# Server + agent-CA certs for the mTLS listener (read-only).
# Populate ${SERVER_TLS_DIR} (default /etc/shellfleet/tls) with
# server.pem, server-key.pem, agent-ca.pem using the commands above.
- ${SERVER_TLS_DIR:-/etc/shellfleet/tls}:/etc/shellfleet/tls:ro
healthcheck:
test: ["CMD-SHELL", "wget -q -O- http://127.0.0.1:8080/healthz || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
restart: unless-stopped
networks:
- shellfleet_net
web:
image: ghcr.io/sppidy/shellfleet/web:latest
build:
context: .
dockerfile: Dockerfile.web
ports:
# Loopback only — reached via nginx proxy_pass to 127.0.0.1:3000.
- "127.0.0.1:3000:3000"
environment:
- NEXT_PUBLIC_WS_URL=${NEXT_PUBLIC_WS_URL:-wss://dashboard.example.com/ui/ws}
restart: unless-stopped
networks:
- shellfleet_net
# NOTE: deliberately NO `depends_on: server`. The web container only talks
# to the server over the network at request time (via nginx), so it doesn't
# need the server up to start — and a dependency made `docker compose up
# --build web` recreate the server container too, bouncing every agent's
# WebSocket on what should be a frontend-only deploy. Web and server are now
# deployed independently.
# The Agent is usually deployed natively on target machines,
# but we include it in docker-compose for local end-to-end testing.
# agent:
# build:
# context: .
# dockerfile: Dockerfile.agent
# environment:
# - SERVER_WS_URL=wss://dashboard.example.com/agent/ws
# volumes:
# # Mount the host's DBus socket to allow the containerized agent to control the host's systemd
# - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
# # Optionally mount host's /etc to allow the config editor to see it
# # - /etc:/etc
# networks:
# - shellfleet_net
# depends_on:
# - server
networks:
shellfleet_net:
driver: bridge
volumes:
server_data: