Skip to content

PNRxA/uwu-admin

Repository files navigation

Note

This project is developed with the assistance of AI code generation tools. AI-generated code is reviewed and tested before being merged, but if you encounter any issues, please feel free to open an issue or submit a pull request.

uwu-admin

2026-02-25_07-57-39_x265.mp4

Web admin dashboard for Continuwuity Matrix homeservers.

GitHub · GitHub Container Registry · Docker Hub

Continuwuity only supports admin commands via messages in a special admin room. uwu-admin provides a proper web UI by connecting to the homeserver as a bot account, sending admin commands to the admin room, and displaying the results.

Caution

uwu-admin has full admin control over your homeserver. It is intended for private or internal use and should not be exposed to the public internet. If you do need remote access, place it behind a TLS-terminating reverse proxy with additional access controls (e.g. VPN, IP allowlist, HTTP basic auth). See Production Deployment for more details.

Table of Contents

Architecture

Browser (Vue)  →  uwu-admin-api (Rust/axum :3001)  →  Matrix Homeserver
                                                         ↕
                                                    Admin Room
                                                   (bot sends !admin commands,
                                                    reads server responses)

Quick Start (Docker)

uwu-admin versions follow Continuwuity versions to indicate compatibility. Set the tag to your Continuwuity server version (e.g. v0.5.5 or 0.5.5), which floats to the latest uwu-admin patch for that version. Use an exact tag like v0.5.5-2 to pin a specific build. latest always points to the most recent release regardless of Continuwuity version — it may move to a newer version that is incompatible with your homeserver. Use latest with caution.

docker run -d -p 8080:8080 \
  -e JWT_SECRET=$(openssl rand -hex 32) \
  -e ENCRYPTION_KEY=$(openssl rand -hex 32) \
  -v uwu-data:/data \
  pnrxa/uwu-admin:v0.5.6

Open http://localhost:8080, create an admin account, and add your homeserver.

Setup

Prerequisites

  • Rust (2024 edition)
  • Node.js 22+
  • A Continuwuity homeserver with an admin bot account and admin room

Configuration

Copy the example environment file and generate secrets:

cp api/.env.example api/.env

Generate values for JWT_SECRET and ENCRYPTION_KEY:

openssl rand -hex 32

Paste a unique value into each field in api/.env. Both JWT_SECRET and ENCRYPTION_KEY are required — the server will refuse to start without them.

Variable Description Default
JWT_SECRET 32-byte hex key for signing auth tokens required
ENCRYPTION_KEY 32-byte hex key for encrypting access tokens at rest required
ADMIN_USERNAME Seed an admin account on first start (skips setup screen) none
ADMIN_PASSWORD Password for the seeded admin account none
DATABASE_URL SQLite connection string sqlite:uwu-admin.db?mode=rwc
API_LISTEN API bind address 127.0.0.1:3001
CORS_ORIGIN Allowed CORS origin (enables CSRF protection and credentialed cookies) none
COOKIE_SECURE Set the Secure flag on refresh token cookies true
ALLOW_PRIVATE_HOMESERVERS Allow adding homeservers that resolve to private/loopback IPs (needed when the homeserver is on the same host or network) false

Development

Start the API and web frontend in two terminals:

# Terminal 1 — API
cd api
cargo run              # Starts on :3001
# Terminal 2 — Web
cd web
npm install
npm run dev            # Vite dev server on :5173, proxies /api → :3001

Open http://localhost:5173, create an admin account on first launch, then add a homeserver by entering its URL, bot credentials, and admin room ID or alias. Room fields accept either a room ID (!abc:example.com) or a room alias (#admins:example.com) — aliases are resolved automatically on connect.

Alternatively, use the quadlet-dev.sh script to run everything in a Podman container.

Production Build

# API
cd api
cargo build --release

# Web
cd web
npm run build          # Output in dist/

Production Deployment

Internal without TLS

If uwu-admin is only accessible on a trusted local network (e.g. LAN, tailnet, Docker bridge), the defaults work as-is. Recommended settings:

Variable Recommendation
CORS_ORIGIN Can be left unset. All access is same-origin and the network is trusted.
COOKIE_SECURE Set to false — you are likely serving over plain HTTP.
ALLOW_PRIVATE_HOMESERVERS Set to true if the homeserver is on the same host or network.

Internal with TLS

If you're serving uwu-admin over HTTPS on a local network (e.g. using a self-signed certificate or a private CA), keep COOKIE_SECURE at the default (true) since cookies will be sent over TLS.

Variable Recommendation
CORS_ORIGIN Set to your internal URL (e.g. https://admin.local:8080) if accessing from a different origin. Can be left unset for same-origin access.
COOKIE_SECURE Leave at the default (true).
ALLOW_PRIVATE_HOMESERVERS Set to true if the homeserver is on the same host or network.

Public (behind access controls)

If you need remote access, place uwu-admin behind a TLS-terminating reverse proxy (e.g. Caddy, nginx, Traefik) with additional access controls such as a VPN, IP allowlist, or HTTP basic auth. The application serves plain HTTP and should not be exposed directly to the internet — without TLS, credentials, tokens, and cookies are sent in plaintext.

Variable Recommendation
CORS_ORIGIN Set to your external URL (e.g. https://admin.example.com). Enables server-side CSRF protection as a defense-in-depth layer on top of SameSite=Strict cookies.
COOKIE_SECURE Leave at the default (true) when behind TLS.

Container Deployment

Container images are available on GitHub Container Registry and Docker Hub.

See containers/ for Docker and Podman Quadlet deployment options.

Both require JWT_SECRET and ENCRYPTION_KEY to be set as environment variables — see the example compose file and quadlet config.

Session Persistence

The API stores server connections in a local SQLite database (uwu-admin.db by default). Access tokens are encrypted at rest using ChaCha20-Poly1305.

On startup the API restores saved connections, validates each token against its homeserver, and removes any stale sessions automatically.

Scripts

Helper scripts live in the scripts/ directory.

update-command-tree.sh

Regenerates shared/command-tree.json from the uwu-admin fork of continuwuity. Clones the fork into ../continuwuity if it doesn't already exist, fetches upstream (including tags), rebases on upstream/main, and runs cargo xtask generate-command-tree. When a tag is specified, the script checks out that tag for generation then returns to main — the repo always ends up on main. Build prerequisites are the same as for continuwuity itself (Rust, C/C++ compiler, libclang, liburing, make).

./scripts/update-command-tree.sh [-w] [-p] [tag]
Option Description
tag Generate from a specific upstream version tag (e.g. v0.5.0). Omit to use main
-w Push tags and commits to the fork (for maintainers). Without this flag, nothing is pushed
-p Create a PR with the updated command tree (requires a version tag). Uses gh CLI

Examples:

./scripts/update-command-tree.sh              # Fetch + rebase on upstream main, generate command tree
./scripts/update-command-tree.sh v0.5.0       # Fetch + rebase, generate from a specific tag, return to main
./scripts/update-command-tree.sh -w           # Same as above + push tags and main to fork
./scripts/update-command-tree.sh -w v0.5.0    # Same as above with a specific tag
./scripts/update-command-tree.sh -p v0.5.6    # Generate from a specific tag + open a PR with the changes
./scripts/update-command-tree.sh -w -p v0.5.6 # Same as above + push tags and main to fork

test.sh

Runs the full test suite against a fresh Quadlet build. Rebuilds the container image, wipes the database, then runs frontend unit tests, backend tests (unit + integration), and E2E tests against the container.

./scripts/test.sh

quadlet-dev.sh

Development helper for managing the uwu-admin Podman Quadlet. Builds the container image, installs quadlet unit files to ~/.config/containers/systemd/, and manages the systemd user service.

./scripts/quadlet-dev.sh <command>
Command Description
build Build the container image
install Copy quadlet files and reload systemd
start Build image (if needed), install quadlets (if needed), and start the service
stop Stop the service
rebuild Stop, rebuild image, and restart
restart Restart the service without rebuilding
reset-db Stop, wipe the database volume, and restart with a fresh DB
test Rebuild image, wipe DB, and start (fresh environment for E2E tests)
status Show service status and recent logs
logs Follow the service journal logs
destroy Stop service, remove quadlet files, volume, and image

Shared

The shared/ directory contains data shared between the API and web frontend.

Command Tree

The file shared/command-tree.json describes every admin command (names, descriptions, argument types). It powers the console's autocomplete, and input validation on both the API and frontend. It is auto-generated from the continuwuity fork source code via update-command-tree.sh.

Testing

API

cd api
cargo test                    # Unit tests (no server needed)
cargo test -- --skip integration  # Same, explicitly skipping integration tests
cargo test                    # Full suite including integration tests (needs server)

Unit tests cover auth, crypto, input validation, command parsing, and response handling.

Integration tests require a running Continuwuity instance. Add the following to api/.env:

Variable Description
TEST_HOMESERVER Homeserver URL (e.g. https://matrix.example.com)
TEST_USERNAME Bot username
TEST_PASSWORD Bot password
TEST_ROOM_ID Admin room ID or alias

The integration suite includes an exhaustive command tree test that walks every leaf command in shared/command-tree.json, sends it to the server with appropriate test arguments (matching each arg's type — user IDs, room IDs, event IDs, numbers, etc.), and verifies the command parses successfully. This catches any drift between the generated command tree and the actual server command definitions.

Web

cd web
npm test              # Run all tests once
npm run test:watch    # Run in watch mode during development

Uses Vitest with jsdom. Tests cover:

  • Lib utilities — response parser (all 4 output branches), HTML sanitization, query key factories, Tailwind class merging
  • Composables — command autocomplete suggestions, argument hints, input validation
  • API layer — token management, auth header injection, error handling, 401 refresh flow, request timeouts
  • Pinia stores — auth (login/register/logout), command execution and history, server connection management

E2E

End-to-end tests use Playwright with Chromium and run against a real API + Vite dev server stack.

cd web
npx playwright install --with-deps chromium
npx playwright test

The suite requires a running uwu-admin-api on :3001 and the same test variables used by the backend integration tests. Copy the web example env and fill in values:

cp web/.env.example web/.env
Variable Description Default
E2E_BASE_URL Base URL to test against (skip to use Vite dev server) http://localhost:5173
TEST_HOMESERVER Homeserver URL (e.g. https://matrix.example.com)
TEST_USERNAME Bot username
TEST_PASSWORD Bot password
TEST_ROOM_ID Admin room ID or alias

A global setup step handles account creation (or login) and adds a test server, saving session tokens so individual specs start authenticated. Tests run serially (workers: 1) because they share server state.

Specs cover:

  • Auth — registration, login, logout flows
  • Server management — adding, switching, removing servers
  • Console — sending admin commands, viewing responses
  • User actions — user list table actions (profile, ban, deactivate, etc.)
  • Room actions — room list table actions (details, aliases, bans, etc.)
  • Theme toggle — light/dark mode switching
  • Copy to clipboard — copying values from the UI

You can also point the tests at an already-running instance (e.g. a container) by setting E2E_BASE_URL:

E2E_BASE_URL=http://localhost:8080 npx playwright test

When testing against a Podman Quadlet, use http://127.0.0.1:8080 instead of localhost — Playwright resolves localhost to [::1] (IPv6), which the container port binding may not listen on.

CI

A GitHub Actions workflow (.github/workflows/test.yml) runs on every push to main and on pull requests.

Job Runner Trigger What it does
frontend ubuntu-latest push + PR npm ci, type-check, Vitest unit tests
backend self-hosted (push) / ubuntu-latest (PR) push + PR cargo test — unit tests always, integration tests on push (secrets available)
e2e self-hosted push only Builds the API, starts it in the background, installs Playwright + Chromium, runs the full e2e suite, uploads the HTML report as an artifact
update-command-tree self-hosted manual (workflow_dispatch) Clones the continuwuity fork, generates shared/command-tree.json for the given version tag, and opens a PR

The e2e job only runs on pushes to main (not PRs) because it needs repository secrets and a self-hosted runner with access to a live Continuwuity instance. The Playwright HTML report is uploaded as a build artifact and retained for 14 days.

The update-command-tree workflow is triggered manually from the Actions tab. It clones the public continuwuity fork over HTTPS — no additional secrets are required beyond the default GITHUB_TOKEN.

Releasing

uwu-admin versions track Continuwuity versions — v0.5.5-0 of uwu-admin is the initial release compatible with Continuwuity v0.5.5. When uwu-admin needs additional releases for the same Continuwuity version (bug fixes, UI improvements, etc.), we increment the suffix: v0.5.5-1, v0.5.5-2, and so on.

Git tag Meaning
v0.5.5-0 Initial release for Continuwuity v0.5.5
v0.5.5-1 First uwu-admin patch for the same Continuwuity version
v0.5.5-2 Second uwu-admin patch
v0.6.0-0 Tracks Continuwuity v0.6.0

Docker images get three tags: the exact version (v0.5.5-2), a floating base version (v0.5.5) that always points to the latest -N release, and latest.

Docker tag Behaviour
v0.5.5-2 Pinned to one specific build
v0.5.5 Floating — always the latest -N for that Continuwuity version
latest Floating — always the most recent release overall

release.sh

Updates version references in package.json, Cargo.toml, and README.md, commits the changes, creates a git tag, and pushes everything. Shows a diff and prompts for confirmation before making any changes.

./scripts/release.sh <version-tag>

Example:

./scripts/release.sh v0.5.6-0
./scripts/release.sh v0.5.6-1

Pushing a v* tag triggers the release workflow (.github/workflows/release.yml), which builds the container image, pushes it to ghcr.io and Docker Hub, and creates a GitHub Release with auto-generated notes.

About

Continuwuity admin web console

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors