CI in a Box (#1175)
GHCR - Development Branches / ghcr-publish (push) Has been cancelled
Box / Build & Lint / Node 24 / alpine / amd64 (push) Has been cancelled
Box / Build & Lint / Node 24 / slim / amd64 (push) Has been cancelled
Box / Build & Lint / Node 26 / alpine / amd64 (push) Has been cancelled
Box / Build & Lint / Node 26 / slim / amd64 (push) Has been cancelled
Box / Build & Lint / Node 24 / alpine / arm64 (push) Has been cancelled
Box / Build & Lint / Node 24 / slim / arm64 (push) Has been cancelled
Box / Build & Lint / Node 26 / alpine / arm64 (push) Has been cancelled
Box / Build & Lint / Node 26 / slim / arm64 (push) Has been cancelled
Box / Unit tests / Node 24 / alpine / amd64 (push) Has been cancelled
Box / Unit tests / Node 24 / slim / amd64 (push) Has been cancelled
Box / Unit tests / Node 26 / alpine / amd64 (push) Has been cancelled
Box / Unit tests / Node 26 / slim / amd64 (push) Has been cancelled
Box / Unit tests / Node 24 / alpine / arm64 (push) Has been cancelled
Box / Unit tests / Node 24 / slim / arm64 (push) Has been cancelled
Box / Unit tests / Node 26 / alpine / arm64 (push) Has been cancelled
Box / Unit tests / Node 26 / slim / arm64 (push) Has been cancelled
Box / Ci Box Integration tests / Node 24 / alpine / amd64 (push) Has been cancelled
Box / Ci Box Integration tests / Node 24 / slim / amd64 (push) Has been cancelled
Box / Ci Box Integration tests / Node 26 / alpine / amd64 (push) Has been cancelled
Box / Ci Box Integration tests / Node 26 / slim / amd64 (push) Has been cancelled
Box / Ci Box Integration tests / Node 24 / alpine / arm64 (push) Has been cancelled
Box / Ci Box Integration tests / Node 24 / slim / arm64 (push) Has been cancelled
Box / Ci Box Integration tests / Node 26 / alpine / arm64 (push) Has been cancelled
Box / Ci Box Integration tests / Node 26 / slim / arm64 (push) Has been cancelled
Box / Application Service Integration tests / Node 24 / alpine / amd64 (push) Has been cancelled
Box / Application Service Integration tests / Node 24 / slim / amd64 (push) Has been cancelled
Box / Application Service Integration tests / Node 26 / alpine / amd64 (push) Has been cancelled
Box / Application Service Integration tests / Node 26 / slim / amd64 (push) Has been cancelled
Box / Application Service Integration tests / Node 24 / alpine / arm64 (push) Has been cancelled
Box / Application Service Integration tests / Node 24 / slim / arm64 (push) Has been cancelled
Box / Application Service Integration tests / Node 26 / alpine / arm64 (push) Has been cancelled
Box / Application Service Integration tests / Node 26 / slim / arm64 (push) Has been cancelled
Docker Hub - Develop / docker-latest (push) Has been cancelled
Tests / Build & Lint (push) Has been cancelled
Tests / Unit tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Application Service Integration tests (push) Has been cancelled
Validate Docker Build / validate-docker-build (push) Has been cancelled

* Introduce Ci in a Box

Ci in a box is the next generation Ci project for Draupnir aiming to kill our dependence on mx-tester in the long run as its unmaintained and less powerful than ci-in-a-box

* Update CI-in-a-Box run conditions

If we run docker validation lets just run ci-in-a-box against it at that point.

* Please the prettier

* Fix CI Logic Error

* Return to Ci box specific harness.

The generic one has the wrong addresses that screwed up AS tests.

* Fix REUSE.toml error for ci-in-a-box Postgres setup script
This commit is contained in:
Catalan Lover authored and GitHub committed 2026-09-09 16:53:56 +00:00
1 parent 020789c5a9
commit 1057ec341e
16 files changed
+976

No files matched your search

+246
View File
@@ -0,0 +1,246 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
#
# SPDX-License-Identifier: Apache-2.0
# Called Box here to have the name that shows up in github GUI not be the full Ci in a box
name: Box
on:
push:
branches-ignore:
- "github-actions/**"
schedule:
- cron: "20 20 * * *"
pull_request:
branches-ignore:
- "github-actions/**"
merge_group:
branches: [main]
workflow_dispatch:
permissions:
contents: read
env:
CI_BOX_COMPOSE_FILE: ci-in-a-box/compose.yaml
DRAUPNIR_CONTEXT: ..
DRAUPNIR_DOCKERFILE: ci-in-a-box/Dockerfile-Draupnir-Develop
DRAUPNIR_CONFIG_SOURCE: ../config
REVERSE_PROXY_CONFIG_PATH: ./scripts/nginx/nginx.conf
REVERSE_PROXY_NEWS_PATH: ../apps/draupnir/test/draupnir_news.json
SYNAPSE_HTTP_ANTISPAM_SOURCE: ../synapse-http-antispam/synapse_http_antispam.py
jobs:
build:
name:
Build & Lint / Node ${{ matrix.node }} / ${{ matrix.variant }} / ${{
matrix.arch }}
runs-on: ${{ matrix.runner }}
continue-on-error: ${{ matrix.node == '26' }}
strategy:
fail-fast: false
matrix:
node: ["24", "26"]
variant: [slim, alpine]
arch: [amd64, arm64]
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
env:
CI_BOX_PROJECT:
ci-in-a-box-node${{ matrix.node }}-${{ matrix.variant }}-${{ matrix.arch
}}
CI_BOX_VARIANT_FILE:
ci-in-a-box/compose.node${{ matrix.node }}-${{ matrix.variant }}.yaml
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Validate Compose configuration
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' config --quiet
- name: Build and lint
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile build-lint up
--abort-on-container-exit --exit-code-from draupnir-build-lint --build
draupnir-build-lint
- name: Collect diagnostics
if: failure()
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' ps && docker compose -p
"$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f "$CI_BOX_VARIANT_FILE"
--profile '*' logs --no-color
- name: Cleanup
if: always()
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' down --volumes --remove-orphans
unit:
name:
Unit tests / Node ${{ matrix.node }} / ${{ matrix.variant }} / ${{
matrix.arch }}
runs-on: ${{ matrix.runner }}
continue-on-error: ${{ matrix.node == '26' }}
strategy:
fail-fast: false
matrix:
node: ["24", "26"]
variant: [slim, alpine]
arch: [amd64, arm64]
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
env:
CI_BOX_PROJECT:
ci-in-a-box-node${{ matrix.node }}-${{ matrix.variant }}-${{ matrix.arch
}}
CI_BOX_VARIANT_FILE:
ci-in-a-box/compose.node${{ matrix.node }}-${{ matrix.variant }}.yaml
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Validate Compose configuration
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' config --quiet
- name: Run unit tests
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile unit up --abort-on-container-exit
--exit-code-from draupnir-unit --build draupnir-unit
- name: Collect diagnostics
if: failure()
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' ps && docker compose -p
"$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f "$CI_BOX_VARIANT_FILE"
--profile '*' logs --no-color
- name: Cleanup
if: always()
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' down --volumes --remove-orphans
integration:
name:
Ci Box Integration tests / Node ${{ matrix.node }} / ${{ matrix.variant }}
/ ${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
continue-on-error: ${{ matrix.node == '26' }}
strategy:
fail-fast: false
matrix:
node: ["24", "26"]
variant: [slim, alpine]
arch: [amd64, arm64]
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
env:
CI_BOX_PROJECT:
ci-in-a-box-node${{ matrix.node }}-${{ matrix.variant }}-${{ matrix.arch
}}
CI_BOX_VARIANT_FILE:
ci-in-a-box/compose.node${{ matrix.node }}-${{ matrix.variant }}.yaml
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Check out external antispam module
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
repository: maunium/synapse-http-antispam
path: synapse-http-antispam
ref: main
- name: Validate Compose configuration
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' config --quiet
- name: Start support stack
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" up -d
- name: Run integration tests
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile integration up
--abort-on-container-exit --exit-code-from draupnir-integration
--build draupnir-integration
- name: Collect diagnostics
if: failure()
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' ps && docker compose -p
"$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f "$CI_BOX_VARIANT_FILE"
--profile '*' logs --no-color
- name: Cleanup
if: always()
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' down --volumes --remove-orphans
appservice-integration:
name:
Application Service Integration tests / Node ${{ matrix.node }} / ${{
matrix.variant }} / ${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
continue-on-error: ${{ matrix.node == '26' }}
strategy:
fail-fast: false
matrix:
node: ["24", "26"]
variant: [slim, alpine]
arch: [amd64, arm64]
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
env:
CI_BOX_PROJECT:
ci-in-a-box-node${{ matrix.node }}-${{ matrix.variant }}-${{ matrix.arch
}}
CI_BOX_VARIANT_FILE:
ci-in-a-box/compose.node${{ matrix.node }}-${{ matrix.variant }}.yaml
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Check out external antispam module
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
repository: maunium/synapse-http-antispam
path: synapse-http-antispam
# Main is intentional so we know if compatibility breaks with the latest version of the antispam module.
# This is for the same reason why Ci is rerun periodically.
ref: main
- name: Validate Compose configuration
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' config --quiet
- name: Start support stack
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" up -d
- name: Run appservice integration tests
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile appservice-integration up
--abort-on-container-exit --exit-code-from
draupnir-appservice-integration --build
draupnir-appservice-integration
- name: Collect diagnostics
if: failure()
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' ps && docker compose -p
"$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f "$CI_BOX_VARIANT_FILE"
--profile '*' logs --no-color
- name: Cleanup
if: always()
run:
docker compose -p "$CI_BOX_PROJECT" -f "$CI_BOX_COMPOSE_FILE" -f
"$CI_BOX_VARIANT_FILE" --profile '*' down --volumes --remove-orphans
+2
View File
@@ -8,6 +8,8 @@ config/*
# Draupnir test data
draupnir-registration.yaml
# Exception for ci-in-a-box appservice config as its static unlike mx-tester one.
!ci-in-a-box/scripts/appservice/draupnir-registration.yaml
# Monorepo
**/node_modules/
+6
View File
@@ -69,3 +69,9 @@ path = "package.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "Gnuxie <Gnuxie@protonmail.com>"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "ci-in-a-box/scripts/postgres/init.sql"
precedence = "aggregate"
SPDX-FileCopyrightText = "2026 Catalan Lover <catalanlover@protonmail.com>"
SPDX-License-Identifier = "0BSD"
+19
View File
@@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
# SPDX-License-Identifier: Apache-2.0
#
# Ci in a Box 3 Compose settings
POSTGRES_IMAGE=postgres:18-alpine
POSTGRES_DB=postgres
SYNAPSE_IMAGE=ghcr.io/element-hq/synapse:latest
SYNAPSE_SERVER_NAME=localhost:9999
NGINX_IMAGE=nginx:stable-alpine
# Paths relative to the main Draupnir checkout.
DRAUPNIR_CONTEXT=..
DRAUPNIR_DOCKERFILE=ci-in-a-box/Dockerfile-Draupnir-Develop
DRAUPNIR_CONFIG_SOURCE=../config
NGINX_PORT=8081
REVERSE_PROXY_CONFIG_PATH=./scripts/nginx/nginx.conf
REVERSE_PROXY_NEWS_PATH=../apps/draupnir/test/draupnir_news.json
SYNAPSE_HTTP_ANTISPAM_SOURCE=../synapse-http-antispam/synapse_http_antispam.py
SYNAPSE_ANTISPAM_BASE_URL=http://draupnir-integration:8082/api/1/spam_check
+56
View File
@@ -0,0 +1,56 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
# Copyright 2024 Gnuxie <Gnuxie@protonmail.com>
# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# SPDX-License-Identifier: Apache-2.0
# syntax=docker/dockerfile:1.7
ARG NODE_VERSION=24
ARG NODE_VARIANT=slim
FROM node:${NODE_VERSION}-${NODE_VARIANT} AS build-stage
RUN if [ -f /etc/alpine-release ]; then \
apk add --no-cache git python3 make g++; \
else \
apt-get update \
&& apt-get install -y --no-install-recommends git python3 make g++ \
&& rm -rf /var/lib/apt/lists/*; \
fi
WORKDIR /tmp/src
COPY package.json package-lock.json ./
COPY packages/interface-manager/package.json ./packages/interface-manager/package.json
COPY packages/matrix-basic-types/package.json ./packages/matrix-basic-types/package.json
COPY packages/matrix-protection-suite/package.json ./packages/matrix-protection-suite/package.json
COPY packages/matrix-protection-suite-for-matrix-bot-sdk/package.json ./packages/matrix-protection-suite-for-matrix-bot-sdk/package.json
COPY packages/mps-interface-adaptor/package.json ./packages/mps-interface-adaptor/package.json
COPY packages/tsconfig/package.json ./packages/tsconfig/package.json
COPY apps/draupnir/package.json ./apps/draupnir/package.json
# Install dependencies first so source edits don't invalidate this layer.
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
# Build the complete development workspace. The CI service needs build tools,
# source, development dependencies, and generated output in the final image.
RUN npm run build
FROM node:${NODE_VERSION}-${NODE_VARIANT} AS final-stage
RUN if [ -f /etc/alpine-release ]; then \
apk add --no-cache git; \
else \
apt-get update \
&& apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/*; \
fi
WORKDIR /workspace
COPY --from=build-stage /tmp/src /workspace
ENV CI=1
ENV NODE_ENV=development
ENV NODE_CONFIG_DIR=/workspace/config
CMD ["npm", "test"]
+149
View File
@@ -0,0 +1,149 @@
<!--
SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
SPDX-License-Identifier: Apache-2.0
-->
# Draupnir CI in a Box
This directory contains the so called CI in a Box. Aka Ci-Box. This provides the
ability to run the same validation that the CI will run but locally and do so
without needing to worry about if your in a linux or windows based environment
as long as you have the ability to run docker.
Ci in a box provides the following capabilities:
- build and lint checks
- unit tests
- bot integration tests
- appservice integration tests
The local commands intentionally mirror the CI workflow so the same setup is
easier to troubleshoot and less prone to the black-box problems we had with the
previous CI.
## Prerequisites
- Docker with Compose
- A local Draupnir checkout
- A local checkout of
[`maunium/synapse-http-antispam`](https://github.com/maunium/synapse-http-antispam)
for integration runs. (Synapse can potentially be a bit cranky even if AS
doesnt need this. Thats why its a requirment for both.)
The local layout is expected to be:
```text
parent/
├── Draupnir/
├── synapse-http-antispam/
└── ...
```
`ci-in-a-box/` sits inside the Draupnir repo and should be invoked from there.
## Quick start
1. Copy the sample environment file if you want to use non-default settings:
```bash
cp .env.example .env
```
2. Review the contents of `.env` to see if your local checkouts are aligned
correctly or if you need to use non defaults.
3. Run the validation command you want to exercise, for example:
```bash
docker compose -f compose.yaml -f compose.node24-slim.yaml --profile build-lint up --build draupnir-build-lint
```
If you need to inspect the resolved Compose configuration while debugging, you
can still run:
```bash
docker compose config
docker compose -f compose.yaml -f compose.node24-slim.yaml --profile '*' config --quiet
```
## Variant selection
These files represent the supported runtime variants:
```text
compose.node24-slim.yaml
compose.node24-alpine.yaml
compose.node26-slim.yaml
compose.node26-alpine.yaml
```
Use the base Compose file together with one of the variant files when running a
check:
```bash
docker compose -f compose.yaml -f compose.node24-slim.yaml --profile build-lint up --build draupnir-build-lint
```
## Common contributor commands
Build and lint:
```bash
docker compose -f compose.yaml -f compose.node24-slim.yaml --profile build-lint up --build draupnir-build-lint
```
Run unit tests:
```bash
docker compose -f compose.yaml -f compose.node24-slim.yaml --profile unit up --build draupnir-unit
```
Run bot mode integration tests:
```bash
docker compose -f compose.yaml -f compose.node24-slim.yaml up -d
docker compose -f compose.yaml -f compose.node24-slim.yaml --profile integration up --build draupnir-integration
```
Run appservice mode integration tests:
```bash
docker compose -f compose.yaml -f compose.node24-slim.yaml up -d
docker compose -f compose.yaml -f compose.node24-slim.yaml --profile appservice-integration up --build draupnir-appservice-integration
```
Clean up after a run:
```bash
docker compose -f compose.yaml -f compose.node24-slim.yaml --profile '*' down --volumes
```
## Debian and Alpine checks
Use the variant file to switch between the Debian and Alpine validation paths:
```bash
docker compose -f compose.yaml -f compose.node24-alpine.yaml --profile unit up --build draupnir-unit
```
This keeps the glibc and musl validation paths in the same matrix, so you can
compare the baseline Debian run against the Alpine target and diagnose
distro-specific regressions. This layout avoids environment variables where
possible, which makes it easier to work consistently across Windows and Linux
hosts.
## CI parity
When changing this workflow, keep the local commands aligned with the GitHub
Actions matrix. In practice, that means:
1. Use the repo-local `ci-in-a-box/` files.
2. Check out the latest `maunium/synapse-http-antispam` revision for the
integration job so compatibility regressions are caught early.
3. Run the same Compose commands locally that the workflow runs in CI.
4. Keep the cleanup step consistent with `down --volumes` after failures or
successful runs.
This keeps the contributor workflow reproducible and makes CI failures easier to
diagnose from the same commands.
+33
View File
@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
# SPDX-License-Identifier: Apache-2.0
services:
draupnir-build-lint:
build:
args:
NODE_VERSION: "24"
NODE_VARIANT: alpine
image: draupnir/ci-box-draupnir:node24-alpine
draupnir-unit:
build:
args:
NODE_VERSION: "24"
NODE_VARIANT: alpine
image: draupnir/ci-box-draupnir:node24-alpine
draupnir-integration:
build:
args:
NODE_VERSION: "24"
NODE_VARIANT: alpine
image: draupnir/ci-box-draupnir:node24-alpine
draupnir-appservice-integration:
build:
args:
NODE_VERSION: "24"
NODE_VARIANT: alpine
image: draupnir/ci-box-draupnir:node24-alpine
draupnir-config-init:
image: draupnir/ci-box-draupnir:node24-alpine
+33
View File
@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
# SPDX-License-Identifier: Apache-2.0
services:
draupnir-build-lint:
build:
args:
NODE_VERSION: "24"
NODE_VARIANT: slim
image: draupnir/ci-box-draupnir:node24-slim
draupnir-unit:
build:
args:
NODE_VERSION: "24"
NODE_VARIANT: slim
image: draupnir/ci-box-draupnir:node24-slim
draupnir-integration:
build:
args:
NODE_VERSION: "24"
NODE_VARIANT: slim
image: draupnir/ci-box-draupnir:node24-slim
draupnir-appservice-integration:
build:
args:
NODE_VERSION: "24"
NODE_VARIANT: slim
image: draupnir/ci-box-draupnir:node24-slim
draupnir-config-init:
image: draupnir/ci-box-draupnir:node24-slim
+33
View File
@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
# SPDX-License-Identifier: Apache-2.0
services:
draupnir-build-lint:
build:
args:
NODE_VERSION: "26"
NODE_VARIANT: alpine
image: draupnir/ci-box-draupnir:node26-alpine
draupnir-unit:
build:
args:
NODE_VERSION: "26"
NODE_VARIANT: alpine
image: draupnir/ci-box-draupnir:node26-alpine
draupnir-integration:
build:
args:
NODE_VERSION: "26"
NODE_VARIANT: alpine
image: draupnir/ci-box-draupnir:node26-alpine
draupnir-appservice-integration:
build:
args:
NODE_VERSION: "26"
NODE_VARIANT: alpine
image: draupnir/ci-box-draupnir:node26-alpine
draupnir-config-init:
image: draupnir/ci-box-draupnir:node26-alpine
+33
View File
@@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
# SPDX-License-Identifier: Apache-2.0
services:
draupnir-build-lint:
build:
args:
NODE_VERSION: "26"
NODE_VARIANT: slim
image: draupnir/ci-box-draupnir:node26-slim
draupnir-unit:
build:
args:
NODE_VERSION: "26"
NODE_VARIANT: slim
image: draupnir/ci-box-draupnir:node26-slim
draupnir-integration:
build:
args:
NODE_VERSION: "26"
NODE_VARIANT: slim
image: draupnir/ci-box-draupnir:node26-slim
draupnir-appservice-integration:
build:
args:
NODE_VERSION: "26"
NODE_VARIANT: slim
image: draupnir/ci-box-draupnir:node26-slim
draupnir-config-init:
image: draupnir/ci-box-draupnir:node26-slim
+185
View File
@@ -0,0 +1,185 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
# SPDX-License-Identifier: Apache-2.0
name: ci-in-a-box
services:
postgres:
image: ${POSTGRES_IMAGE:-postgres:18-alpine}
environment:
POSTGRES_USER: ${POSTGRES_USER:-mjolnir-tester}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mjolnir-test}
POSTGRES_DB: ${POSTGRES_DB:-postgres}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 2s
timeout: 5s
retries: 15
volumes:
- postgres-data:/var/lib/postgresql
- ./scripts/postgres/init.sql:/docker-entrypoint-initdb.d/10-synapse.sql:ro
synapse:
image: ${SYNAPSE_IMAGE:-ghcr.io/element-hq/synapse:latest}
depends_on:
postgres:
condition: service_healthy
synapse-init:
condition: service_completed_successfully
environment:
SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
PYTHONPATH: /data
volumes:
- synapse-data:/data
- ${SYNAPSE_HTTP_ANTISPAM_SOURCE:-../synapse-http-antispam/synapse_http_antispam.py}:/data/synapse_http_antispam.py:ro
healthcheck:
test:
[
"CMD-SHELL",
"curl -fsS http://localhost:8008/_matrix/client/versions >/dev/null",
]
interval: 5s
timeout: 5s
retries: 30
synapse-init:
image: ${SYNAPSE_IMAGE:-ghcr.io/element-hq/synapse:latest}
entrypoint: ["/bin/sh"]
depends_on:
postgres:
condition: service_healthy
environment:
SYNAPSE_SERVER_NAME: ${SYNAPSE_SERVER_NAME:-localhost:9999}
SYNAPSE_PUBLIC_BASEURL: ${SYNAPSE_PUBLIC_BASEURL:-http://localhost:8081}
SYNAPSE_POSTGRES_DB: ${SYNAPSE_POSTGRES_DB:-synapse-test-db}
SYNAPSE_ANTISPAM_BASE_URL: ${SYNAPSE_ANTISPAM_BASE_URL:-http://draupnir-integration:8082/api/1/spam_check}
PYTHONPATH: /data
SYNAPSE_REPORT_STATS: "no"
SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
volumes:
- synapse-data:/data
- ./scripts/synapse/homeserver.overrides.yaml:/etc/ci-box/homeserver.overrides.yaml:ro
- ./scripts/appservice/draupnir-registration.yaml:/etc/ci-box/draupnir-registration.yaml:ro
- ${SYNAPSE_HTTP_ANTISPAM_SOURCE:-../synapse-http-antispam/synapse_http_antispam.py}:/data/synapse_http_antispam.py:ro
command:
- -c
- |
if [ ! -f /data/homeserver.yaml ]; then
/start.py generate;
fi;
sed \
-e "s|__CI_BOX_SYNAPSE_SERVER_NAME__|$${SYNAPSE_SERVER_NAME}|g" \
-e "s|__CI_BOX_SYNAPSE_PUBLIC_BASEURL__|$${SYNAPSE_PUBLIC_BASEURL}|g" \
-e "s|__CI_BOX_SYNAPSE_ANTISPAM_BASE_URL__|$${SYNAPSE_ANTISPAM_BASE_URL}|g" \
-e "s|__CI_BOX_SYNAPSE_DB__|$${SYNAPSE_POSTGRES_DB}|g" \
/etc/ci-box/homeserver.overrides.yaml > /data/homeserver.yaml;
cp /etc/ci-box/draupnir-registration.yaml /data/draupnir-registration.yaml
restart: "no"
draupnir-build-lint:
build:
context: ${DRAUPNIR_CONTEXT:-../Draupnir}
dockerfile: ${DRAUPNIR_DOCKERFILE:-../Draupnir-Ci-in-a-box/Dockerfile-Draupnir-Develop}
args:
NODE_VERSION: ${NODE_VERSION:-24}
NODE_VARIANT: ${NODE_VARIANT:-slim}
image: ${DRAUPNIR_IMAGE:-draupnir/ci-box-draupnir:node${NODE_VERSION:-24}-${NODE_VARIANT:-slim}}
environment:
CI: "1"
NODE_ENV: development
working_dir: /workspace
command: ["sh", "-lc", "npm run build:all && npm run lint"]
profiles: [build-lint]
draupnir-unit:
build:
context: ${DRAUPNIR_CONTEXT:-../Draupnir}
dockerfile: ${DRAUPNIR_DOCKERFILE:-../Draupnir-Ci-in-a-box/Dockerfile-Draupnir-Develop}
args:
NODE_VERSION: ${NODE_VERSION:-24}
NODE_VARIANT: ${NODE_VARIANT:-slim}
image: ${DRAUPNIR_IMAGE:-draupnir/ci-box-draupnir:node${NODE_VERSION:-24}-${NODE_VARIANT:-slim}}
environment:
CI: "1"
NODE_ENV: development
working_dir: /workspace
command: ["sh", "-lc", "npm run build:all && npm test"]
profiles: [unit]
draupnir-integration:
build:
context: ${DRAUPNIR_CONTEXT:-../Draupnir}
dockerfile: ${DRAUPNIR_DOCKERFILE:-../Draupnir-Ci-in-a-box/Dockerfile-Draupnir-Develop}
args:
NODE_VERSION: ${NODE_VERSION:-24}
NODE_VARIANT: ${NODE_VARIANT:-slim}
image: ${DRAUPNIR_IMAGE:-draupnir/ci-box-draupnir:node${NODE_VERSION:-24}-${NODE_VARIANT:-slim}}
depends_on:
synapse:
condition: service_healthy
draupnir-config-init:
condition: service_completed_successfully
environment:
CI: "1"
NODE_ENV: harness
SYNAPSE_BASE_URL: http://synapse:8008
working_dir: /workspace
volumes:
- draupnir-config:/workspace/config
command: ["npm", "run", "-w", "apps/draupnir", "test:integration"]
profiles: [integration]
draupnir-appservice-integration:
build:
context: ${DRAUPNIR_CONTEXT:-../Draupnir}
dockerfile: ${DRAUPNIR_DOCKERFILE:-../Draupnir-Ci-in-a-box/Dockerfile-Draupnir-Develop}
args:
NODE_VERSION: ${NODE_VERSION:-24}
NODE_VARIANT: ${NODE_VARIANT:-slim}
image: ${DRAUPNIR_IMAGE:-draupnir/ci-box-draupnir:node${NODE_VERSION:-24}-${NODE_VARIANT:-slim}}
depends_on:
synapse:
condition: service_healthy
draupnir-config-init:
condition: service_completed_successfully
environment:
CI: "1"
NODE_ENV: harness
SYNAPSE_BASE_URL: http://synapse:8008
working_dir: /workspace
volumes:
- draupnir-config:/workspace/config
- ./scripts/appservice/draupnir-registration.yaml:/workspace/apps/draupnir/draupnir-registration.yaml:ro
- ./scripts/appservice/config.harness.yaml:/workspace/apps/draupnir/src/appservice/config/config.harness.yaml:ro
command:
["npm", "run", "-w", "apps/draupnir", "test:appservice:integration"]
profiles: [appservice-integration]
draupnir-config-init:
image: ${DRAUPNIR_IMAGE:-draupnir/ci-box-draupnir:node${NODE_VERSION:-24}-${NODE_VARIANT:-slim}}
volumes:
- draupnir-config:/config
- ${DRAUPNIR_CONFIG_SOURCE:-../Draupnir/config}:/source-config:ro
command:
- sh
- -lc
- >-
cp -a /source-config/. /config/; sed -i -e
"s|http://localhost:8081|http://nginx:8081|g" -e
"s|127.0.0.1:8081|nginx:8081|g" /config/harness.yaml
/config/default.yaml
restart: "no"
profiles: [integration, appservice-integration, development]
nginx:
image: ${NGINX_IMAGE:-nginx:stable-alpine}
depends_on:
synapse:
condition: service_healthy
volumes:
- ${REVERSE_PROXY_CONFIG_PATH:-./scripts/nginx/nginx.conf}:/etc/nginx/nginx.conf:ro
- ${REVERSE_PROXY_NEWS_PATH:-../Draupnir/apps/draupnir/test/draupnir_news.json}:/var/www/test/draupnir_news.json:ro
volumes:
postgres-data:
synapse-data:
draupnir-config:
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
#
# SPDX-License-Identifier: Apache-2.0
homeserver:
domain: "localhost:9999"
url: http://nginx:8081
db:
engine: "postgres"
connectionString: "postgres://mjolnir-tester:mjolnir-test@postgres:5432/mjolnir-test-db"
adminRoom: "#draupnir-admin:localhost:9999"
dataPath: "./test/harness/mjolnir-data/"
roomStateBackingStore:
enabled: false
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
#
# SPDX-License-Identifier: Apache-2.0
id: "draupnir-for-all"
as_token: "as_token"
hs_token: "hs_token"
url: "http://draupnir-appservice-integration:9000"
sender_localpart: draupnir-moderation
namespaces:
users:
- exclusive: true
regex: "@mjolnir_.*"
- exclusive: true
regex: "@draupnir_.*"
rate_limited: false
+43
View File
@@ -0,0 +1,43 @@
# SPDX-FileCopyrightText: 2024 Gnuxie <Gnuxie@protonmail.com>
#
# SPDX-License-Identifier: CC0-1.0
events {
}
http {
resolver 127.0.0.11 ipv6=off valid=5s;
server {
listen 8081;
location ~ ^/_matrix/client/(r0|v3)/rooms/([^/]*)/report/(.*)$ {
mirror /report_mirror;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,Keep-Alive,X-Requested-With,If-Modified-Since' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
add_header 'Access-Control-Max-Age' 1728000;
set $room_id $2;
set $event_id $3;
set $draupnir_integration_upstream draupnir-integration:8082;
proxy_pass http://$draupnir_integration_upstream/api/1/report/$room_id/$event_id;
}
location / {
proxy_pass http://synapse:8008;
}
location /report_mirror {
internal;
proxy_pass http://synapse:8008$request_uri;
}
location /draupnir_news.json {
root /var/www/test;
default_type application/json;
}
}
}
+12
View File
@@ -0,0 +1,12 @@
CREATE DATABASE "synapse-test-db"
WITH OWNER = "mjolnir-tester"
ENCODING = 'UTF8'
LC_COLLATE = 'C'
LC_CTYPE = 'C'
TEMPLATE = template0;
CREATE DATABASE "mjolnir-test-db"
WITH OWNER = "mjolnir-tester"
ENCODING = 'UTF8'
LC_COLLATE = 'C'
LC_CTYPE = 'C'
TEMPLATE = template0;
@@ -0,0 +1,96 @@
# SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
#
# SPDX-License-Identifier: Apache-2.0
server_name: "__CI_BOX_SYNAPSE_SERVER_NAME__"
pid_file: /data/homeserver.pid
listeners:
- port: 8008
resources:
- compress: false
names:
- client
- federation
tls: false
type: http
x_forwarded: true
database:
name: psycopg2
args:
user: "mjolnir-tester"
password: "mjolnir-test"
database: "__CI_BOX_SYNAPSE_DB__"
host: "postgres"
port: 5432
log_config: "/data/__CI_BOX_SYNAPSE_SERVER_NAME__.log.config"
media_store_path: /data/media_store
registration_shared_secret: "REGISTRATION_SHARED_SECRET"
report_stats: false
macaroon_secret_key: "Cee:o8O=k7hOp4C3;eyoTH~&SL;tVa.j8zBXF1zbWGd.fcRc-m"
form_secret: "_&Vo8k;b:bG9fTMGYSV0JFKUFKarjvhRif~:.dN^eiiI_pn:Y#"
signing_key_path: "/data/__CI_BOX_SYNAPSE_SERVER_NAME__.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
public_baseurl: "__CI_BOX_SYNAPSE_PUBLIC_BASEURL__"
enable_registration: true
enable_registration_without_verification: true
app_service_config_files:
- "/data/draupnir-registration.yaml"
modules:
- module: synapse_http_antispam.HTTPAntispam
config:
base_url: __CI_BOX_SYNAPSE_ANTISPAM_BASE_URL__
authorization: DEFAULT
do_ping: false
enabled_callbacks:
- user_may_invite
- user_may_join_room
- check_event_for_spam
fail_open:
user_may_invite: true
user_may_join_room: true
check_event_for_spam: true
rc_registration:
per_second: 10000
burst_count: 10000
rc_message:
per_second: 10000
burst_count: 10000
rc_invites:
per_room:
per_second: 10000
burst_count: 10000
per_user:
per_second: 10000
burst_count: 10000
per_sender:
per_second: 10000
burst_count: 10000
rc_login:
address:
per_second: 10000
burst_count: 10000
account:
per_second: 10000
burst_count: 10000
failed_attempts:
per_second: 10000
burst_count: 10000
rc_admin_redaction:
per_second: 10000
burst_count: 10000
rc_joins:
local:
per_second: 10000
burst_count: 10000
remote:
per_second: 10000
burst_count: 10000