Merge branch 'alpine/master' into framework/master

This commit is contained in:
tristan 2025-04-29 16:29:27 +01:00
commit 01f1ee2c96
20 changed files with 472 additions and 134 deletions

View file

@ -8,9 +8,6 @@
}: let
user = config.user;
in {
imports = [
./modules/podman.nix
];
nix = {
settings = {

77
nixos/services/arr.nix Normal file
View file

@ -0,0 +1,77 @@
{config, lib, ...}: let
inherit (config) sops;
inherit (sops) templates placeholder;
in {
nixpkgs.config.permittedInsecurePackages = [
"aspnetcore-runtime-6.0.36"
"aspnetcore-runtime-wrapped-6.0.36"
"dotnet-sdk-6.0.428"
"dotnet-sdk-wrapped-6.0.428"
];
users.groups.media = {};
services.jackett = {
enable = true;
};
services.lidarr = {
enable = true;
group = "media";
};
services.sonarr = {
enable = true;
group = "media";
};
services.radarr = {
enable = true;
group = "media";
};
services.jellyseerr.enable = true;
sops.secrets.sonarr-sslkey = {
sopsFile = ../../certs/alpine.prawn-justice.ts.net.key;
format = "binary";
owner = "nginx";
};
# this was fun to figure out, but pointless atm.
services.nginx.virtualHosts."alpine.prawn-justice.ts.net" = {
forceSSL = true;
sslCertificateKey = config.sops.secrets.sonarr-sslkey.path;
sslCertificate = ../../certs/alpine.prawn-justice.ts.net.crt;
};
# probably easier if i just put this in a nixos-container
virtualisation.oci-containers.containers.transmission = {
autoStart = false;
image = "docker.io/haugene/transmission-openvpn:5.3.1";
ports = ["9091:9091"];
volumes = [
"/mnt/storage/downloads:/data"
"/home/tristan/pods/transmission/config:/config"
"/mnt/storage/media/unsorted:/data/completed"
];
environmentFiles = [ templates."transmission/env".path ];
environment = {
PUID = "1000";
GUID = "1000";
LOCAL_NETWORK = "100.0.0.0/8";
};
privileged = true;
capabilities = {
"NET_ADMIN" = true;
"NET_RAW" = true;
"MKNOD" = true;
};
};
sops.secrets = {
"transmission/auth/OPENVPN_PROVIDER" = {};
"transmission/auth/OPENVPN_CONFIG" = {};
"transmission/auth/OPENVPN_USERNAME" = {};
"transmission/auth/OPENVPN_PASSWORD" = {};
};
sops.templates."transmission/env" = {
owner = "tristan";
content = ''
OPENVPN_PROVIDER="${placeholder."transmission/auth/OPENVPN_PROVIDER"}"
OPENVPN_CONFIG="${placeholder."transmission/auth/OPENVPN_CONFIG"}"
OPENVPN_USERNAME="${placeholder."transmission/auth/OPENVPN_USERNAME"}"
OPENVPN_PASSWORD="${placeholder."transmission/auth/OPENVPN_PASSWORD"}"
'';
};
}

View file

@ -9,7 +9,8 @@
port = "5437";
};
authentik-config = {
image = "ghcr.io/goauthentik/server:2023.10.7";
autoStart = true;
image = "ghcr.io/goauthentik/server:2025.2.4";
volumes = ["/home/tristan/pods/authentik/media:/media"];
environment = {
AUTHENTIK_POSTGRESQL__USER = postgres.user;
@ -20,7 +21,8 @@
AUTHENTIK_EMAIL__FROM = "Authentik <tristan@tristans.cloud>";
AUTHENTIK_DEFAULT_USER_CHANGE_USERNAME = "false";
};
envFile = templates."authentik/environment".path;
environmentFiles = [templates."authentik/environment".path];
dependsOn = ["authentik-redis" "authentik-postgres"];
};
in {
sops.secrets = {
@ -50,34 +52,38 @@ in {
'';
};
};
podman.authentik-redis = {
image = "redis:latest";
ports = ["${redis_port}:6379"];
};
podman.authentik-server =
authentik-config
// {
command = "server";
ports = ["${authentik_port}:9000" "9084:9300"];
virtualisation.oci-containers.containers = {
authentik-redis = {
autoStart = true;
image = "redis:7.2-alpine";
ports = ["${redis_port}:6379"];
volumes = ["authentik-redis:/data"];
};
podman.authentik-worker =
authentik-config
// {
command = "worker";
};
authentik-server =
authentik-config
// {
cmd = ["server"];
ports = ["${authentik_port}:9000" "9084:9300"];
};
podman.authentik-postgres = {
image = "docker.io/postgres:14-alpine";
ports = ["${postgres.port}:5432"];
volumes = ["/home/tristan/pods/authentik/db:/var/lib/postgresql/data"];
environment = {
POSTGRES_USER = postgres.user;
POSTGRES_DB = postgres.db;
authentik-worker =
authentik-config
// {
cmd = ["worker"];
};
authentik-postgres = {
autoStart = true;
image = "docker.io/postgres:14-alpine";
ports = ["${postgres.port}:5432"];
volumes = ["/home/tristan/pods/authentik/db:/var/lib/postgresql/data"];
environment = {
POSTGRES_USER = postgres.user;
POSTGRES_DB = postgres.db;
};
environmentFiles = [templates."authentik/postgres_env".path];
};
envFile = templates."authentik/postgres_env".path;
};
services.nginx.virtualHosts."auth.tristans.cloud" = {

View file

@ -1,6 +1,15 @@
{config, ...}: let
{config, lib, pkgs, ...}: let
cfg = config.services.grafana;
secrets = config.sops.secrets;
mkDashboards = dashboards: pkgs.symlinkJoin {
name = "dashboards";
paths = map mkDashboard dashboards;
};
mkDashboard = {name, url, sha256}: pkgs.writeTextFile {
inherit name;
text = builtins.readFile ( builtins.fetchurl {inherit url sha256;} );
destination = "/dash/${name}.json";
};
in {
sops.secrets."grafana/oidc_client_secret" = {
owner = "grafana";
@ -25,6 +34,22 @@ in {
role_attribute_path = "contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer'";
};
};
provision.dashboards.settings.providers = [{
name = "Node Exporter";
type = "file";
options.path = mkDashboards [
{
name = "node-exporter";
url = "https://grafana.com/api/dashboards/1860/revisions/37/download";
sha256 = "sha256:0qza4j8lywrj08bqbww52dgh2p2b9rkhq5p313g72i57lrlkacfl";
}
{
name = "synapse";
url = "https://raw.githubusercontent.com/element-hq/synapse/refs/heads/master/contrib/grafana/synapse.json";
sha256 = "sha256:07qlr0waw9phmyd38bv22bn5v303w3397b89l44l3lzwhpnhs16s";
}
];
}];
};
services.nginx.virtualHosts = {
${cfg.settings.server.domain} = {

View file

@ -1,6 +1,8 @@
{
services.jellyfin = {
enable = true;
group = "media"; # access to user stuff
openFirewall = true;
};
services.nginx.virtualHosts."movies.tristans.cloud" = {
forceSSL = true;

98
nixos/services/loki.nix Normal file
View file

@ -0,0 +1,98 @@
{config, ...}: let
inherit (config.services) loki;
in {
services.loki = {
enable = true;
configuration = {
auth_enabled = false;
server.http_listen_port = 3100;
schema_config.configs = [
{
from = "2024-10-12";
object_store = "filesystem";
store = "tsdb";
schema = "v13";
index = {
prefix = "index_";
period = "24h";
};
}
];
storage_config."filesystem".directory = "/tmp/loki/chunks";
common = {
ring = {
instance_addr = "127.0.0.1";
kvstore.store = "inmemory";
};
replication_factor = 1;
path_prefix = "/tmp/loki";
};
limits_config = {
ingestion_rate_strategy = "local";
ingestion_rate_mb = 24;
ingestion_burst_size_mb = 36;
};
};
};
services.prometheus.scrapeConfigs = [{
job_name = "loki";
static_configs = [
{
targets = ["localhost:3100"];
}
];
}];
services.promtail = {
enable = true;
# https://grafana.com/docs/loki/latest/send-data/promtail/configuration/
configuration = {
server = {
http_listen_port = 9080;
grpc_listen_port = 0;
};
clients = [
{url = "http://localhost:3100/loki/api/v1/push";}
];
scrape_configs = [
{
job_name = "system";
journal = {
path = "/var/log/journal/";
};
relabel_configs = [
{
source_labels = ["__journal_message"];
target_label = "message";
regex = "(.+)";
}
{
source_labels = ["__journal__systemd_unit"];
target_label = "systemd_unit";
regex = "(.+)";
}
{
source_labels = ["__journal__systemd_user_unit"];
target_label = "systemd_user_unit";
regex = "(.+)";
}
{
source_labels = ["__journal__transport"];
target_label = "transport";
regex = "(.+)";
}
{
source_labels = ["__journal__priority_keyword"];
target_label = "severity";
regex = "(.+)";
}
];
}
];
};
};
services.grafana.provision.datasources.settings.datasources = [{
name = "Loki";
type = "loki";
url = "http://localhost:${toString loki.configuration.server.http_listen_port}";
}];
}

View file

@ -1,9 +1,44 @@
{config, ...}: let
inherit (config) sops;
inherit (sops) templates placeholder;
{config, lib, ...}: let
inherit (import ./lib.nix) toAppRegistration;
inherit (config.sops) templates placeholder;
in {
virtualisation.oci-containers.containers.mautrix-signal = {
image = "dock.mau.dev/mautrix/signal:v0.7.1";
dependsOn = ["mautrix-signal-psql"];
volumes = [
"/home/tristan/pods/signal-bridge/mautrix-signal:/data:z"
];
ports = [
"29328:29328"
"8000:8000"
];
};
# when you get around to backing this up
# 1. stop the server.
# 2. backup the db.
# 3. migrate to newer version of postgres
# 4. migrate db to local
virtualisation.oci-containers.containers.mautrix-signal-psql = {
image = "docker.io/postgres:14-alpine";
# ports = [ "127.0.0.1:5435:5432" ];
ports = [ "5435:5432" ];
volumes = [ "/home/tristan/pods/signal-bridge/db:/var/lib/postgresql/data" ];
environmentFiles = [templates."mautrix-signal/psql.env".path];
};
sops.templates = {
"mautrix-signal/psql.env" = {
owner = config.users.users.nobody.name;
content = lib.strings.toShellVars {
POSTGRES_PASSWORD = placeholder."mautrix-signal/postgres_password";
POSTGRES_USER = "signald";
POSTGRES_DB = "signald";
};
};
};
sops.secrets = {
"mautrix-signal/postgres_password" = {};
"mautrix-signal/as_token" = {};
"mautrix-signal/hs_token" = {};
};
@ -20,10 +55,8 @@ in {
};
};
};
# mautrix-signal server currently in ansible/podman
services.matrix-synapse.settings.app_service_config_files = [
templates."mautrix-signal/appservice.yaml".path
];
}

View file

@ -1,34 +1,17 @@
{config, ...}: let
inherit (config) sops;
inherit (sops) templates placeholder;
inherit (import ./lib.nix) toAppRegistration;
in {
sops.secrets = {
"mautrix-whatsapp/as_token" = {};
"mautrix-whatsapp/hs_token" = {};
};
sops.templates = {
"mautrix-whatsapp/appservice.yaml" = {
owner = "matrix-synapse";
content = toAppRegistration {
id = "whatsapp";
port = config.services.mautrix-whatsapp.settings.appservice.port;
as_token = placeholder."mautrix-whatsapp/as_token";
hs_token = placeholder."mautrix-whatsapp/hs_token";
sender_localpart = "Gx8tLTHsxVlrdD3qibaPdaP9t7GhfciV";
"de.sorunome.msc2409.push_ephemeral" = true;
};
};
# "mautrix-whatsapp/env".content = ''
# MAUTRIX_WHATSAPP_APPSERVICE_AS_TOKEN=${placeholder."mautrix-whatsapp/as_token"}
# MAUTRIX_WHATSAPP_APPSERVICE_HS_TOKEN=${placeholder."mautrix-whatsapp/hs_token"}
# '';
};
{config, ...}:
{
# TODO: totally borked for some reason. DB migration?
nixpkgs.config.permittedInsecurePackages = [
"olm-3.2.16"
];
services.mautrix-whatsapp = {
enable = true;
# environmentFile = templates."mautrix-whatsapp/env".path;
registerToSynapse = true;
settings = {
appservice.database = {
type = "sqlite3";
uri = "/var/lib/mautrix-whatsapp/mautrix-whatsapp.db";
};
homeserver = {
address = "http://localhost:8008";
domain = "tristans.cloud";
@ -46,9 +29,4 @@ in {
};
};
};
services.matrix-synapse.settings.app_service_config_files = [
templates."mautrix-whatsapp/appservice.yaml".path
# "/var/lib/mautrix-whatsapp/whatsapp-registration.yaml"
];
}

View file

@ -10,6 +10,7 @@
confirm-external-bind=1
out-peers=64 # This will enable much faster sync and tx awareness; the default 8 is suboptimal nowadays
in-peers=1024 # The default is unlimited; we prefer to put a cap on this
zmq-pub=tcp://localhost:18083
'';
};
}

View file

@ -2,6 +2,26 @@
services.mpd = {
enable = true;
network.listenAddress = "0.0.0.0";
extraConfig = ''
audio_output {
type "fifo"
name "snapcast"
path "${config.services.snapserver.streams.mpd.location}"
format "${config.services.snapserver.streams.mpd.sampleFormat}"
mixer_type "software"
}
'';
};
networking.firewall.allowedTCPPorts = [config.services.mpd.network.port];
services.snapserver = {
enable = true;
openFirewall = true;
buffer = 1000;
streams.mpd = {
type = "pipe";
location = "/run/snapserver/mpd";
sampleFormat = "44100:16:2";
codec = "pcm";
};
};
}

View file

@ -1,6 +1,7 @@
{
config,
pkgs,
lib,
...
}: let
nextcloud = config.services.nextcloud;
@ -27,6 +28,7 @@ in {
services.nextcloud = {
enable = true;
https = true;
package = pkgs.nextcloud30;
hostName = "files.${config.networking.domain}";
configureRedis = true;
database.createLocally = true;
@ -35,6 +37,7 @@ in {
dbtype = "pgsql";
};
secretFile = sops.templates."nextcloud/secrets.json".path;
phpOptions."opcache.interned_strings_buffer" = "23";
settings = {
maintenance_window_start = 2;
default_phone_region = "GB";
@ -78,17 +81,10 @@ in {
notes
maps
previewgenerator
deck
news
oidc_login
;
oidc_login = pkgs.fetchNextcloudApp {
sha256 = "sha256-cN5azlThKPKRVip14yfUNR85of5z+N6NVI7sg6pSGQI=";
url = "https://github.com/pulsejet/nextcloud-oidc-login/releases/download/v3.0.2/oidc_login.tar.gz";
license = "agpl3Only";
};
news = pkgs.fetchNextcloudApp {
sha256 = "sha256-aePXUn57U+1e01dntxFuzWZ8ILzwbnsAOs60Yz/6zUU=";
url = "https://github.com/nextcloud/news/releases/download/25.0.0-alpha4/news.tar.gz";
license = "agpl3Only";
};
};
maxUploadSize = "5G";
};

View file

@ -34,4 +34,9 @@ in {
};
};
};
services.grafana.provision.datasources.settings.datasources = [{
name = "Prometheus";
type = "prometheus";
url = "http://localhost:${toString prometheus.port}";
}];
}

View file

@ -16,6 +16,8 @@
inherit (config.services) matrix-synapse matrix-sliding-sync;
inherit (sops) secrets templates;
in {
imports = [./metrics.nix];
services.postgresql.enable = true;
services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
@ -66,7 +68,7 @@ in {
server_name = domain;
baseurl = "https://${domain}";
oidc_providers = [];
settings.listeners = [
listeners = [
{
inherit port;
bind_addresses = ["localhost"];
@ -84,12 +86,6 @@ in {
};
};
services.matrix-sliding-sync = {
enable = true;
environmentFile = templates."synapse/sliding_sync_env".path;
settings.SYNCV3_SERVER = "https://${domain}";
};
services.nginx.virtualHosts = {
${domain} = {
locations."= /.well-known/matrix/server".extraConfig = mkWellKnown {
@ -97,7 +93,6 @@ in {
};
locations."= /.well-known/matrix/client".extraConfig = mkWellKnown {
"m.homeserver".base_url = "https://${fqdn}";
"org.matrix.msc3575.proxy"."url" = "https://${fqdn}";
};
locations."= /.well-known/matrix/support".extraConfig = mkWellKnown {
admins = [
@ -110,14 +105,12 @@ in {
};
locations."/_matrix".proxyPass = "http://localhost:${toString port}";
locations."/_synapse/client".proxyPass = "http://localhost:${toString port}";
locations."/_matrix/client/unstable/org.matrix.msc3575/sync".proxyPass = "http://${toString matrix-sliding-sync.settings.SYNCV3_BINDADDR}";
};
${fqdn} = {
enableACME = true;
forceSSL = true;
locations."/_matrix".proxyPass = "http://localhost:${toString port}";
locations."/_synapse/client".proxyPass = "http://localhost:${toString port}";
locations."/_matrix/client/unstable/org.matrix.msc3575/sync".proxyPass = "http://${toString matrix-sliding-sync.settings.SYNCV3_BINDADDR}";
};
};
}

View file

@ -0,0 +1,25 @@
let
port = 9008;
in {
services.prometheus.scrapeConfigs = [{
job_name = "synapse";
metrics_path = "/_synapse/metrics";
static_configs = [{
targets = ["localhost:${toString port}"];
}];
}];
services.matrix-synapse.settings = {
enable_metrics = true;
listeners = [
{
port = port;
type = "metrics";
bind_addresses = ["127.0.0.1"];
tls = false;
resources = []; # unneeded with type: metrics, just to make the nix module happy
}
];
};
# Grafana rules?
# https://github.com/element-hq/synapse/tree/master/contrib/prometheus/
}