closer to mvp

This commit is contained in:
Jason Ross
2026-04-27 17:51:37 -05:00
parent e34fdde7d6
commit bb1def719f
9 changed files with 63 additions and 56 deletions
+9 -8
View File
@@ -14,15 +14,16 @@ INSTANCE_NAME=My Stoat Instance
# Admin API listen port and the HTTPS browser origin allowed by CORS.
ADMIN_API_PORT=5181
ADMIN_WEB_ORIGIN=https://admin.example.com
ADMIN_WEB_ORIGIN=https://localhost:9443
# Admin hostname terminated by the dedicated Caddy proxy. This hostname only
# needs to resolve on your WireGuard/private network.
ADMIN_HOSTNAME=admin.example.com
# Admin hostname terminated by the dedicated Caddy proxy. Use localhost for
# local compose usage and replace it with a real name for deployment.
ADMIN_HOSTNAME=localhost
# Compose-level convenience variables for the dedicated admin proxy.
# Compose-level convenience variables for the dedicated admin proxy. These
# defaults avoid privileged ports locally; set 80/443 in deployment if needed.
ADMIN_BIND_IP=127.0.0.1
ADMIN_HTTP_PORT=80
ADMIN_HTTPS_PORT=443
# Leave blank to use same-origin /api requests through admin-proxy.
ADMIN_HTTP_PORT=9080
ADMIN_HTTPS_PORT=9443
# Optional frontend API base URL override for standalone web builds.
ADMIN_WEB_API_URL=
+11 -7
View File
@@ -16,7 +16,8 @@ Stoat Admin is a lightweight self-hosted moderation and invite dashboard for a S
```text
.
├── api/ # Express + TypeScript backend
├── web/ # Vite + React + TypeScript frontend
├── proxy/ # Caddy + Coraza reverse proxy image
├── web/ # Vite + React frontend and static image build
├── deploy/s6/ # Example s6 service directories and systemd unit
├── compose.yml # Production-oriented compose file
├── compose.override.example.yml
@@ -93,19 +94,22 @@ pnpm dev:web
| `ADMIN_API_PORT` | Listen port for `admin-api` |
| `ADMIN_WEB_ORIGIN` | Exact HTTPS browser origin allowed by CORS |
| `ADMIN_HOSTNAME` | Hostname served by the dedicated Caddy proxy |
| `ADMIN_BIND_IP` | Compose bind IP for the proxy's `80`/`443` ports |
| `ADMIN_HTTP_PORT` | Host port for ACME HTTP and redirect handling |
| `ADMIN_HTTPS_PORT` | Host port for HTTPS |
| `ADMIN_WEB_API_URL` | Optional frontend API base URL override |
| `ADMIN_BIND_IP` | Compose bind IP for the proxy's published ports |
| `ADMIN_HTTP_PORT` | Published HTTP port for redirect handling |
| `ADMIN_HTTPS_PORT` | Published HTTPS port for the admin dashboard |
| `ADMIN_WEB_API_URL` | Optional frontend API override outside compose |
## Deployment
The repo ships with a standalone `compose.yml` that expects an external `stoat_default` network. Update the network name if your Stoat stack uses a different one.
For local compose use, the proxy defaults to `https://localhost:9443` and `http://localhost:9080`. For deployed hosts, set `ADMIN_HOSTNAME` to the real admin name and switch `ADMIN_HTTP_PORT`/`ADMIN_HTTPS_PORT` to `80`/`443` or use [compose.override.example.yml](/home/jasonross/workspace/stoat-admin/compose.override.example.yml) as a starting point.
The deployment topology is:
- `admin-proxy` publishes ports `80` and `443`, issues a private certificate from Caddy's internal CA, and reverse-proxies `/api/*` to `admin-api` and everything else to `admin-web`.
- `admin-proxy` is built from [proxy/Dockerfile](/home/jasonross/workspace/stoat-admin/proxy/Dockerfile), publishes the configured HTTP and HTTPS ports, issues a private certificate from Caddy's internal CA, applies Coraza, and reverse-proxies `/api/*` to `admin-api` and everything else to `admin-web`.
- `admin-web` and `admin-api` are no longer published directly on the host.
- `admin-web` serves the built Vite bundle privately on the admin network.
- `admin-api` stays attached to the shared Stoat network for MongoDB access and also joins a private admin network used by the proxy.
Before starting the stack, point `ADMIN_HOSTNAME` at the host running `admin-proxy` on your WireGuard/private network and set `ADMIN_WEB_ORIGIN` to `https://<that-hostname>`.
@@ -144,7 +148,7 @@ tail -f /var/log/s6/stoat-admin/current | s6-tai64nlocal
## Development Notes
- The backend uses SQLite for admin credentials, audit logs, and invite metadata, and MongoDB for Stoat state.
- In the composed deployment, the frontend uses same-origin `/api` requests through `admin-proxy`, while local development can still override `VITE_API_URL`.
- In the composed deployment, the frontend always uses same-origin `/api` requests through `admin-proxy`; `VITE_API_URL` is only useful outside compose.
- Root task orchestration is handled by Turborepo through [turbo.json](/home/jasonross/workspace/stoat-admin/turbo.json).
- The current repo state is a first implementation slice based on the design docs in [docs/stoat-admin-design.md](/home/jasonross/workspace/stoat-admin/docs/stoat-admin-design.md) and [docs/stoat-admin-tasks.md](/home/jasonross/workspace/stoat-admin/docs/stoat-admin-tasks.md).
+5 -2
View File
@@ -1,10 +1,12 @@
services:
admin-proxy:
environment:
ADMIN_HOSTNAME: admin.example.com
ADMIN_HTTP_PORT: 80
ADMIN_HTTPS_PORT: 443
ports:
- "10.0.0.1:80:80"
- "10.0.0.1:443:443"
environment:
ADMIN_HOSTNAME: admin.example.com
admin-api:
environment:
@@ -14,5 +16,6 @@ services:
# Common customizations:
# - Change the external network name in compose.yml if your Stoat stack uses a different name.
# - Bind admin-proxy to the interface IP that should answer ports 80/443.
# - Use ADMIN_HTTP_PORT/ADMIN_HTTPS_PORT=9080/9443 for local compose HTTPS without privileged ports.
# - Ensure ADMIN_HOSTNAME resolves to that IP on your WireGuard/private network.
# - Add resource limits or alternate image tags per deployment.
+10 -8
View File
@@ -15,7 +15,7 @@ services:
image: ghcr.io/owner/stoat-admin-api:latest
restart: unless-stopped
expose:
- "5181"
- "${ADMIN_API_PORT:-5181}"
volumes:
- ./data:/data
- ./.env:/app/.env:ro
@@ -26,8 +26,6 @@ services:
admin-web:
build:
context: ./web
args:
VITE_API_URL: ${ADMIN_WEB_API_URL:-}
image: ghcr.io/owner/stoat-admin-web:latest
restart: unless-stopped
expose:
@@ -36,18 +34,22 @@ services:
- admin
admin-proxy:
image: caddy:latest
build:
context: ./proxy
image: ghcr.io/owner/stoat-admin-proxy:latest
restart: unless-stopped
depends_on:
- admin-api
- admin-web
environment:
ADMIN_HOSTNAME: ${ADMIN_HOSTNAME}
ADMIN_API_PORT: ${ADMIN_API_PORT:-5181}
ADMIN_HOSTNAME: ${ADMIN_HOSTNAME:-localhost}
ADMIN_HTTP_PORT: ${ADMIN_HTTP_PORT:-9080}
ADMIN_HTTPS_PORT: ${ADMIN_HTTPS_PORT:-9443}
ports:
- "${ADMIN_BIND_IP:-127.0.0.1}:${ADMIN_HTTP_PORT:-80}:80"
- "${ADMIN_BIND_IP:-127.0.0.1}:${ADMIN_HTTPS_PORT:-443}:443"
- "${ADMIN_BIND_IP:-127.0.0.1}:${ADMIN_HTTP_PORT:-9080}:${ADMIN_HTTP_PORT:-9080}"
- "${ADMIN_BIND_IP:-127.0.0.1}:${ADMIN_HTTPS_PORT:-9443}:${ADMIN_HTTPS_PORT:-9443}"
volumes:
- ./proxy/Caddyfile:/etc/caddy/Caddyfile:ro
- admin_proxy_data:/data
- admin_proxy_config:/config
networks:
+17 -3
View File
@@ -1,14 +1,28 @@
{$ADMIN_HOSTNAME} {
{
http_port {$ADMIN_HTTP_PORT:9080}
https_port {$ADMIN_HTTPS_PORT:9443}
order coraza_waf first
}
{$ADMIN_HOSTNAME:localhost} {
tls internal
encode zstd gzip
coraza_waf {
load_owasp_crs
directives `
Include /etc/caddy/coraza.conf
SecRuleEngine On
`
}
@api path /api/*
handle @api {
reverse_proxy admin-api:5181
reverse_proxy admin-api:{$ADMIN_API_PORT:5181}
}
handle {
reverse_proxy admin-web:80
reverse_proxy admin-web:80 admin-web:443 admin-web:9080 admin-web:9443
}
}
+9
View File
@@ -0,0 +1,9 @@
FROM caddy:2-builder-alpine AS build-caddy
RUN xcaddy build \
--with github.com/corazawaf/coraza-caddy/v2
FROM caddy:2-alpine
COPY --from=build-caddy /usr/bin/caddy /usr/bin/caddy
COPY Caddyfile /etc/caddy/Caddyfile
COPY coraza.conf /etc/caddy/coraza.conf
EXPOSE 80 443 9080 9443
+1 -2
View File
@@ -1,5 +1,4 @@
# Coraza configuration overrides
# Tune false positives here as they arise.
SecAction "id:900000, phase:1, pass, t:none, nolog, setvar:tx.blocking_paranoia_level=1"
SecAction "id:900000, phase:1, pass, t:none, nolog, setvar:tx.blocking_paranoia_level=1"
-14
View File
@@ -1,14 +0,0 @@
:80 {
coraza_waf {
load_owasp_crs
directives `
Include /etc/caddy/coraza.conf
SecRuleEngine On
`
}
root * /srv
file_server
try_files {path} /index.html
}
+1 -12
View File
@@ -5,15 +5,4 @@ RUN corepack enable && pnpm install --frozen-lockfile=false
COPY . .
ARG VITE_API_URL
ENV VITE_API_URL=${VITE_API_URL}
RUN pnpm build
FROM caddy:builder AS build-caddy
RUN xcaddy build \
--with github.com/corazawaf/coraza-caddy/v2
FROM caddy:latest
COPY --from=build-caddy /usr/bin/caddy /usr/bin/caddy
COPY --from=build-app /app/dist /srv
COPY Caddyfile /etc/caddy/Caddyfile
COPY coraza.conf /etc/caddy/coraza.conf
EXPOSE 80
EXPOSE 9080 9443