feat: initial WireGUI implementation — full VPN management platform
Some checks failed
CI / test (push) Failing after 26s
CI / release (push) Has been skipped
CI / docker (push) Has been skipped

Complete Python/NiceGUI rewrite of the Wirezone (Elixir/Phoenix) VPN
management platform. All 10 implementation phases delivered.

Core stack:
- NiceGUI reactive UI with SQLModel ORM on PostgreSQL (asyncpg)
- Alembic migrations, Valkey/Redis cache, pydantic-settings config
- WireGuard management via subprocess (wg/ip/nft CLIs)
- 164 tests passing, 35% code coverage

Features:
- User/device/rule CRUD with admin and unprivileged roles
- Full device config form with per-device WG overrides
- WireGuard client config generation with QR codes
- REST API (v0) with Bearer token auth for all resources
- TOTP MFA with QR registration and challenge flow
- OIDC SSO with authlib (provider registry, auto-create users)
- Magic link passwordless sign-in via email
- SAML SP-initiated SSO with IdP metadata parsing
- WebAuthn/FIDO2 security key registration
- nftables firewall with per-user chains and masquerade
- Background tasks: WG stats polling, VPN session expiry,
  OIDC token refresh, WAN connectivity checks
- Startup reconciliation (DB ↔ WireGuard state sync)
- In-memory notification system with header badge
- Admin UI: users, devices, rules, settings (3 tabs), diagnostics
- Loguru logging with optional timestamped file output

Deployment:
- Multi-stage Dockerfile (python:3.13-slim)
- Docker Compose prod stack (bridge networking, NET_ADMIN, nftables)
- Forgejo CI: tests → semantic versioning → Docker registry push
- Health endpoint at /api/health
This commit is contained in:
Stefano Bertelli 2026-03-30 16:53:46 -05:00
commit 0546b44507
109 changed files with 11793 additions and 0 deletions

View file

@ -0,0 +1,211 @@
name: CI
on:
push:
branches:
- main
pull_request:
jobs:
test:
runs-on: docker
container:
image: python:3.13-slim
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: wiregui
POSTGRES_PASSWORD: wiregui
POSTGRES_DB: wiregui
options: >-
--health-cmd "pg_isready -U wiregui"
--health-interval 5s
--health-timeout 5s
--health-retries 5
env:
WG_DATABASE_URL: postgresql+asyncpg://wiregui:wiregui@postgres/wiregui
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
apt-get update && apt-get install -y --no-install-recommends \
wireguard-tools pkg-config libxml2-dev libxmlsec1-dev libxmlsec1-openssl
- name: Install uv
run: pip install uv
- name: Install dependencies
run: uv sync
- name: Run tests
run: uv run pytest -v --tb=short
release:
needs: test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: docker
outputs:
new_tag: ${{ steps.version.outputs.new_tag }}
new_version: ${{ steps.version.outputs.new_version }}
skip: ${{ steps.version.outputs.skip }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine version bump
id: version
run: |
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "latest_tag=${LATEST_TAG}" >> "$GITHUB_OUTPUT"
CURRENT="${LATEST_TAG#v}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
COMMITS=$(git log "${LATEST_TAG}..HEAD" --pretty=format:"%s" 2>/dev/null || git log --pretty=format:"%s")
BUMP="none"
while IFS= read -r msg; do
case "$msg" in
*"BREAKING CHANGE"*|*"!:"*)
BUMP="major"
break
;;
feat:*|feat\(*)
[ "$BUMP" != "major" ] && BUMP="minor"
;;
fix:*|fix\(*|perf:*|perf\(*|refactor:*|refactor\(*)
[ "$BUMP" = "none" ] && BUMP="patch"
;;
esac
done <<< "$COMMITS"
if [ "$BUMP" = "none" ]; then
echo "No version-relevant commits since ${LATEST_TAG}, skipping release"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
case "$BUMP" in
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
patch) PATCH=$((PATCH + 1)) ;;
esac
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
echo "new_tag=v${NEW_VERSION}" >> "$GITHUB_OUTPUT"
echo "bump=${BUMP}" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "Version bump: ${BUMP} -> v${NEW_VERSION}"
- name: Generate changelog
id: changelog
if: steps.version.outputs.skip != 'true'
run: |
LATEST_TAG="${{ steps.version.outputs.latest_tag }}"
NEW_TAG="${{ steps.version.outputs.new_tag }}"
BODY="## ${NEW_TAG}"$'\n\n'
for type_label in "feat:Features" "fix:Bug Fixes" "refactor:Refactoring" "perf:Performance" "docs:Documentation" "chore:Maintenance"; do
prefix="${type_label%%:*}"
label="${type_label#*:}"
MATCHES=$(git log "${LATEST_TAG}..HEAD" --pretty=format:"%s" 2>/dev/null | grep -E "^${prefix}[:(]" || true)
if [ -n "$MATCHES" ]; then
BODY="${BODY}### ${label}"$'\n\n'
while IFS= read -r line; do
CLEAN=$(echo "$line" | sed -E "s/^${prefix}(\([^)]*\))?:\s*//")
BODY="${BODY}- ${CLEAN}"$'\n'
done <<< "$MATCHES"
BODY="${BODY}"$'\n'
fi
done
echo "${BODY}" > /tmp/changelog.md
echo "Generated changelog for ${NEW_TAG}"
- name: Create tag and release
if: steps.version.outputs.skip != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
NEW_TAG="${{ steps.version.outputs.new_tag }}"
git config user.name "Forgejo Actions"
git config user.email "noreply@forge.provvedo.com"
git tag -a "${NEW_TAG}" -m "Release ${NEW_TAG}"
git push origin "${NEW_TAG}"
FORGEJO_URL="${GITHUB_SERVER_URL}"
REPO="${GITHUB_REPOSITORY}"
python3 -c "
import json, urllib.request, os
body = open('/tmp/changelog.md').read()
tag = '${NEW_TAG}'
data = json.dumps({
'tag_name': tag,
'name': tag,
'body': body,
'draft': False,
'prerelease': False
}).encode()
req = urllib.request.Request(
'${FORGEJO_URL}/api/v1/repos/${REPO}/releases',
data=data,
headers={
'Authorization': 'token ' + os.environ['GITHUB_TOKEN'],
'Content-Type': 'application/json'
},
method='POST'
)
resp = urllib.request.urlopen(req)
print(f'Created release {tag} (HTTP {resp.status})')
"
docker:
needs: release
if: needs.release.outputs.skip != 'true'
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
options: --privileged
steps:
- uses: actions/checkout@v4
- name: Build and push image
shell: bash
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
VERSION="${{ needs.release.outputs.new_version }}"
TAG="${{ needs.release.outputs.new_tag }}"
REGISTRY=$(echo "${{ github.server_url }}" | sed 's|https://||; s|http://||')
IMAGE="${REGISTRY}/${{ github.repository_owner }}/wiregui"
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
echo "Building ${IMAGE}:${TAG}"
# Log in to Forgejo container registry
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \
-u "${{ github.repository_owner }}" --password-stdin
# Build the image
docker build --network host \
--build-arg "VERSION=${VERSION}" \
-t "${IMAGE}:${TAG}" \
-t "${IMAGE}:${MAJOR}.${MINOR}" \
-t "${IMAGE}:latest" \
.
# Push all tags
docker push "${IMAGE}:${TAG}"
docker push "${IMAGE}:${MAJOR}.${MINOR}"
docker push "${IMAGE}:latest"
echo "Pushed ${IMAGE}:${TAG}, ${IMAGE}:${MAJOR}.${MINOR}, ${IMAGE}:latest"