Priority 1 - Production Quality: - Revert Matrix homeserver log level from debug to info - Reduces log volume by ~70% (22k+ lines/day to <7k) - Improves performance and reduces disk usage Priority 2 - Technical Debt: - Automate sender_localpart fix in mautrix-slack.nix - Eliminates manual sed command on fresh deployments - Fix verified working (tested 2025-10-26) - Update CLAUDE.md to document automated solution Priority 3 - Project Hygiene: - Remove unused mautrix-whatsapp and mautrix-gmessages imports - Archive old configurations to docs/examples/alternative-deployments/ - Remove stale staging/ directories from 001 extraction workflow - Update deployment documentation in tasks.md and quickstart.md - Add deployment status notes to spec files Files Modified: - modules/dev-services.nix: log level debug → info - modules/mautrix-slack.nix: automatic sender_localpart fix - hosts/ops-jrz1.nix: remove unused bridge imports - CLAUDE.md: update Known Issues, add Resolved Issues section - specs/002-*/: add deployment status notes - configurations/ → docs/examples/alternative-deployments/ Tested and Verified: - All services running (matrix, bridge, forgejo, postgresql, nginx) - Bridge authenticated and message flow working - sender_localpart fix generates correct registration file
312 lines
8.7 KiB
Nix
312 lines
8.7 KiB
Nix
# Development services module - Matrix, Forgejo, and Slack bridge
|
|
{ config, pkgs, pkgs-unstable, lib, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.dev-platform;
|
|
in
|
|
{
|
|
options.services.dev-platform = {
|
|
enable = mkEnableOption "development platform with Matrix and Forgejo";
|
|
|
|
domain = mkOption {
|
|
type = types.str;
|
|
default = "localhost";
|
|
description = "Base domain for services";
|
|
};
|
|
|
|
matrix = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable Matrix server";
|
|
};
|
|
|
|
serverName = mkOption {
|
|
type = types.str;
|
|
default = cfg.domain;
|
|
description = "Matrix server name";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 8008;
|
|
description = "Matrix server port";
|
|
};
|
|
};
|
|
|
|
forgejo = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable Forgejo git service";
|
|
};
|
|
|
|
subdomain = mkOption {
|
|
type = types.str;
|
|
default = "git";
|
|
description = "Subdomain for Forgejo";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 3000;
|
|
description = "Forgejo port";
|
|
};
|
|
};
|
|
|
|
slackBridge = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enable Slack bridge";
|
|
};
|
|
|
|
workspace = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
description = "Slack workspace name";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 29319;
|
|
description = "Slack bridge port";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
# PostgreSQL for Forgejo and bridge services (Matrix uses RocksDB)
|
|
services.postgresql = {
|
|
enable = true;
|
|
ensureDatabases = [
|
|
"forgejo"
|
|
] ++ optional cfg.slackBridge.enable "mautrix_slack";
|
|
|
|
ensureUsers = [
|
|
{
|
|
name = "forgejo";
|
|
ensureDBOwnership = true;
|
|
}
|
|
] ++ optional cfg.slackBridge.enable {
|
|
name = "mautrix_slack";
|
|
ensureDBOwnership = true;
|
|
};
|
|
};
|
|
|
|
# Matrix Continuwuity server
|
|
systemd.services.matrix-continuwuity = mkIf cfg.matrix.enable {
|
|
description = "Continuwuity Matrix homeserver";
|
|
after = [ "network.target" ];
|
|
wants = [ "network-online.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
preStart = ''
|
|
# Generate config file at runtime with secret injection from systemd credentials
|
|
cat > /var/lib/matrix-continuwuity/continuwuity.toml <<EOF
|
|
[global]
|
|
server_name = "${cfg.matrix.serverName}"
|
|
address = "127.0.0.1"
|
|
port = ${toString cfg.matrix.port}
|
|
allow_registration = true
|
|
registration_token = "$(cat $CREDENTIALS_DIRECTORY/matrix-registration-token)"
|
|
allow_encryption = true
|
|
allow_federation = false
|
|
database_backend = "rocksdb"
|
|
database_path = "/var/lib/matrix-continuwuity/db/"
|
|
log = "info"
|
|
admin_room_tag = "m.server_notice"
|
|
EOF
|
|
'';
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
DynamicUser = true;
|
|
StateDirectory = "matrix-continuwuity";
|
|
WorkingDirectory = "/var/lib/matrix-continuwuity";
|
|
|
|
# Load secrets via systemd credentials (proper way for DynamicUser)
|
|
LoadCredential = "matrix-registration-token:/run/secrets/matrix-registration-token";
|
|
|
|
ExecStart = "${pkgs-unstable.matrix-continuwuity}/bin/conduwuit -c /var/lib/matrix-continuwuity/continuwuity.toml";
|
|
|
|
Restart = "always";
|
|
RestartSec = "10s";
|
|
|
|
# Security hardening
|
|
NoNewPrivileges = true;
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
PrivateTmp = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
};
|
|
};
|
|
|
|
# Forgejo git service
|
|
services.forgejo = mkIf cfg.forgejo.enable {
|
|
enable = true;
|
|
stateDir = "/var/lib/forgejo";
|
|
|
|
database = {
|
|
type = "postgres";
|
|
host = "/run/postgresql";
|
|
name = "forgejo";
|
|
user = "forgejo";
|
|
createDatabase = false; # We handle this with ensureDatabases
|
|
};
|
|
|
|
settings = {
|
|
server = {
|
|
DOMAIN = "${cfg.forgejo.subdomain}.${cfg.domain}";
|
|
ROOT_URL = "https://${cfg.forgejo.subdomain}.${cfg.domain}";
|
|
PROTOCOL = "http";
|
|
HTTP_ADDR = "127.0.0.1";
|
|
HTTP_PORT = cfg.forgejo.port;
|
|
};
|
|
|
|
service = {
|
|
DISABLE_REGISTRATION = true; # Disable public registration
|
|
DEFAULT_KEEP_EMAIL_PRIVATE = true;
|
|
DEFAULT_ALLOW_CREATE_ORGANIZATION = true;
|
|
};
|
|
|
|
log = {
|
|
LEVEL = "Info";
|
|
MODE = "console";
|
|
};
|
|
|
|
repository = {
|
|
DEFAULT_BRANCH = "main";
|
|
};
|
|
|
|
ui = {
|
|
DEFAULT_THEME = "arc-green";
|
|
};
|
|
|
|
# Enable Actions for CI/CD
|
|
actions = {
|
|
ENABLED = true;
|
|
};
|
|
};
|
|
|
|
lfs.enable = true;
|
|
};
|
|
|
|
# mautrix-slack bridge
|
|
services.mautrix-slack = mkIf cfg.slackBridge.enable {
|
|
enable = true;
|
|
package = pkgs-unstable.mautrix-slack or (pkgs-unstable.callPackage ../pkgs/mautrix-slack {});
|
|
|
|
matrix = {
|
|
homeserverUrl = "http://127.0.0.1:${toString cfg.matrix.port}";
|
|
serverName = cfg.matrix.serverName;
|
|
};
|
|
|
|
appservice = {
|
|
port = cfg.slackBridge.port;
|
|
hostname = "127.0.0.1";
|
|
};
|
|
|
|
database = {
|
|
type = "postgres";
|
|
uri = "postgresql:///mautrix_slack?host=/run/postgresql";
|
|
};
|
|
|
|
bridge.permissions = {
|
|
"${cfg.matrix.serverName}" = "user";
|
|
};
|
|
};
|
|
|
|
# Basic Nginx reverse proxy
|
|
services.nginx = {
|
|
enable = true;
|
|
recommendedProxySettings = true;
|
|
recommendedTlsSettings = true;
|
|
recommendedOptimisation = true;
|
|
recommendedGzipSettings = true;
|
|
|
|
# Rate limiting for login endpoints
|
|
appendHttpConfig = ''
|
|
limit_req_zone $binary_remote_addr zone=logins:10m rate=5r/m;
|
|
'';
|
|
|
|
virtualHosts = {
|
|
# Matrix endpoints
|
|
"${cfg.domain}" = mkIf cfg.matrix.enable {
|
|
enableACME = true;
|
|
forceSSL = true;
|
|
extraConfig = ''
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
'';
|
|
locations = {
|
|
"~ ^/_matrix/client/(r0|v3)/login" = {
|
|
proxyPass = "http://127.0.0.1:${toString cfg.matrix.port}";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
limit_req zone=logins burst=10 nodelay;
|
|
'';
|
|
};
|
|
"/_matrix" = {
|
|
proxyPass = "http://127.0.0.1:${toString cfg.matrix.port}";
|
|
proxyWebsockets = true;
|
|
};
|
|
"/.well-known/matrix/server" = {
|
|
extraConfig = ''
|
|
add_header Content-Type application/json;
|
|
return 200 '{"m.server": "${cfg.matrix.serverName}:443"}';
|
|
'';
|
|
};
|
|
"/.well-known/matrix/client" = {
|
|
extraConfig = ''
|
|
add_header Content-Type application/json;
|
|
add_header Access-Control-Allow-Origin *;
|
|
return 200 '{"m.homeserver": {"base_url": "https://${cfg.domain}"}}';
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
# Forgejo git service (subdomain-based, requires DNS)
|
|
"${cfg.forgejo.subdomain}.${cfg.domain}" = mkIf cfg.forgejo.enable {
|
|
enableACME = true;
|
|
forceSSL = true;
|
|
extraConfig = ''
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
'';
|
|
locations."/user/login" = {
|
|
proxyPass = "http://127.0.0.1:${toString cfg.forgejo.port}";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
limit_req zone=logins burst=10 nodelay;
|
|
'';
|
|
};
|
|
locations."/user/sign_up" = {
|
|
proxyPass = "http://127.0.0.1:${toString cfg.forgejo.port}";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
limit_req zone=logins burst=10 nodelay;
|
|
'';
|
|
};
|
|
locations."/" = {
|
|
proxyPass = "http://127.0.0.1:${toString cfg.forgejo.port}";
|
|
proxyWebsockets = true;
|
|
extraConfig = ''
|
|
client_max_body_size 512M;
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
# Systemd tmpfiles for data directories
|
|
# Note: matrix-continuwuity directory managed by StateDirectory with DynamicUser
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/forgejo 0750 forgejo forgejo -"
|
|
] ++ optional cfg.slackBridge.enable
|
|
"d /var/lib/mautrix-slack 0750 mautrix_slack mautrix_slack -";
|
|
};
|
|
} |