Merge branch 'master' of ssh://git.joshuabell.xyz:3032/ringofstorms/dotfiles
This commit is contained in:
commit
4851c4358b
5 changed files with 522 additions and 0 deletions
|
|
@ -20,6 +20,8 @@
|
|||
"etebase"
|
||||
"photos"
|
||||
"location"
|
||||
"matrix"
|
||||
"element"
|
||||
];
|
||||
|
||||
# Base domain
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
./dawarich.nix
|
||||
./forgejo.nix
|
||||
./immich.nix
|
||||
./matrix.nix
|
||||
./opengist.nix
|
||||
./zitadel.nix
|
||||
];
|
||||
|
|
|
|||
501
hosts/h001/containers/matrix.nix
Normal file
501
hosts/h001/containers/matrix.nix
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
name = "matrix";
|
||||
hostDataDir = "/var/lib/${name}";
|
||||
hostAddress = "10.0.0.1";
|
||||
containerAddress = "10.0.0.6";
|
||||
|
||||
# Use unstable nixpkgs for the container (mautrix-gmessages module not in stable)
|
||||
matrixNixpkgs = inputs.matrix-nixpkgs;
|
||||
|
||||
# Matrix server configuration
|
||||
serverName = "matrix.joshuabell.xyz";
|
||||
elementDomain = "element.joshuabell.xyz";
|
||||
|
||||
# Bind mount definitions following forgejo.nix pattern
|
||||
binds = [
|
||||
{
|
||||
host = "${hostDataDir}/postgres";
|
||||
container = "/var/lib/postgresql/17";
|
||||
user = "postgres";
|
||||
uid = 71;
|
||||
gid = 71;
|
||||
}
|
||||
{
|
||||
host = "${hostDataDir}/backups";
|
||||
container = "/var/backup/postgresql";
|
||||
user = "postgres";
|
||||
uid = 71;
|
||||
gid = 71;
|
||||
}
|
||||
{
|
||||
host = "${hostDataDir}/dendrite";
|
||||
container = "/var/lib/dendrite";
|
||||
user = "dendrite";
|
||||
uid = 993;
|
||||
gid = 993;
|
||||
}
|
||||
{
|
||||
host = "${hostDataDir}/gmessages";
|
||||
container = "/var/lib/mautrix_gmessages";
|
||||
user = "mautrix_gmessages";
|
||||
uid = 992;
|
||||
gid = 992;
|
||||
}
|
||||
];
|
||||
|
||||
uniqueUsers = lib.unique (map (b: { inherit (b) user uid gid; }) binds);
|
||||
|
||||
# Element Web configuration - points to our server, registration disabled
|
||||
elementConfig = {
|
||||
default_server_config = {
|
||||
"m.homeserver" = {
|
||||
base_url = "https://${serverName}";
|
||||
server_name = serverName;
|
||||
};
|
||||
};
|
||||
disable_guests = true;
|
||||
disable_login_language_selector = false;
|
||||
disable_3pid_login = true;
|
||||
brand = "Element";
|
||||
integrations_ui_url = "";
|
||||
integrations_rest_url = "";
|
||||
integrations_widgets_urls = [ ];
|
||||
show_labs_settings = false;
|
||||
room_directory = {
|
||||
servers = [ serverName ];
|
||||
};
|
||||
# Security: disable features that could leak data
|
||||
enable_presence_by_hs_url = { };
|
||||
permalink_prefix = "https://${elementDomain}";
|
||||
};
|
||||
|
||||
elementConfigFile = pkgs.writeText "element-config.json" (builtins.toJSON elementConfig);
|
||||
|
||||
# Custom Element Web with our config
|
||||
elementWebCustom = pkgs.runCommand "element-web-custom" { } ''
|
||||
cp -r ${pkgs.element-web} $out
|
||||
chmod -R u+w $out
|
||||
rm $out/config.json
|
||||
cp ${elementConfigFile} $out/config.json
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
# Create host directories and users
|
||||
system.activationScripts."${name}-dirs" = lib.stringAfter [ "users" "groups" ] ''
|
||||
${lib.concatMapStringsSep "\n" (b: ''
|
||||
mkdir -p ${b.host}
|
||||
chown ${toString b.uid}:${toString b.gid} ${b.host}
|
||||
'') binds}
|
||||
'';
|
||||
|
||||
# Create users/groups on host for bind mount permissions
|
||||
users.users = lib.listToAttrs (
|
||||
map (u: {
|
||||
name = u.user;
|
||||
value = {
|
||||
isSystemUser = true;
|
||||
uid = u.uid;
|
||||
group = u.user;
|
||||
};
|
||||
}) (lib.filter (u: u.user != "postgres") uniqueUsers)
|
||||
);
|
||||
|
||||
users.groups = lib.listToAttrs (
|
||||
map (u: {
|
||||
name = u.user;
|
||||
value = {
|
||||
gid = u.gid;
|
||||
};
|
||||
}) (lib.filter (u: u.user != "postgres") uniqueUsers)
|
||||
);
|
||||
|
||||
# nginx reverse proxy on host
|
||||
services.nginx.virtualHosts = {
|
||||
# Matrix server - handles client and federation API
|
||||
"${serverName}" = {
|
||||
forceSSL = true;
|
||||
useACMEHost = "joshuabell.xyz";
|
||||
|
||||
# .well-known for Matrix federation discovery
|
||||
locations."= /.well-known/matrix/server" = {
|
||||
return = ''200 '{"m.server": "${serverName}:443"}' '';
|
||||
extraConfig = ''
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
'';
|
||||
};
|
||||
|
||||
locations."= /.well-known/matrix/client" = {
|
||||
return = ''200 '{"m.homeserver": {"base_url": "https://${serverName}"}}' '';
|
||||
extraConfig = ''
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
'';
|
||||
};
|
||||
|
||||
# Matrix client and federation API
|
||||
locations."/_matrix" = {
|
||||
proxyPass = "http://${containerAddress}:8008";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
proxy_read_timeout 600s;
|
||||
client_max_body_size 50M;
|
||||
'';
|
||||
};
|
||||
|
||||
# Dendrite admin API (only accessible internally)
|
||||
locations."/_dendrite" = {
|
||||
proxyPass = "http://${containerAddress}:8008";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
|
||||
# Default location - redirect to Element
|
||||
locations."/" = {
|
||||
return = "301 https://${elementDomain}";
|
||||
};
|
||||
};
|
||||
|
||||
# Element Web client
|
||||
"${elementDomain}" = {
|
||||
forceSSL = true;
|
||||
useACMEHost = "joshuabell.xyz";
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://${containerAddress}:80";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# The container
|
||||
containers.${name} = {
|
||||
ephemeral = true;
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostAddress = hostAddress;
|
||||
localAddress = containerAddress;
|
||||
nixpkgs = matrixNixpkgs;
|
||||
|
||||
bindMounts = lib.listToAttrs (
|
||||
map (b: {
|
||||
name = b.container;
|
||||
value = {
|
||||
hostPath = b.host;
|
||||
isReadOnly = false;
|
||||
};
|
||||
}) binds
|
||||
);
|
||||
|
||||
config =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
system.stateVersion = "24.11";
|
||||
|
||||
# Allow olm - required by mautrix-gmessages. The security issues are
|
||||
# side-channel attacks on E2EE crypto, but SMS/RCS isn't E2EE through
|
||||
# the bridge anyway (RCS encryption is handled by Google Messages).
|
||||
nixpkgs.config.permittedInsecurePackages = [
|
||||
"olm-3.2.16"
|
||||
];
|
||||
|
||||
networking = {
|
||||
firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [
|
||||
8008 # Dendrite Matrix API
|
||||
80 # Element Web (nginx)
|
||||
];
|
||||
};
|
||||
useHostResolvConf = lib.mkForce false;
|
||||
};
|
||||
|
||||
services.resolved.enable = true;
|
||||
|
||||
# PostgreSQL for Dendrite and mautrix-gmessages
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
package = pkgs.postgresql_17;
|
||||
ensureDatabases = [
|
||||
"dendrite"
|
||||
"mautrix_gmessages"
|
||||
];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "dendrite";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
{
|
||||
name = "mautrix_gmessages";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
# Only allow local connections - no network access
|
||||
enableTCPIP = false;
|
||||
authentication = ''
|
||||
local all all peer
|
||||
'';
|
||||
};
|
||||
|
||||
# PostgreSQL backup
|
||||
services.postgresqlBackup = {
|
||||
enable = true;
|
||||
databases = [
|
||||
"dendrite"
|
||||
"mautrix_gmessages"
|
||||
];
|
||||
};
|
||||
|
||||
# Dendrite Matrix homeserver
|
||||
services.dendrite = {
|
||||
enable = true;
|
||||
httpPort = 8008;
|
||||
|
||||
# Load signing key from file (generated on first boot)
|
||||
loadCredential = [ "signing_key:/var/lib/dendrite/matrix_key.pem" ];
|
||||
|
||||
settings = {
|
||||
global = {
|
||||
server_name = serverName;
|
||||
private_key = "$CREDENTIALS_DIRECTORY/signing_key";
|
||||
|
||||
# Security: Disable federation to keep messages private
|
||||
# Set to true if you want to chat with users on other Matrix servers
|
||||
disable_federation = true;
|
||||
|
||||
database = {
|
||||
connection_string = "postgresql:///dendrite?host=/run/postgresql";
|
||||
max_open_conns = 50;
|
||||
max_idle_conns = 5;
|
||||
conn_max_lifetime = -1;
|
||||
};
|
||||
|
||||
# Security: strict DNS caching
|
||||
dns_cache = {
|
||||
enabled = true;
|
||||
cache_size = 256;
|
||||
cache_lifetime = "5m";
|
||||
};
|
||||
};
|
||||
|
||||
# Client API configuration
|
||||
client_api = {
|
||||
# Security: Disable registration - only admin can create accounts
|
||||
registration_disabled = true;
|
||||
|
||||
# Security: Disable guest access
|
||||
guests_disabled = true;
|
||||
|
||||
# Rate limiting
|
||||
rate_limiting = {
|
||||
enabled = true;
|
||||
threshold = 20;
|
||||
cooloff_ms = 500;
|
||||
exempt_user_ids = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
# Federation API - disabled for privacy
|
||||
federation_api = {
|
||||
# Security: No federation means messages stay on your server only
|
||||
disable_tls_validation = false;
|
||||
disable_http_keepalives = false;
|
||||
send_max_retries = 16;
|
||||
key_perspectives = [ ];
|
||||
};
|
||||
|
||||
# Media API
|
||||
media_api = {
|
||||
base_path = "/var/lib/dendrite/media";
|
||||
max_file_size_bytes = 52428800; # 50MB
|
||||
dynamic_thumbnails = true;
|
||||
};
|
||||
|
||||
# Sync API
|
||||
sync_api = {
|
||||
real_ip_header = "X-Forwarded-For";
|
||||
search = {
|
||||
enabled = true;
|
||||
index_path = "/var/lib/dendrite/searchindex";
|
||||
};
|
||||
};
|
||||
|
||||
# User API
|
||||
user_api = {
|
||||
bcrypt_cost = 12; # Security: higher bcrypt cost
|
||||
};
|
||||
|
||||
# MSCs (Matrix Spec Changes) - enable useful ones
|
||||
mscs = {
|
||||
mscs = [
|
||||
"msc2836" # Threading
|
||||
"msc2946" # Spaces
|
||||
];
|
||||
};
|
||||
|
||||
# Logging
|
||||
logging = [
|
||||
{
|
||||
type = "std";
|
||||
level = "warn";
|
||||
}
|
||||
];
|
||||
|
||||
# App services (bridges) - will be configured below
|
||||
app_service_api = {
|
||||
database = {
|
||||
connection_string = "postgresql:///dendrite?host=/run/postgresql";
|
||||
max_open_conns = 10;
|
||||
max_idle_conns = 2;
|
||||
conn_max_lifetime = -1;
|
||||
};
|
||||
config_files = [
|
||||
"/var/lib/mautrix_gmessages/registration.yaml"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Generate Dendrite signing key if it doesn't exist
|
||||
systemd.services.dendrite-keygen = {
|
||||
description = "Generate Dendrite signing key";
|
||||
wantedBy = [ "dendrite.service" ];
|
||||
before = [ "dendrite.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "dendrite";
|
||||
Group = "dendrite";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
if [ ! -f /var/lib/dendrite/matrix_key.pem ]; then
|
||||
${pkgs.dendrite}/bin/generate-keys --private-key /var/lib/dendrite/matrix_key.pem
|
||||
chmod 600 /var/lib/dendrite/matrix_key.pem
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
# mautrix-gmessages bridge (manual service - no NixOS module exists)
|
||||
systemd.services.mautrix-gmessages = {
|
||||
description = "mautrix-gmessages Matrix-Google Messages bridge";
|
||||
after = [
|
||||
"network.target"
|
||||
"dendrite.service"
|
||||
"postgresql.service"
|
||||
"mautrix-gmessages-init.service"
|
||||
];
|
||||
requires = [ "postgresql.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "mautrix_gmessages";
|
||||
Group = "mautrix_gmessages";
|
||||
ExecStart = "${pkgs.mautrix-gmessages}/bin/mautrix-gmessages -c /var/lib/mautrix_gmessages/config.yaml";
|
||||
Restart = "on-failure";
|
||||
RestartSec = "10s";
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ReadWritePaths = [ "/var/lib/mautrix_gmessages" ];
|
||||
StateDirectory = "mautrix_gmessages";
|
||||
};
|
||||
};
|
||||
|
||||
# Generate mautrix-gmessages config if it doesn't exist
|
||||
systemd.services.mautrix-gmessages-init = {
|
||||
description = "Initialize mautrix-gmessages configuration";
|
||||
wantedBy = [ "mautrix-gmessages.service" ];
|
||||
before = [ "mautrix-gmessages.service" ];
|
||||
after = [ "postgresql.service" ];
|
||||
requires = [ "postgresql.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "mautrix_gmessages";
|
||||
Group = "mautrix_gmessages";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
|
||||
script = ''
|
||||
CONFIG_DIR="/var/lib/mautrix_gmessages"
|
||||
CONFIG_FILE="$CONFIG_DIR/config.yaml"
|
||||
REG_FILE="$CONFIG_DIR/registration.yaml"
|
||||
|
||||
# Generate example config if none exists
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
${pkgs.mautrix-gmessages}/bin/mautrix-gmessages -c "$CONFIG_FILE" -g
|
||||
|
||||
# Patch the generated config with our settings
|
||||
${pkgs.yq-go}/bin/yq -i '
|
||||
.homeserver.address = "http://localhost:8008" |
|
||||
.homeserver.domain = "${serverName}" |
|
||||
.appservice.database.type = "postgres" |
|
||||
.appservice.database.uri = "postgresql:///mautrix_gmessages?host=/run/postgresql" |
|
||||
.appservice.hostname = "127.0.0.1" |
|
||||
.appservice.port = 29336 |
|
||||
.appservice.id = "gmessages" |
|
||||
.appservice.bot.username = "gmessagesbot" |
|
||||
.appservice.bot.displayname = "Google Messages Bridge" |
|
||||
.bridge.permissions."${serverName}" = "user" |
|
||||
.bridge.permissions."@josh:${serverName}" = "admin" |
|
||||
.bridge.delivery_receipts = true |
|
||||
.bridge.sync_direct_chat_list = true |
|
||||
.logging.min_level = "warn"
|
||||
' "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Generate registration file if none exists
|
||||
if [ ! -f "$REG_FILE" ]; then
|
||||
${pkgs.mautrix-gmessages}/bin/mautrix-gmessages -c "$CONFIG_FILE" -r "$REG_FILE"
|
||||
chmod 640 "$REG_FILE"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
# Create user/group for mautrix_gmessages
|
||||
users.users.mautrix_gmessages = {
|
||||
isSystemUser = true;
|
||||
group = "mautrix_gmessages";
|
||||
home = "/var/lib/mautrix_gmessages";
|
||||
};
|
||||
|
||||
users.groups.mautrix_gmessages = { };
|
||||
|
||||
# nginx inside container for Element Web
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."element" = {
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = 80;
|
||||
}
|
||||
];
|
||||
root = elementWebCustom;
|
||||
locations."/" = {
|
||||
tryFiles = "$uri $uri/ /index.html";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Ensure directories exist with proper permissions
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/dendrite 0750 dendrite dendrite -"
|
||||
"d /var/lib/dendrite/media 0750 dendrite dendrite -"
|
||||
"d /var/lib/dendrite/searchindex 0750 dendrite dendrite -"
|
||||
"d /var/lib/mautrix_gmessages 0750 mautrix_gmessages mautrix_gmessages -"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
17
hosts/h001/flake.lock
generated
17
hosts/h001/flake.lock
generated
|
|
@ -257,6 +257,22 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"matrix-nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1770562336,
|
||||
"narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d6c71932130818840fc8fe9509cf50be8c64634f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"n8n-nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769170682,
|
||||
|
|
@ -1335,6 +1351,7 @@
|
|||
"home-manager": "home-manager",
|
||||
"immich-nixpkgs": "immich-nixpkgs",
|
||||
"litellm-nixpkgs": "litellm-nixpkgs",
|
||||
"matrix-nixpkgs": "matrix-nixpkgs",
|
||||
"n8n-nixpkgs": "n8n-nixpkgs",
|
||||
"nixarr": "nixarr",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
n8n-nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
dawarich-nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
immich-nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
matrix-nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
|
||||
# Use relative to get current version for testing
|
||||
# common.url = "path:../../flakes/common";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue