This commit is contained in:
Joakim Repomaa
2025-02-08 19:44:52 +02:00
parent 271d9e8f88
commit 913d3d1238
32 changed files with 844 additions and 544 deletions

View File

@@ -21,6 +21,7 @@
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "freun-dev"; # Define your hostname.
networking.domain = "freun.dev";
networking.useDHCP = false;
networking.nftables.enable = true;

View File

@@ -6,7 +6,7 @@ in
imports = [
./hardware-configuration.nix
./configuration.nix
./services
./services.nix
gtrackmap.nixosModules.default
];
}

View File

@@ -0,0 +1,107 @@
{ pkgs, config, ... }:
let
immichDataDir = "/mnt/storage/immich";
syncthingDataDir = "/mnt/storage/syncthing";
smtp = {
host = "horologium.uberspace.de";
port = 587;
username = "noreply@${config.networking.domain}";
from = "noreply@${config.networking.domain}";
heloName = config.networking.domain;
};
in
{
services.postgresql.package = pkgs.postgresql_17;
modules.storageBoxMounts = {
${immichDataDir} = {
path = "/backup/immich";
user = "u407959";
};
${syncthingDataDir} = {
path = "/backup/syncthing";
user = "u407959";
uid = config.users.users.syncthing.uid;
gid = config.users.groups.syncthing.gid;
};
};
modules.services = {
vaultwarden = {
enable = true;
subdomain = "pw";
config = {
YUBICO_CLIENT_ID = 86799;
SMTP_HOST = smtp.host;
SMTP_FROM = smtp.from;
SMTP_FROM_NAME = "Vaultwarden";
SMTP_USERNAME = smtp.username;
SMTP_PORT = smtp.port;
HELO_NAME = smtp.heloName;
};
};
immich = {
enable = true;
subdomain = "img";
dataDir = immichDataDir;
storageDirs = [ syncthingDataDir ];
version = "v1.125.7";
};
syncthing = {
enable = true;
subdomain = "sync";
dataDir = syncthingDataDir;
};
gotosocial = {
enable = true;
subdomain = "social";
settings = {
smtp-host = smtp.host;
smtp-port = smtp.port;
smtp-username = smtp.username;
smtp-from = smtp.from;
};
};
bin = {
enable = true;
subdomain = "bin";
};
workout-tracker = {
enable = true;
subdomain = "fit";
};
workout-sync = {
enable = true;
subdomain = "ws";
};
invidious = {
enable = true;
subdomain = "vid";
};
grafana = {
enable = true;
subdomain = "graph";
};
gtrackmap = {
enable = true;
subdomain = "trackmap";
};
owncast = {
enable = true;
subdomain = "stream";
};
tailscale.enable = true;
};
}

View File

@@ -1,73 +0,0 @@
{ pkgs, ... }:
let
fqdn = "bin.freun.dev";
port = 3600;
config = (pkgs.formats.toml { }).generate "rustypaste-config.toml" {
server = {
address = "[::1]:${toString port}";
max_content_length = "1GB";
upload_path = "/var/lib/rustypaste/uploads";
timeout = "5m";
};
landing_page = {
text = ''
Submit files via HTTP POST here:
curl -F 'file=@example.txt' https://${fqdn}
This will return the URL of the uploaded file.
The server administrator might remove any pastes that they do not personally
want to host.
If you are the server administrator and want to change this page, just go
into your config file and change it! If you change the expiry time, it is
recommended that you do.
By default, pastes expire every hour. The server admin may or may not have
changed this.
Check out the GitHub repository at https://github.com/orhun/rustypaste
Command line tool is available at https://github.com/orhun/rustypaste-cli
'';
content_type = "text/plain; charset=utf-8";
};
paste = {
default_extension = "txt";
random_url = { type = "petname"; words = 2; separator = "-"; };
delete_expirted_files = { enabled = true; interval = "1h"; };
default_expiry = "100y";
mime_override = [
{ mime = "text/plain"; regex = "^.*\.(log|txt|diff|sh|rs|toml|cr|nix|rb|js|tsx|ts|jsx)$"; }
];
};
};
in
{
systemd.services.rustypaste = {
enable = true;
description = "Rustypaste pastebin";
environment = {
CONFIG = config;
AUTH_TOKENS_FILE = "/var/secrets/rustypaste-tokens";
};
serviceConfig = {
ExecStart = "${pkgs.rustypaste}/bin/rustypaste";
WorkingDirectory = "/var/lib/rustypaste";
StateDirectory = "rustypaste";
DynamicUser = true;
BindReadOnlyPaths = [ "/var/secrets/rustypaste-tokens" ];
};
wantedBy = [ "multi-user.target" ];
confinement = {
enable = true;
packages = [ config ];
};
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
}

View File

@@ -1,39 +0,0 @@
{ pkgs, ... }:
{
services.postgresql = {
enable = true;
package = pkgs.postgresql_17;
};
virtualisation.podman = {
enable = true;
autoPrune.enable = true;
dockerCompat = true;
defaultNetwork.settings = {
# Required for container networking to be able to use names.
dns_enabled = true;
};
};
virtualisation.oci-containers.backend = "podman";
networking.firewall.trustedInterfaces = [ "podman1" ];
modules.firewall.interfaces.podman1 = [ "dns" ];
modules.webserver.enable = true;
imports = [
./vaultwarden.nix
./immich.nix
./syncthing.nix
./invidious.nix
./grafana.nix
./gtrackmap.nix
./owncast.nix
./tailscale.nix
./workout-tracker.nix
./gotosocial.nix
./bin.nix
./workout-sync.nix
];
}

View File

@@ -1,47 +0,0 @@
{ config, lib, ... }:
let
domain = "freun.dev";
fqdn = "social.${domain}";
port = 3500;
in
{
services.gotosocial = {
enable = true;
environmentFile = "/var/secrets/gotosocial.env";
settings = {
host = "social.freun.dev";
account-domain = "freun.dev";
protocol = "https";
bind-address = "localhost";
instance-languages = [ "de" "fi" "en" ];
instance-inject-mastodon-version = true;
accounts-registration-open = true;
instance-expose-public-timeline = true;
letsencrypt-enabled = false;
smtp-host = "horologium.uberspace.de";
smtp-port = 587;
smtp-username = "noreply@freun.dev";
smtp-from = "noreply@freun.dev";
inherit port;
};
setupPostgresqlDB = true;
};
modules.webserver.vHosts = {
${domain}.locations = lib.listToAttrs (
lib.map
(path: {
name = "/.well-known/${path}";
value.extraConfig = ''
rewrite ^.*$ https://${fqdn}/.well-known/${path} permanent;
'';
}) [
"host-meta"
"webfinger"
"nodeinfo"
]
);
"${fqdn}".locations."/".proxyPort = port;
};
}

View File

@@ -1,55 +0,0 @@
{ ... }:
let
fqdn = "graph.freun.dev";
port = 3300;
in
{
services.grafana = {
enable = true;
settings = {
server = {
root_url = "https://${fqdn}";
http_port = port;
};
database = {
host = "/var/run/postgresql";
type = "postgres";
user = "grafana";
};
smtp = {
enabled = true;
host = "horologium.uberspace.de";
from_address = "noreply@freun.dev";
from_name = "Vaultwarden";
user = "noreply@freun.dev";
password = "$__file{/var/secrets/smtp-password}";
};
};
};
services.prometheus = {
enable = true;
exporters.node.enable = true;
scrapeConfigs = [
{
job_name = "node";
static_configs = [
{ targets = [ "localhost:9100" ]; }
];
}
];
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
services.postgresql = {
ensureDatabases = [ "grafana" ];
ensureUsers = [{
name = "grafana";
ensureDBOwnership = true;
}];
};
}

View File

@@ -1,13 +0,0 @@
{ ... }:
let
fqdn = "trackmap.freun.dev";
port = 3200;
in
{
services.gtrackmap = {
enable = true;
inherit port;
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
}

View File

@@ -1,185 +0,0 @@
{ pkgs, lib, ... }:
let
fqdn = "img.freun.dev";
port = 2283;
immich_version = "v1.125.7";
storage_dir = "/mnt/storage/syncthing";
immich_data_dir = "/mnt/storage/immich";
volumeServices = names: (
lib.lists.foldl
(services: name:
services // {
"podman-volume-immich_${name}" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
podman volume inspect immich_${name} || podman volume create immich_${name}
'';
partOf = [ "podman-compose-immich-root.target" ];
wantedBy = [ "podman-compose-immich-root.target" ];
};
}
)
{ }
names
);
containerServices = services: (
lib.lists.foldl
(acc: { name, volumes ? [ ], ... }:
let
volume_services = map (volume: "podman-volume-immich_${volume}.service") volumes;
dependent_services = [ "podman-network-immich_default.service" ] ++ volume_services;
in
acc // {
"podman-immich_${name}" = {
serviceConfig = {
Restart = lib.mkOverride 500 "always";
};
after = dependent_services;
requires = dependent_services;
partOf = [
"podman-compose-immich-root.target"
];
wantedBy = [
"podman-compose-immich-root.target"
];
};
}
)
{ }
services
);
in
{
# Containers
virtualisation.oci-containers.containers = {
"immich_machine_learning" = {
image = "ghcr.io/immich-app/immich-machine-learning:${immich_version}";
environmentFiles = [
"/var/secrets/immich.env"
];
volumes = [
"immich_model_cache:/cache:rw"
];
log-driver = "journald";
extraOptions = [
"--network-alias=immich-machine-learning"
"--network=immich_default"
];
};
"immich_postgres" = {
image = "registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0";
environmentFiles = [
"/var/secrets/immich.env"
];
environment = {
POSTGRES_INITDB_ARGS = "--data-checksums";
};
volumes = [
"immich_db_data:/var/lib/postgresql/data:rw"
];
cmd = [ "postgres" "-c" "shared_preload_libraries=vectors.so" "-c" "search_path=\"$user\", public, vectors" "-c" "logging_collector=on" "-c" "max_wal_size=2GB" "-c" "shared_buffers=512MB" "-c" "wal_compression=on" ];
log-driver = "journald";
extraOptions = [
"--network-alias=database"
"--network=immich_default"
];
};
"immich_redis" = {
image = "registry.hub.docker.com/library/redis:6.2-alpine";
environmentFiles = [
"/var/secrets/immich.env"
];
log-driver = "journald";
extraOptions = [
"--network-alias=redis"
"--network=immich_default"
];
};
"immich_server" = {
image = "ghcr.io/immich-app/immich-server:${immich_version}";
environmentFiles = [
"/var/secrets/immich.env"
];
volumes = [
"/etc/localtime:/etc/localtime:ro"
"${immich_data_dir}:/usr/src/app/upload:rw"
"${storage_dir}:${storage_dir}:ro"
];
ports = [
"${toString port}:2283/tcp"
];
dependsOn = [
"immich_postgres"
"immich_redis"
];
log-driver = "journald";
extraOptions = [
"--network-alias=immich-server"
"--network=immich_default"
];
};
};
systemd.services = (containerServices [
{ name = "machine_learning"; volumes = [ "model_cache" ]; }
{ name = "postgres"; volumes = [ "db_data" ]; }
{ name = "redis"; }
{ name = "server"; }
]) // {
# Networks
"podman-network-immich_default" = {
path = [ pkgs.podman ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStop = "${pkgs.podman}/bin/podman network rm -f immich_default";
};
script = ''
podman network inspect immich_default || podman network create immich_default
'';
partOf = [ "podman-compose-immich-root.target" ];
wantedBy = [ "podman-compose-immich-root.target" ];
};
} // (volumeServices [
"db_data"
"model_cache"
]);
# Root service
# When started, this will automatically create all resources and start
# the containers. When stopped, this will teardown all resources.
systemd.targets."podman-compose-immich-root" = {
unitConfig = {
Description = "Root target generated by compose2nix.";
};
wantedBy = [ "multi-user.target" ];
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
fileSystems."${immich_data_dir}" = {
device = "//u407959.your-storagebox.de/backup/immich";
fsType = "cifs";
options =
let
# this line prevents hanging on network split
automount_opts = "x-systemd.automount,auto,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s";
in
[ "${automount_opts},credentials=/var/secrets/smb-storage" ];
};
environment.systemPackages = [ pkgs.cifs-utils ];
}

View File

@@ -1,23 +0,0 @@
{ ... }:
let
fqdn = "vid.freun.dev";
port = 3000;
in
{
services.invidious = {
enable = true;
domain = fqdn;
address = "127.0.0.1";
inherit port;
settings = {
external_port = 443;
db = {
dbname = "invidious";
user = "invidious";
};
};
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
}

View File

@@ -1,14 +0,0 @@
{ ... }:
let
fqdn = "stream.freun.dev";
port = 8080;
in
{
services.owncast = {
enable = true;
openFirewall = true;
inherit port;
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
}

View File

@@ -1,34 +0,0 @@
{ pkgs, config, lib, ... }:
let
fqdn = "sync.freun.dev";
port = 8384;
storage_dir = "/mnt/storage/syncthing";
in
{
services.syncthing = {
enable = true;
dataDir = "/mnt/storage/syncthing";
openDefaultPorts = true;
guiAddress = "[::1]:${toString port}";
settings.gui.insecureSkipHostCheck = true;
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
fileSystems."${storage_dir}" = {
device = "//u407959.your-storagebox.de/backup/syncthing";
fsType = "cifs";
options =
let
# this line prevents hanging on network split
automount_opts = "x-systemd.automount,auto,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s";
uid = builtins.toString config.users.users.syncthing.uid;
gid = builtins.toString config.users.groups.syncthing.gid;
in
[ "${automount_opts},credentials=/var/secrets/smb-storage,uid=${uid},gid=${gid}" ];
};
environment.systemPackages = [ pkgs.cifs-utils ];
}

View File

@@ -1,7 +0,0 @@
{ ... }:
{
services.tailscale = {
enable = true;
useRoutingFeatures = "server";
};
}

View File

@@ -1,38 +0,0 @@
{ lib, ... }:
let
fqdn = "pw.freun.dev";
port = 8000;
in
{
services.vaultwarden = {
enable = true;
dbBackend = "postgresql";
environmentFile = "/var/secrets/vaultwarden.env";
config = {
DOMAIN = "https://${fqdn}";
DATABASE_URL = "postgres://%2Fvar%2Frun%2Fpostgresql/vaultwarden";
WEBSOCKET_ENABLED = true;
SIGNUPS_VERIFY = true;
PASSWORD_ITERATIONS = 600000;
YUBICO_CLIENT_ID = 86799;
SMTP_HOST = "horologium.uberspace.de";
SMTP_FROM = "noreply@freun.dev";
SMTP_FROM_NAME = "Vaultwarden";
SMTP_USERNAME = "noreply@freun.dev";
SMTP_PORT = 587;
HELO_NAME = "freun.dev";
ROCKET_LIMITS = "{json=10485760}";
ROCKET_PORT = port;
};
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
services.postgresql = {
ensureDatabases = [ "vaultwarden" ];
ensureUsers = [{
name = "vaultwarden";
ensureDBOwnership = true;
}];
};
}

View File

@@ -1,26 +0,0 @@
{ pkgs, inputs, config, ... }:
let
fqdn = "ws.freun.dev";
port = 3344;
workout-sync = inputs.workout-sync.packages.${pkgs.system}.default;
in
{
systemd.services.workout-sync = {
enable = true;
description = "Workout sync service";
environment = {
PORT = toString port;
WORKOUT_TRACKER_URL = "http://localhost:${toString config.services.workout-tracker.port}";
};
serviceConfig = {
ExecStart = "${workout-sync}/bin/workout-sync";
Restart = "always";
DynamicUser = true;
EnvironmentFile = "/var/secrets/workout-sync.env";
};
wantedBy = [ "multi-user.target" ];
confinement.enable = true;
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
}

View File

@@ -1,35 +0,0 @@
{ pkgs, ... }:
let
fqdn = "fit.freun.dev";
port = 3322;
version = "2.0.3";
in
{
services.workout-tracker = {
enable = true;
inherit port;
package = pkgs.stdenv.mkDerivation {
name = "workout-tracker";
src = pkgs.fetchurl {
url = "https://github.com/jovandeginste/workout-tracker/releases/download/v${version}/workout-tracker-v${version}-linux-arm64.tar.gz";
hash = "sha256-k6Fq9emrUgGr29d5AaWJtyjGoftGN9IpTgdTOdvOE1o=";
};
nativeBuildInputs = [
pkgs.autoPatchelfHook
];
sourceRoot = ".";
installPhase = ''
runHook preInstall
install -m755 -D workout-tracker $out/bin/workout-tracker
runHook postInstall
'';
meta = with pkgs.lib; {
description = "A simple workout tracker";
license = licenses.mit;
mainProgram = "workout-tracker";
};
};
};
modules.webserver.vHosts.${fqdn}.locations."/".proxyPort = port;
}