Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
**/.env.*
**/node_modules/
**/.next/
# Desktop artifacts are mounted into client_web at runtime, never copied in.
/artifacts/
56 changes: 36 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ and relay, with no business logic.
> (SQLx). Release management is implemented with step validation and automatic
> blocking by linked incidents. Desktop is partially implemented as a Tauri
> URL-mode shell with tray/background behavior and native assignment,
> high-severity, and `release_blocked` notifications. AppImage packaging and the
> release CI artifact remain open.
> high-severity, and `release_blocked` notifications. Compose builds an
> installable Linux `.deb` and serves it over HTTP through `client_web`; the
> AppImage is built by the release CI on a GitHub `ubuntu-22.04` runner and is
> exposed as `/client.AppImage` when copied into `./artifacts`.

## Scope

Expand Down Expand Up @@ -88,8 +90,9 @@ microservices instinct is honored where it pays, without distributed-systems tax

**Extended Features** (in progress / planned)

- Tauri desktop URL-mode shell is present (OS notifications + tray); Compose can
build a local `.deb`, while AppImage/release CI packaging remains open
- Tauri desktop URL-mode shell is present (OS notifications + tray); Compose
builds a local `.deb` served by `client_web`, and the release CI builds the
AppImage artifact for the canonical `/client.AppImage` download path
- Google OAuth2 exists as optional auth plumbing
- GitLab as an Action; additional REActions (Slack / HTTP / Email)

Expand Down Expand Up @@ -117,23 +120,26 @@ cd opswarden
cp .env.example .env
# adjust OPSWARDEN_KICKOFF_TOKEN and DATABASE_URL if needed

# 3. Run everything (database + server + web UI)
# 3. Run everything (database + server + desktop artifact + web UI)
docker compose up --build
```

Compose brings up `db`, the `server` on `:8080`, and the production `client_web`
on **`:8081`** (the Next.js UI, also the URL-mode target the desktop build loads).
`client_web` proxies `/api/*` to the server over the compose network; the browser
reaches the WebSocket directly on the server's published `:8080`. If host port
`8081` is already in use, run `CLIENT_WEB_PORT=8091 docker compose up --build`;
the container still listens on `:8081` internally.
Compose brings up `db`, the `server` on `:8080`, a build-only `client_desktop`
service that deposits the Linux desktop package in `./artifacts`, and the
production `client_web` on **`:8081`** (the Next.js UI, also the URL-mode target
the desktop build loads). `client_web` proxies `/api/*` to the server over the
compose network; the browser reaches the WebSocket directly on the server's
published `:8080`. If host port `8081` is already in use, run
`CLIENT_WEB_PORT=8091 docker compose up --build`; the container still listens on
`:8081` internally.

Check the services respond:

```bash
curl http://localhost:8080/health # -> {"status":"ok"}
curl http://localhost:8080/about.json # -> service catalog + SHA-256 token
curl http://localhost:8081/en # -> 200, the web UI (FR at /fr)
curl -I http://localhost:8081/client.deb
```

### Desktop app (Tauri, URL-mode)
Expand All @@ -143,16 +149,26 @@ compose stack (or a dev server) running. In dev: `just desktop-dev`.

A build-only `client_desktop` compose service builds an installable Linux package
in an Ubuntu/FHS container (the Tauri bundler can't run on a NixOS host) and drops
it on the host:
it on the host under `./artifacts`. `client_web` depends on that build and exposes
the package over HTTP:

```bash
docker compose --profile desktop up --build client_desktop
# -> ./artifacts/OpsWarden_amd64.deb (install: sudo apt install ./artifacts/OpsWarden_amd64.deb)
docker compose up --build
curl -I http://localhost:8081/client.deb
sudo apt install ./artifacts/OpsWarden_amd64.deb
```

The **AppImage** is intentionally deferred to the release CI path (GitHub
`ubuntu-22.04` runner via `tauri-action`): Tauri's pinned `linuxdeploy` is broken
inside a local Docker container, so the local Compose path ships the `.deb`.
The **AppImage** is produced by the release CI, not by local Docker: Tauri's
pinned `linuxdeploy` is broken inside a local container, while GitHub's
`ubuntu-22.04` runner (`tauri-action`) builds it successfully. On a `v*.*.*` tag
the `Release` workflow attaches it to the GitHub Release; a manual run
(**Run workflow** / `workflow_dispatch`) uploads it as the `opswarden-appimage`
artifact. Place the downloaded AppImage at `./artifacts/client.AppImage` and the
running web container serves the VIGIL canonical route:

```bash
curl -I http://localhost:8081/client.AppImage
```

### The project at a glance

Expand All @@ -173,7 +189,7 @@ opswarden/
├── client-desktop/ # Tauri -- URL-mode native app + tray (alpha)
├── investigation/ # AI SRE agent (RAG / pgvector) (planned, not in repo yet)
├── .github/workflows/ # server + web + release CI
├── docker-compose.yml # compose setup: server + db
├── docker-compose.yml # compose: db + server + client_desktop + client_web
├── Cargo.toml # cargo workspace
└── package.json # npm workspaces
```
Expand Down Expand Up @@ -268,8 +284,8 @@ handlers (Axum, WS) -> app (use-cases) -> ports (traits) -> domain (pure)
- Tauri URL-mode shell reusing the front-end, with tray/background behavior
- Native OS notifications: assignment, high/critical severity, and blocked
Release are live-proven
- Compose now covers `server` 8080 / `client_web` 8081 / `client_desktop`
build-only `.deb` / `db`; final AppImage + release CI remain the packaging gate
- Compose covers `db` / `server` 8080 / build-only `client_desktop` / `client_web` 8081. The web client serves the local `.deb` at `/client.deb` and the CI-built
AppImage at `/client.AppImage` when placed in `./artifacts/client.AppImage`
- FR/EN i18n (labels, states, severities) persisted server-side

## Contributing
Expand Down
11 changes: 6 additions & 5 deletions client-web/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
# Production web surface for OpsWarden (Next.js), served with `next start` on
# :8081 — the URL-mode target the Tauri desktop build loads. Built from the
# monorepo root context (single root package-lock + npm workspaces). `/api/*` is
# proxied to the server via OPSWARDEN_API_ORIGIN (Compose: http://server:8080),
# resolved at server start; the browser reaches the WebSocket directly on the
# server's published :8080 (NEXT_PUBLIC_WS_URL, baked at build time).
# proxied to the server via OPSWARDEN_API_ORIGIN (Compose: http://server:8080).
# Next *bakes* rewrite destinations into the build, so OPSWARDEN_API_ORIGIN is a
# build ARG, not a runtime env. The browser reaches the WebSocket directly on the
# server's published :8080 (NEXT_PUBLIC_WS_URL, likewise baked at build time).

# ---- build ----
FROM node:20-bookworm-slim AS builder
Expand All @@ -28,8 +29,8 @@ RUN npm run build --workspace client-web
FROM node:20-bookworm-slim AS runner
WORKDIR /app/client-web
ENV NODE_ENV=production
# Installed deps + the built app. next.config.mjs is read at boot, so
# OPSWARDEN_API_ORIGIN is resolved at runtime from the container env.
# Installed deps + the built app. The /api/* rewrite destination is already
# baked into the build above, so the runtime stage carries no API-origin env.
COPY --from=builder /app/node_modules /app/node_modules
COPY --from=builder /app/client-web /app/client-web
EXPOSE 8081
Expand Down
53 changes: 33 additions & 20 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Compose runtime: db + server + client_web.
# The build-only client_desktop service lives behind the optional `desktop` profile.
# Compose runtime: db + server + client_desktop artifact build + client_web.
services:
db:
image: postgres:18-alpine
Expand Down Expand Up @@ -53,9 +52,27 @@ services:
timeout: 3s
retries: 10

# Build-only desktop artifact: produces the installable Linux `.deb` in an
# Ubuntu/FHS container (Tauri's bundler cannot run on the NixOS host) and copies
# it to ./artifacts on the host, then exits. The AppImage is produced by the
# release CI/U4 path (release.yml tauri-action) and can be dropped into
# ./artifacts/client.AppImage for the same web download surface.
client_desktop:
build:
context: .
dockerfile: client-desktop/Dockerfile
volumes:
- ./artifacts:/out
restart: "no"

# Production web surface (Next.js) — the URL-mode target the desktop build
# loads. Serves the UI on :8081 and proxies /api/* to the server over the
# compose network; the browser hits the server's published :8080 for the WS.
# It also serves the desktop artifact over HTTP: ./artifacts is mounted
# read-only into Next's public/ so the .deb built by `client_desktop` is
# downloadable at GET http://localhost:8081/client.deb. If the release-CI
# AppImage is placed at ./artifacts/client.AppImage, it is exposed at the
# VIGIL-specified GET http://localhost:8081/client.AppImage.
client_web:
build:
context: .
Expand All @@ -66,11 +83,25 @@ services:
# the server over the compose network.
NEXT_PUBLIC_WS_URL: ${NEXT_PUBLIC_WS_URL:-ws://localhost:8080/ws}
OPSWARDEN_API_ORIGIN: http://server:8080
# Desktop artifact download surface: ./artifacts (the `.deb` from the
# client_desktop build, and/or a release-CI AppImage) is served as Next
# static files under /desktop/. Read-only; client_desktop fills the .deb
# before client_web starts.
volumes:
- ./artifacts:/app/client-web/public/desktop:ro
command:
[
"sh",
"-c",
"mkdir -p public && ln -sfn desktop/OpsWarden_amd64.deb public/client.deb && ln -sfn desktop/client.AppImage public/client.AppImage && exec /app/node_modules/.bin/next start -p 8081",
]
ports:
- "${CLIENT_WEB_PORT:-8081}:8081"
depends_on:
server:
condition: service_healthy
client_desktop:
condition: service_completed_successfully
healthcheck:
test:
[
Expand All @@ -81,23 +112,5 @@ services:
timeout: 5s
retries: 12

# Build-only desktop artifact: produces the installable Linux `.deb` in an
# Ubuntu/FHS container (Tauri's bundler cannot run on the NixOS host) and copies
# it to ./artifacts on the host, then exits. The AppImage is intentionally left
# to the release CI/U4 path (release.yml tauri-action) — Tauri's linuxdeploy is
# broken inside a local Docker container (see client-desktop/Dockerfile). Behind the `desktop`
# profile so a normal `docker compose up` stays lean (db + server + client_web).
# Generate with:
# docker compose --profile desktop up --build client_desktop
# -> ./artifacts/OpsWarden_amd64.deb
client_desktop:
profiles: ["desktop"]
build:
context: .
dockerfile: client-desktop/Dockerfile
volumes:
- ./artifacts:/out
restart: "no"

volumes:
db_data: