Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions data/global.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
---
lookup_options:
'^profile::.+::sensitive_\w+$':
convert_to: 'Sensitive'
profiles::ssh_keys::additional_keys::user_list:
merge: deep

Expand Down
6 changes: 2 additions & 4 deletions data/nodes/matrix01.voxpupu.li.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
---
role: roles::matrix_server

profiles::ssh_keys::additional_keys::user_list:
- genebean
- ananace

classes:
- profiles::nginx
- profiles::matrix

profiles::borg::borg_includes:
- /srv/pg_dumps
8 changes: 8 additions & 0 deletions site/profiles/files/matrix/Dockerfile.synapse
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Dockerfile.synapse
# Extends the official Synapse image with the S3 storage provider.
# Build: docker build -f Dockerfile.synapse -t synapse-s3:latest .
# Then replace matrixdotorg/synapse:latest with synapse-s3:latest
# in docker-compose.yml for all synapse* and media-cron services.

FROM matrixdotorg/synapse:latest
RUN pip install --no-cache-dir synapse-s3-storage-provider
236 changes: 236 additions & 0 deletions site/profiles/files/matrix/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
services:
# ─────────────────────────────────────────
# DATABASE (PostgreSQL backend)
# ─────────────────────────────────────────
postgres:
image: postgres:16-alpine
restart: unless-stopped
env_file:
- .env
environment:
POSTGRES_DB: synapse
POSTGRES_USER: synapse
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required}
POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C
volumes:
- ./data/postgres:/var/lib/postgresql/data
healthcheck:
test:
- CMD-SHELL
- pg_isready -U synapse
interval: 10s
timeout: 5s
retries: 5
# ─────────────────────────────────────────
# REDIS (Replication bus for workers)
# ─────────────────────────────────────────
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --save "" --appendonly no
volumes:
- ./data/redis:/data
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 10s
timeout: 5s
retries: 5
# ─────────────────────────────────────────
# SYNAPSE MAIN PROCESS (Admin API, fallback routing, background tasks)
# ─────────────────────────────────────────
synapse-main:
build:
context: .
dockerfile: Dockerfile.synapse
image: synapse-s3-local:latest
restart: unless-stopped
env_file:
- .env
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
volumes:
- ./config/synapse:/config:ro
- ./data/synapse:/data
- ./data/media:/media
- ./logs/synapse:/logs
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
ports:
- 127.0.0.1:8008:8008
# ─────────────────────────────────────────
# WORKER: generic_worker1 (Client API endpoints)
# ─────────────────────────────────────────
synapse-worker1:
image: synapse-s3-local:latest
restart: unless-stopped
env_file:
- .env
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
SYNAPSE_WORKER_APP: synapse.app.generic_worker
SYNAPSE_WORKER_NAME: generic_worker1
SYNAPSE_WORKER_REPLICATION_HOST: synapse-main
SYNAPSE_WORKER_REPLICATION_HTTP_PORT: '9093'
SYNAPSE_WORKER_LISTENERS: "[{\"type\": \"http\", \"port\": 8081, \"bind_addresses\"\
: [\"0.0.0.0\"],\n \"resources\": [{\"names\": [\"client\", \"federation\"\
]}]},\n {\"type\": \"http\", \"port\": 9101, \"bind_addresses\": [\"127.0.0.1\"\
],\n \"resources\": [{\"names\": [\"metrics\"]}]}]\n"
volumes:
- ./config/synapse:/config:ro
- ./data/synapse:/data
- ./logs/synapse:/logs
depends_on:
- synapse-main
ports:
- 127.0.0.1:8081:8081
# ─────────────────────────────────────────
# WORKER: generic_worker2 (Client API endpoints)
# ─────────────────────────────────────────
synapse-worker2:
image: synapse-s3-local:latest
restart: unless-stopped
env_file:
- .env
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
SYNAPSE_WORKER_APP: synapse.app.generic_worker
SYNAPSE_WORKER_NAME: generic_worker2
SYNAPSE_WORKER_REPLICATION_HOST: synapse-main
SYNAPSE_WORKER_REPLICATION_HTTP_PORT: '9093'
SYNAPSE_WORKER_LISTENERS: "[{\"type\": \"http\", \"port\": 8082, \"bind_addresses\"\
: [\"0.0.0.0\"],\n \"resources\": [{\"names\": [\"client\", \"federation\"\
]}]},\n {\"type\": \"http\", \"port\": 9102, \"bind_addresses\": [\"127.0.0.1\"\
],\n \"resources\": [{\"names\": [\"metrics\"]}]}]\n"
volumes:
- ./config/synapse:/config:ro
- ./data/synapse:/data
- ./logs/synapse:/logs
depends_on:
- synapse-main
ports:
- 127.0.0.1:8082:8082
# ─────────────────────────────────────────
# WORKER: generic_worker3 (Federation sender and receiver)
# ─────────────────────────────────────────
synapse-worker3:
image: synapse-s3-local:latest
restart: unless-stopped
env_file:
- .env
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
SYNAPSE_WORKER_APP: synapse.app.generic_worker
SYNAPSE_WORKER_NAME: generic_worker3
SYNAPSE_WORKER_REPLICATION_HOST: synapse-main
SYNAPSE_WORKER_REPLICATION_HTTP_PORT: '9093'
SYNAPSE_WORKER_LISTENERS: "[{\"type\": \"http\", \"port\": 8083, \"bind_addresses\"\
: [\"0.0.0.0\"],\n \"resources\": [{\"names\": [\"federation\"]}]},\n {\"\
type\": \"http\", \"port\": 9103, \"bind_addresses\": [\"127.0.0.1\"],\n \
\ \"resources\": [{\"names\": [\"metrics\"]}]}]\n"
volumes:
- ./config/synapse:/config:ro
- ./data/synapse:/data
- ./logs/synapse:/logs
depends_on:
- synapse-main
ports:
- 127.0.0.1:8083:8083
# ─────────────────────────────────────────
# WORKER: generic_worker4 (Media endpoints and client Sync)
# ─────────────────────────────────────────
synapse-worker4:
image: synapse-s3-local:latest
restart: unless-stopped
env_file:
- .env
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
SYNAPSE_WORKER_APP: synapse.app.generic_worker
SYNAPSE_WORKER_NAME: generic_worker4
SYNAPSE_WORKER_REPLICATION_HOST: synapse-main
SYNAPSE_WORKER_REPLICATION_HTTP_PORT: '9093'
SYNAPSE_WORKER_LISTENERS: "[{\"type\": \"http\", \"port\": 8084, \"bind_addresses\"\
: [\"0.0.0.0\"],\n \"resources\": [{\"names\": [\"media\", \"client\"]}]},\n\
\ {\"type\": \"http\", \"port\": 9104, \"bind_addresses\": [\"127.0.0.1\"\
],\n \"resources\": [{\"names\": [\"metrics\"]}]}]\n"
volumes:
- ./config/synapse:/config:ro
- ./data/synapse:/data
- ./data/media:/media
- ./logs/synapse:/logs
depends_on:
- synapse-main
ports:
- 127.0.0.1:8084:8084
# ─────────────────────────────────────────
# STREAM WRITER: events_persister (Handles event persistence streams)
# ─────────────────────────────────────────
synapse-events-persister:
image: synapse-s3-local:latest
restart: unless-stopped
env_file:
- .env
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
SYNAPSE_WORKER_APP: synapse.app.generic_worker
SYNAPSE_WORKER_NAME: events_persister
SYNAPSE_WORKER_REPLICATION_HOST: synapse-main
SYNAPSE_WORKER_REPLICATION_HTTP_PORT: '9093'
SYNAPSE_WORKER_LISTENERS: "[{\"type\": \"http\", \"port\": 8085, \"bind_addresses\"\
: [\"0.0.0.0\"],\n \"resources\": [{\"names\": [\"replication\"]}]},\n {\"\
type\": \"http\", \"port\": 9105, \"bind_addresses\": [\"127.0.0.1\"],\n \
\ \"resources\": [{\"names\": [\"metrics\"]}]}]\n"
volumes:
- ./config/synapse:/config:ro
- ./data/synapse:/data
- ./logs/synapse:/logs
depends_on:
- synapse-main
# ─────────────────────────────────────────
# STREAM WRITER: receipts_writer (Handles typing/receipts/to-device streams)
# ─────────────────────────────────────────
synapse-receipts-writer:
image: synapse-s3-local:latest
restart: unless-stopped
env_file:
- .env
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
SYNAPSE_WORKER_APP: synapse.app.generic_worker
SYNAPSE_WORKER_NAME: receipts_writer
SYNAPSE_WORKER_REPLICATION_HOST: synapse-main
SYNAPSE_WORKER_REPLICATION_HTTP_PORT: '9093'
SYNAPSE_WORKER_LISTENERS: "[{\"type\": \"http\", \"port\": 8086, \"bind_addresses\"\
: [\"0.0.0.0\"],\n \"resources\": [{\"names\": [\"replication\"]}]},\n {\"\
type\": \"http\", \"port\": 9106, \"bind_addresses\": [\"127.0.0.1\"],\n \
\ \"resources\": [{\"names\": [\"metrics\"]}]}]\n"
volumes:
- ./config/synapse:/config:ro
- ./data/synapse:/data
- ./logs/synapse:/logs
depends_on:
- synapse-main
# ─────────────────────────────────────────
# MEDIA EVICTION CRON (S3 sync to Hetzner Object Storage)
# ─────────────────────────────────────────
media-cron:
image: synapse-s3-local:latest
restart: unless-stopped
env_file:
- .env
entrypoint: "sh -c \"while true; do\n python -m synapse.app.media_repository\
\ \\\n --config-path /config/homeserver.yaml \\\n --media-path /media\
\ \\\n --storage-providers s3; \\\n sleep 86400; \\\ndone\"\n"
environment:
SYNAPSE_CONFIG_PATH: /config/homeserver.yaml
volumes:
- ./config/synapse:/config:ro
- ./data/media:/media
depends_on:
- synapse-main
109 changes: 27 additions & 82 deletions site/profiles/manifests/matrix.pp
Original file line number Diff line number Diff line change
@@ -1,83 +1,28 @@
class profiles::matrix {
$domain = 'voxpupuli.party'
$root = "/srv/${domain}"
file { $root:
ensure => 'directory',
}
file { "${root}/.well-known":
ensure => 'directory',
}
file { "${root}/.well-known/matrix/":
ensure => 'directory',
}
file { "${root}/.well-known/matrix/client":
ensure => 'file',
}
file { "${root}/.well-known/matrix/server":
ensure => 'file',
}
require nftables::rules::http
require nftables::rules::https

if fact("letsencrypt_directory.\"${domain}\"") {
$path = fact("letsencrypt_directory.\"${domain}\"")
$ssl = {
ssl_key => "${path}/privkey.pem",
ssl_cert => "${path}/fullchain.pem",
ssl => true,
ssl_port => 443,
ssl_session_timeout => '1d',
ssl_session_tickets => 'off',
ssl_protocols => 'TLSv1.2 TLSv1.3',
ssl_prefer_server_ciphers => 'off',
add_header => {
'Strict-Transport-Security' => {
'max-age=63072000' => 'always',
},
},
}
} else {
$ssl = {}
}
nginx::resource::server { $domain:
listen_port => 80,
server_name => [$domain],
ipv6_enable => true,
ipv6_listen_options => '', # when using IPv4 & IPv6 the default options break Nginx
http2 => 'on',
access_log => "/var/log/nginx/${domain}.access.log",
error_log => "/var/log/nginx/${domain}.error.log",
format_log => 'combined',
use_default_location => false,
index_files => [],
www_root => $root,
* => $ssl,
}
nginx::resource::location { "${domain} Let's Encrypt challenges":
server => $domain,
www_root => '/var/lib/letsencrypt/',
index_files => [],
location => '^~ /.well-known/acme-challenge/',
}
nginx::resource::location { "${domain} acme-challenge directory":
server => $domain,
location_cfg_append => { 'return' => '404', },
index_files => [],
location => '= /.well-known/acme-challenge/',
}
letsencrypt::certonly { $domain:
domains => [$domain],
plugin => 'nginx',
manage_cron => true,
additional_args => [
'--no-redirect',
],
require => [
Nginx::Resource::Server[$domain],
Nginx::Resource::Location[
"${domain} Let's Encrypt challenges",
"${domain} acme-challenge directory",
],
],
}
# @summary A wrapper profile to set up Matrix Synapse and its required services
#
# This profile serves as the entry point for configuring the Matrix server. It configures
# Nginx as a reverse proxy, provisions SSL certificates, and orchestrates the Synapse
# Docker backend with Hetzner S3 object storage for media eviction and federation routing.
#
# @param sensitive_postgres_password The password for the Synapse PostgreSQL database.
# @param sensitive_macaroon_secret_key The secret key used for signing macaroons.
# @param sensitive_form_secret The secret key used for form validation.
# @param sensitive_s3_access_key The access key for the S3 object storage bucket.
# @param sensitive_s3_secret_key The secret key for the S3 object storage bucket.
# @param s3_bucket The name of the S3 bucket to act as the media store.
# @param s3_region The region where the S3 bucket is hosted.
# @param s3_endpoint The endpoint URL for the S3 object storage.
#
class profiles::matrix (
Sensitive[String] $sensitive_postgres_password,
Sensitive[String] $sensitive_macaroon_secret_key,
Sensitive[String] $sensitive_form_secret,
Sensitive[String] $sensitive_s3_access_key,
Sensitive[String] $sensitive_s3_secret_key,
String $s3_bucket,
String $s3_region,
String $s3_endpoint,
) {
include profiles::matrix::nginx
include profiles::matrix::synapse
}
Loading
Loading