WireGuard server with an embedded admin console

Go backend that drives kernel WireGuard over netlink (wireguard-go as the
fallback), nftables NAT with MSS clamping, forwarding and buffer sysctls,
SQLite for peers, users, sessions, traffic history and the audit log.

React console: dashboard with live rates and usage history, peer management
with QR codes and .conf downloads, disconnect, session reset, key rotation,
expiry, client-supplied keys, settings, users with admin and viewer roles,
two-factor authentication with recovery codes, audit log.

Docker image on Alpine with compose files for bridged and host networking,
CI and GHCR publish workflows, performance notes.
This commit is contained in:
jcoffey
2026-09-12 19:56:08 -07:00
commit 6c006e1d4d
72 changed files with 11675 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
name: CI
on:
push:
branches: [main]
pull_request:
# Lets a run be started by hand against any ref, including one GitHub
# queued and then orphaned.
workflow_dispatch:
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 26
cache: npm
cache-dependency-path: web/package-lock.json
- run: npm ci --ignore-scripts --no-audit --no-fund
working-directory: web
- run: npm run build
working-directory: web
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: go vet ./...
- run: go test -count=1 ./...
- name: govulncheck
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
- name: Docker build
run: docker build -t wgx:ci .
+127
View File
@@ -0,0 +1,127 @@
# Publish the container image to GHCR.
#
# FIRST RUN: a package GHCR creates for the first time is private, even in a
# public repository. Set it to public by hand under the package's settings
# and check with a logged-out `docker pull`.
#
# Two architectures, each built on its own native runner rather than under
# QEMU (`ubuntu-24.04-arm` is free for public repositories). Each runner
# pushes an untagged image by digest and a final job joins the two into one
# multi-arch tag.
name: Publish image
on:
release:
types: [published]
workflow_dispatch:
inputs:
ref:
description: "Tag, branch or SHA to build"
required: true
default: main
tag_latest:
description: "Also move :latest to this build"
type: boolean
default: false
env:
IMAGE: ghcr.io/coffey-labs/wgx
jobs:
version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.v.outputs.version }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref || github.ref }}
fetch-depth: 0
- id: v
run: |
V="$(git describe --tags --always --dirty)"
V="${V#v}"
echo "version=$V" >> "$GITHUB_OUTPUT"
echo "version $V"
build:
needs: version
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref || github.ref }}
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: push
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ matrix.platform }}
build-args: WGX_VERSION=${{ needs.version.outputs.version }}
provenance: false
sbom: false
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Save the digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.push.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- uses: actions/upload-artifact@v7
with:
name: digest-${{ strategy.job-index }}
path: /tmp/digests/*
retention-days: 1
if-no-files-found: error
publish:
needs: [version, build]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digest-*
merge-multiple: true
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create the manifest
run: |
tags=(-t "${IMAGE}:${{ needs.version.outputs.version }}")
if [ "${{ github.event_name }}" = "release" ] && [ "${{ github.event.release.prerelease }}" = "false" ]; then
tags+=(-t "${IMAGE}:latest")
elif [ "${{ inputs.tag_latest }}" = "true" ]; then
tags+=(-t "${IMAGE}:latest")
fi
refs=()
for f in /tmp/digests/*; do
refs+=("${IMAGE}@sha256:$(basename "$f")")
done
docker buildx imagetools create "${tags[@]}" "${refs[@]}"
- name: Show what landed
run: docker buildx imagetools inspect "${IMAGE}:${{ needs.version.outputs.version }}"