Merge branch 'main' into feat/enterprise-pack

# Conflicts:
#	components/admin/admin-shell.tsx
#	lib/admin/outgoing-hosts-registry.ts
#	lib/admin/reachable-hosts.ts
#	scripts/lite/build.mjs
#	stores/policy-store.ts
This commit is contained in:
Linus Rath
2026-11-01 13:46:59 +01:00
117 changed files with 5963 additions and 80 deletions
@@ -181,6 +181,11 @@ jobs:
cache-from: type=gha,scope=lite
cache-to: type=gha,mode=max,scope=lite
# No source maps for this image yet: with BULWARK_SOURCE_MAPS=1 the
# multi-platform build stalled on the runner after the Lite build step
# (twice, 2026-11-01), though it finishes locally. The static Lite zips
# (static-lite.yml) still upload theirs.
- name: Inspect image
run: docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
@@ -59,10 +59,34 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_COMMIT=${{ github.sha }}
BULWARK_SOURCE_MAPS=1
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
# The same build again, a cache hit on this builder, for the stage that
# holds only the source maps (Dockerfile, "sourcemaps"); the image never
# has them. Both platforms build identical chunks, so amd64 uploads.
# https://bulwarkmail.org/docs/deployment/monitoring
- name: Export source maps
if: matrix.platform == 'linux/amd64'
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ matrix.platform }}
target: sourcemaps
build-args: |
GIT_COMMIT=${{ github.sha }}
BULWARK_SOURCE_MAPS=1
outputs: type=local,dest=sourcemaps
cache-from: type=gha,scope=${{ matrix.platform }}
- name: Upload source maps to Bugsink
if: matrix.platform == 'linux/amd64'
env:
BUGSINK_AUTH_TOKEN: ${{ secrets.BUGSINK_AUTH_TOKEN }}
run: bash scripts/upload-sourcemaps.sh sourcemaps
- name: Export digest
run: |
mkdir -p /tmp/digests
@@ -184,10 +208,35 @@ jobs:
build-args: |
GIT_COMMIT=${{ github.sha }}
NEXT_PUBLIC_LOCALE_PREFIX=always
BULWARK_SOURCE_MAPS=1
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=always-${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=always-${{ matrix.platform }}
# The same build again, a cache hit on this builder, for the stage that
# holds only the source maps (Dockerfile, "sourcemaps"); the image never
# has them. Both platforms build identical chunks, so amd64 uploads.
# https://bulwarkmail.org/docs/deployment/monitoring
- name: Export source maps
if: matrix.platform == 'linux/amd64'
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ matrix.platform }}
target: sourcemaps
build-args: |
GIT_COMMIT=${{ github.sha }}
NEXT_PUBLIC_LOCALE_PREFIX=always
BULWARK_SOURCE_MAPS=1
outputs: type=local,dest=sourcemaps
cache-from: type=gha,scope=always-${{ matrix.platform }}
- name: Upload source maps to Bugsink
if: matrix.platform == 'linux/amd64'
env:
BUGSINK_AUTH_TOKEN: ${{ secrets.BUGSINK_AUTH_TOKEN }}
run: bash scripts/upload-sourcemaps.sh sourcemaps
- name: Export digest
run: |
mkdir -p /tmp/digests-always
+27
View File
@@ -18,6 +18,9 @@ on:
- "public/**"
- "package.json"
- "package-lock.json"
- "scripts/healthcheck.mjs"
- "scripts/sourcemaps.mjs"
- "scripts/upload-sourcemaps.sh"
- ".github/workflows/docker-publish.yml"
workflow_dispatch:
@@ -89,10 +92,34 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_COMMIT=${{ github.sha }}
BULWARK_SOURCE_MAPS=1
outputs: type=image,name=${{ needs.prepare.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
# The same build again, a cache hit on this builder, for the stage that
# holds only the source maps (Dockerfile, "sourcemaps"); the image never
# has them. Both platforms build identical chunks, so amd64 uploads.
# https://bulwarkmail.org/docs/deployment/monitoring
- name: Export source maps
if: matrix.platform == 'linux/amd64'
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ matrix.platform }}
target: sourcemaps
build-args: |
GIT_COMMIT=${{ github.sha }}
BULWARK_SOURCE_MAPS=1
outputs: type=local,dest=sourcemaps
cache-from: type=gha,scope=${{ matrix.platform }}
- name: Upload source maps to Bugsink
if: matrix.platform == 'linux/amd64'
env:
BUGSINK_AUTH_TOKEN: ${{ secrets.BUGSINK_AUTH_TOKEN }}
run: bash scripts/upload-sourcemaps.sh sourcemaps
- name: Export digest
run: |
mkdir -p /tmp/digests
+103
View File
@@ -0,0 +1,103 @@
name: "CI: Kubernetes manifests"
# Builds the image from this commit, deploys deploy/kubernetes to a kind
# cluster (three replicas and one Valkey) and runs
# deploy/kubernetes/test/smoke.sh against it: every replica ready on the
# shared store, one admin session valid everywhere, a config change reaching
# every replica, no failed requests while a pod is deleted and during a
# rolling restart. The config-in-git overlay runs next to it in its own
# namespace and must refuse config saves.
#
# It runs when the manifests change, weekly on main (an app change can break
# several replicas without touching the manifests), and on demand:
# gh workflow run kubernetes.yml
on:
pull_request:
paths:
- deploy/kubernetes/**
- .github/workflows/kubernetes.yml
push:
branches: [main]
paths:
- deploy/kubernetes/**
- .github/workflows/kubernetes.yml
schedule:
- cron: "41 3 * * 1"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: kubernetes-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
kind:
name: kind
runs-on: ubuntu-latest
timeout-minutes: 45
env:
KIND_VERSION: v0.29.0
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Build the image
run: docker build --build-arg "GIT_COMMIT=${GITHUB_SHA}" -t bulwark-local:ci .
- name: Create the cluster
run: |
# The runner's own kind in /usr/local/bin belongs to root; use a pinned one.
mkdir -p "$RUNNER_TEMP/bin"
curl -fsSLo "$RUNNER_TEMP/bin/kind" "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64"
chmod +x "$RUNNER_TEMP/bin/kind"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
export PATH="$RUNNER_TEMP/bin:$PATH"
kind create cluster --name ci --wait 180s
kind load docker-image bulwark-local:ci --name ci
- name: Deploy the base and the config-in-git overlay
run: |
umask 077
admin="$(openssl rand -hex 16)"
echo "::add-mask::$admin"
{
echo "SESSION_SECRET=$(openssl rand -base64 32)"
echo "ADMIN_PASSWORD=$admin"
echo "VALKEY_PASSWORD=$(openssl rand -hex 24)"
} > deploy/kubernetes/base/secrets.env
echo "ADMIN_PASSWORD=$admin" >> "$GITHUB_ENV"
for target in base:bulwark overlays/config-in-git:bulwark-ro; do
src="${target%%:*}"; ns="${target##*:}"
mkdir -p "ci/$ns"
cat > "ci/$ns/kustomization.yaml" <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: $ns
resources:
- ../../deploy/kubernetes/$src
images:
- name: ghcr.io/bulwarkmail/webmail
newName: bulwark-local
newTag: ci
EOF
kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -k "ci/$ns"
done
- name: Smoke test (base)
run: bash deploy/kubernetes/test/smoke.sh bulwark
- name: Smoke test (config in git)
run: READ_ONLY_CONFIG=1 bash deploy/kubernetes/test/smoke.sh bulwark-ro
- name: Show the cluster
if: failure()
run: |
kubectl get pods -A -o wide
for ns in bulwark bulwark-ro; do
kubectl -n "$ns" describe pods | tail -n 80
kubectl -n "$ns" logs -l app.kubernetes.io/name=bulwark --tail=80 --prefix || true
done
+20
View File
@@ -40,9 +40,26 @@ jobs:
- name: Install dependencies
run: npm ci
# With browser and server source maps; Turbopack gives every chunk a
# debug ID. The maps go to Bugsink and never into the tarball.
# https://bulwarkmail.org/docs/deployment/monitoring
- name: Build standalone
env:
BULWARK_SOURCE_MAPS: "1"
run: npm run build
# .next/server holds the server chunks' maps; standalone copies the
# chunks only. It doesn't ship, but its maps are needed for server frames.
- name: Move source maps out of the build
run: node scripts/sourcemaps.mjs collect --out sourcemaps .next/static .next/standalone .next/server
# Turbopack chunks were not checked for being the same on both
# platforms, so each one uploads its own.
- name: Upload source maps to Bugsink
env:
BUGSINK_AUTH_TOKEN: ${{ secrets.BUGSINK_AUTH_TOKEN }}
run: bash scripts/upload-sourcemaps.sh sourcemaps
- name: Package tarball
env:
REF_NAME: ${{ github.ref_name }}
@@ -59,6 +76,9 @@ jobs:
tar -czf "$TARBALL" bulwark-standalone/
echo "TARBALL=$TARBALL" >> "$GITHUB_ENV"
- name: Check the tarball holds no source maps
run: node scripts/sourcemaps.mjs check "$TARBALL"
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
+13
View File
@@ -79,8 +79,18 @@ jobs:
LITE_APP_NAME: ${{ inputs.app_name }}
LITE_LOCALES: ${{ inputs.locales }}
LITE_DEMO_MODE: ${{ inputs.demo }}
# Browser source maps for Bugsink; build:lite moves them out of out/
# before anything is packed. https://bulwarkmail.org/docs/deployment/monitoring
BULWARK_SOURCE_MAPS: "1"
SOURCEMAPS_OUT: sourcemaps
run: npm run build:lite
# Each target is its own build with its own chunks, so each uploads.
- name: Upload source maps to Bugsink
env:
BUGSINK_AUTH_TOKEN: ${{ secrets.BUGSINK_AUTH_TOKEN }}
run: bash scripts/upload-sourcemaps.sh sourcemaps
# Every host literal in the bundle must be a placeholder, the configured
# JMAP server, a built-in host the no-third-party switch turns off, or a
# string listed as never requested (#903).
@@ -107,6 +117,9 @@ jobs:
echo "ZIP=${ZIP}" >> "$GITHUB_ENV"
ls -la "${ZIP}"
- name: Check the zip holds no source maps
run: node scripts/sourcemaps.mjs check "$ZIP"
- name: Upload artifact
uses: actions/upload-artifact@v7
with: