Massive nixfmt reformatting

This commit is contained in:
Natsu Kagami 2025-04-03 13:59:50 +02:00
parent fe4492f004
commit b29ddd5e65
Signed by: nki
GPG key ID: 55A032EB38B49ADB
109 changed files with 4323 additions and 2368 deletions

View file

@ -1,4 +1,10 @@
{ pkgs, config, lib, ... }: {
{
pkgs,
config,
lib,
...
}:
{
imports = [
./hardware-configuration.nix
@ -88,13 +94,19 @@
services.my-tinc.rsaPrivateKey = config.sops.secrets."tinc/rsa-private-key".path;
services.my-tinc.ed25519PrivateKey = config.sops.secrets."tinc/ed25519-private-key".path;
sops.secrets."nix-build-farm/private-key" = { mode = "0400"; };
sops.secrets."nix-build-farm/private-key" = {
mode = "0400";
};
services.nix-build-farm.hostname = "home";
services.nix-build-farm.privateKeyFile = config.sops.secrets."nix-build-farm/private-key".path;
# Set up traefik
sops.secrets.cloudflare-dns-api-token = { owner = "traefik"; };
sops.secrets.traefik-dashboard-users = { owner = "traefik"; };
sops.secrets.cloudflare-dns-api-token = {
owner = "traefik";
};
sops.secrets.traefik-dashboard-users = {
owner = "traefik";
};
cloud.traefik.cloudflareKeyFile = config.sops.secrets.cloudflare-dns-api-token.path;
cloud.traefik.dashboard = {
enable = true;
@ -108,9 +120,19 @@
settings.HOST = "127.0.0.1";
settings.PORT = "16904";
};
cloud.traefik.hosts.uptime-kuma = { host = "status.nkagami.me"; port = 16904; noCloudflare = true; };
cloud.traefik.hosts.uptime-kuma-dtth = { host = "status.dtth.ch"; port = 16904; };
cloud.traefik.hosts.uptime-kuma-codefun = { host = "status.codefun.vn"; port = 16904; };
cloud.traefik.hosts.uptime-kuma = {
host = "status.nkagami.me";
port = 16904;
noCloudflare = true;
};
cloud.traefik.hosts.uptime-kuma-dtth = {
host = "status.dtth.ch";
port = 16904;
};
cloud.traefik.hosts.uptime-kuma-codefun = {
host = "status.codefun.vn";
port = 16904;
};
# Bitwarden
sops.secrets.vaultwarden-env = { };
@ -120,7 +142,9 @@
virtualisation.arion.backend = "docker";
# Conduit
sops.secrets.heisenbridge = { owner = "heisenbridge"; };
sops.secrets.heisenbridge = {
owner = "heisenbridge";
};
cloud.conduit.enable = true;
cloud.conduit.instances = {
"nkagami" = {
@ -155,7 +179,10 @@
};
# Mail
sops.secrets.mail-users = { owner = "maddy"; reloadUnits = [ "maddy.service" ]; };
sops.secrets.mail-users = {
owner = "maddy";
reloadUnits = [ "maddy.service" ];
};
cloud.mail = {
enable = true;
debug = true;
@ -177,7 +204,10 @@
sops.secrets.authentik-env = { };
cloud.authentik.enable = true;
cloud.authentik.envFile = config.sops.secrets.authentik-env.path;
cloud.traefik.hosts.authentik = { host = "auth.dtth.ch"; port = config.cloud.authentik.port; };
cloud.traefik.hosts.authentik = {
host = "auth.dtth.ch";
port = config.cloud.authentik.port;
};
# Firezone
sops.secrets.firezone-env = { };
@ -197,14 +227,18 @@
};
# GoToSocial
sops.secrets.gts-env = { restartUnits = [ "gotosocial.service" ]; };
sops.secrets.gts-env = {
restartUnits = [ "gotosocial.service" ];
};
cloud.gotosocial = {
enable = true;
envFile = config.sops.secrets.gts-env.path;
};
# Grist
sops.secrets."grist/env" = { restartUnits = [ "arion-grist.service" ]; };
sops.secrets."grist/env" = {
restartUnits = [ "arion-grist.service" ];
};
cloud.grist = {
enable = true;
envFile = config.sops.secrets."grist/env".path;
@ -212,9 +246,12 @@
dataDir = "/mnt/data/grist";
};
# ntfy
cloud.traefik.hosts.ntfy-sh = { host = "ntfy.nkagami.me"; port = 11161; noCloudflare = true; };
cloud.traefik.hosts.ntfy-sh = {
host = "ntfy.nkagami.me";
port = 11161;
noCloudflare = true;
};
services.ntfy-sh = {
enable = true;
settings = {
@ -238,4 +275,3 @@
mkdir -p /var/lib/ntfy-sh/attachments
'';
}

View file

@ -1,4 +1,9 @@
{ pkgs, config, lib, ... }:
{
pkgs,
config,
lib,
...
}:
with lib;
let
user = "gitea";
@ -126,7 +131,11 @@ in
};
repository = {
DEFAULT_PRIVATE = "private";
PREFERRED_LICENSES = strings.concatStringsSep "," [ "AGPL-3.0-or-later" "GPL-3.0-or-later" "Apache-2.0" ];
PREFERRED_LICENSES = strings.concatStringsSep "," [
"AGPL-3.0-or-later"
"GPL-3.0-or-later"
"Apache-2.0"
];
# DISABLE_HTTP_GIT = true;
DEFAULT_BRANCH = "master";
ENABLE_PUSH_CREATE_USER = true;
@ -216,18 +225,17 @@ in
environment.GNUPGHOME = "${config.services.gitea.stateDir}/.gnupg";
# https://github.com/NixOS/nixpkgs/commit/93c1d370db28ad4573fb9890c90164ba55391ce7
serviceConfig.SystemCallFilter = mkForce "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
preStart =
''
# Import the signing subkey
if cat ${config.services.forgejo.stateDir}/.gnupg/gpg.conf | grep -q ${signingKey}; then
echo "Keys already imported"
# imported
else
echo "Import your keys!"
${pkgs.gnupg}/bin/gpg --quiet --import ${secrets."gitea/signing-key".path}
echo "trusted-key ${signingKey}" >> ${config.services.forgejo.stateDir}/.gnupg/gpg.conf
exit 1
fi
'';
preStart = ''
# Import the signing subkey
if cat ${config.services.forgejo.stateDir}/.gnupg/gpg.conf | grep -q ${signingKey}; then
echo "Keys already imported"
# imported
else
echo "Import your keys!"
${pkgs.gnupg}/bin/gpg --quiet --import ${secrets."gitea/signing-key".path}
echo "trusted-key ${signingKey}" >> ${config.services.forgejo.stateDir}/.gnupg/gpg.conf
exit 1
fi
'';
};
}

View file

@ -1,11 +1,15 @@
{ pkgs, config, lib, ... }:
{
pkgs,
config,
lib,
...
}:
with lib;
let
cfg = config.cloud.grist;
mkImage =
{ imageName, imageDigest, ... }: "${imageName}@${imageDigest}";
mkImage = { imageName, imageDigest, ... }: "${imageName}@${imageDigest}";
# If we can pullImage we can just do
# mkImage = pkgs.dockerTools.pullImage;
@ -24,7 +28,12 @@ let
};
};
defaultEnv = {
GRIST_HIDE_UI_ELEMENTS = lib.concatStringsSep "," [ "helpCenter" "billing" "multiAccounts" "supportGrist" ];
GRIST_HIDE_UI_ELEMENTS = lib.concatStringsSep "," [
"helpCenter"
"billing"
"multiAccounts"
"supportGrist"
];
GRIST_PAGE_TITLE_SUFFIX = " - DTTH Grist";
GRIST_FORCE_LOGIN = "true";
GRIST_WIDGET_LIST_URL = "https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json";
@ -60,7 +69,11 @@ in
allowedWebhookDomains = mkOption {
type = types.listOf types.str;
description = "List of domains to be allowed in webhooks";
default = [ "dtth.ch" "nkagami.me" "discord.com" ];
default = [
"dtth.ch"
"nkagami.me"
"discord.com"
];
};
defaultEmail = mkOption {
type = types.str;
@ -105,7 +118,10 @@ in
command = "--save 60 1 --loglevel warning";
restart = "unless-stopped";
healthcheck = {
test = [ "CMD-SHELL" "valkey-cli ping | grep PONG" ];
test = [
"CMD-SHELL"
"valkey-cli ping | grep PONG"
];
start_period = "20s";
interval = "30s";
retries = 5;
@ -124,4 +140,3 @@ in
};
};
}

View file

@ -2,11 +2,25 @@
{
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
boot.loader.grub.device = "/dev/sda";
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" "vmw_pvscsi" ];
boot.initrd.availableKernelModules = [
"ata_piix"
"uhci_hcd"
"xen_blkfront"
"vmw_pvscsi"
];
boot.initrd.kernelModules = [ "nvme" ];
fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
fileSystems."/" = {
device = "/dev/sda1";
fsType = "ext4";
};
# swap
swapDevices = [{ device = "/var/swapfile"; size = 4 * 1024; priority = 1024; }];
swapDevices = [
{
device = "/var/swapfile";
size = 4 * 1024;
priority = 1024;
}
];
zramSwap.enable = true;
# volumes
services.btrfs.autoScrub.enable = true;

View file

@ -1,4 +1,9 @@
{ pkgs, config, lib, ... }:
{
pkgs,
config,
lib,
...
}:
let
secrets = config.sops.secrets;
@ -7,9 +12,14 @@ let
webuiPort = 19877;
in
rec {
sops.secrets."headscale/client_secret" = { owner = "headscale"; };
sops.secrets."headscale/client_secret" = {
owner = "headscale";
};
sops.secrets."headscale/webui-env" = { };
sops.secrets."headscale/derp-servers/vnm" = { owner = "headscale"; name = "headscale/derp-servers/vnm.yaml"; };
sops.secrets."headscale/derp-servers/vnm" = {
owner = "headscale";
name = "headscale/derp-servers/vnm.yaml";
};
# database
cloud.postgresql.databases = [ "headscale" ];
# traefik
@ -27,8 +37,14 @@ rec {
noCloudflare = true;
};
systemd.services.headscale.requires = [ "postgresql.service" "arion-authentik.service" ];
systemd.services.headscale.after = [ "postgresql.service" "arion-authentik.service" ];
systemd.services.headscale.requires = [
"postgresql.service"
"arion-authentik.service"
];
systemd.services.headscale.after = [
"postgresql.service"
"arion-authentik.service"
];
services.headscale = {
enable = true;
inherit port;

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
ipv6-rotator =
let
@ -11,7 +16,14 @@ let
in
pkgs.writeShellApplication {
name = "smart-ipv6-rotator";
runtimeInputs = [ (pkgs.python3.withPackages (p: with p; [ pyroute2 requests ])) ];
runtimeInputs = [
(pkgs.python3.withPackages (
p: with p; [
pyroute2
requests
]
))
];
text = ''
if [ -z "$IPV6_ROTATOR_RANGE" ]; then
echo "Range required"
@ -22,10 +34,17 @@ let
};
in
{
sops.secrets."invidious" = { mode = "0444"; };
sops.secrets."invidious-rotator-env" = { mode = "0444"; };
sops.secrets."invidious" = {
mode = "0444";
};
sops.secrets."invidious-rotator-env" = {
mode = "0444";
};
cloud.postgresql.databases = [ "invidious" ];
cloud.traefik.hosts.invidious = { host = "invi.dtth.ch"; port = 61191; };
cloud.traefik.hosts.invidious = {
host = "invi.dtth.ch";
port = 61191;
};
services.invidious = {
enable = true;
domain = "invi.dtth.ch";
@ -54,8 +73,13 @@ in
};
systemd.timers.smart-ipv6-rotator = {
description = "Rotate ipv6 routes to Google";
timerConfig = { OnCalendar = "*-*-* 00,06,12,18:00:00"; };
wantedBy = [ "invidious.service" "timers.target" ];
timerConfig = {
OnCalendar = "*-*-* 00,06,12,18:00:00";
};
wantedBy = [
"invidious.service"
"timers.target"
];
unitConfig = { };
};
systemd.services.smart-ipv6-rotator = {
@ -68,4 +92,3 @@ in
};
};
}

View file

@ -1,4 +1,9 @@
{ pkgs, config, lib, ... }:
{
pkgs,
config,
lib,
...
}:
with lib;
let
user = "miniflux";
@ -42,7 +47,10 @@ in
systemd.services.miniflux = {
description = "Miniflux service";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "postgresql.service" ];
after = [
"network.target"
"postgresql.service"
];
requires = [ "postgresql.service" ];
serviceConfig = {
@ -72,16 +80,22 @@ in
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" ];
SystemCallFilter = [
"@system-service"
"~@privileged"
];
UMask = "0077";
};
environment = configEnv;
};
}

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
secrets = config.sops.secrets;
@ -12,7 +17,9 @@ let
plugins = pkgs.callPackage ./n8n/plugins/package.nix { };
in
{
sops.secrets."n8n/env" = { reloadUnits = [ "n8n.service" ]; };
sops.secrets."n8n/env" = {
reloadUnits = [ "n8n.service" ];
};
cloud.postgresql.databases = [ db ];
cloud.traefik.hosts.n8n = {
inherit port host;

View file

@ -1,4 +1,5 @@
{ nodejs, importNpmLock }: importNpmLock.buildNodeModules {
{ nodejs, importNpmLock }:
importNpmLock.buildNodeModules {
inherit nodejs;
npmRoot = ./.;
}

View file

@ -1,7 +1,14 @@
{ config, pkgs, ... }: {
sops.secrets.authentik-oidc-client-secret = { owner = "outline"; };
sops.secrets."outline/smtp-password" = { owner = "outline"; };
sops.secrets."outline/s3-secret-key" = { owner = "outline"; };
{ config, pkgs, ... }:
{
sops.secrets.authentik-oidc-client-secret = {
owner = "outline";
};
sops.secrets."outline/smtp-password" = {
owner = "outline";
};
sops.secrets."outline/s3-secret-key" = {
owner = "outline";
};
services.outline = {
enable = true;
@ -52,5 +59,8 @@
AWS_S3_R2 = "true";
AWS_S3_R2_PUBLIC_URL = "https://s3.wiki.dtth.ch";
};
cloud.traefik.hosts.outline = { host = "wiki.dtth.ch"; port = 18729; };
cloud.traefik.hosts.outline = {
host = "wiki.dtth.ch";
port = 18729;
};
}

View file

@ -1,4 +1,9 @@
{ pkgs, config, lib, ... }:
{
pkgs,
config,
lib,
...
}:
let
host = "owncast.nkagami.me";
port = 61347;

View file

@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
secrets = config.sops.secrets;
cfg = config.services.peertube;
@ -9,8 +14,14 @@ let
port = 19878;
in
{
sops.secrets."peertube" = { owner = cfg.user; restartUnits = [ "peertube.service" ]; };
sops.secrets."peertube-env" = { owner = cfg.user; restartUnits = [ "peertube.service" ]; };
sops.secrets."peertube" = {
owner = cfg.user;
restartUnits = [ "peertube.service" ];
};
sops.secrets."peertube-env" = {
owner = cfg.user;
restartUnits = [ "peertube.service" ];
};
# database
cloud.postgresql.databases = [ "peertube" ];
# traefik
@ -61,7 +72,9 @@ in
};
# Trust proxy
settings.trust_proxy = [ "loopback" ] ++ config.services.traefik.staticConfigOptions.entrypoints.https.forwardedHeaders.trustedIPs;
settings.trust_proxy = [
"loopback"
] ++ config.services.traefik.staticConfigOptions.entrypoints.https.forwardedHeaders.trustedIPs;
# Federation
settings.federation = {
@ -70,7 +83,10 @@ in
videos.cleanup_remote_interactions = true;
};
dataDirs = [ "/var/lib/peertube" "/mnt/data/peertube" ];
dataDirs = [
"/var/lib/peertube"
"/mnt/data/peertube"
];
};
systemd.services.peertube = {
@ -90,4 +106,3 @@ in
};
};
}

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
host = "social.dtth.ch";
port = 61010;
@ -6,11 +11,12 @@ in
{
cloud.traefik.hosts.phanpy = { inherit host port; };
services.nginx.virtualHosts.phanpy = {
listen = [{
inherit port;
addr = "127.0.0.1";
}];
listen = [
{
inherit port;
addr = "127.0.0.1";
}
];
root = "${pkgs.dtth-phanpy}/lib/phanpy";
};
}

View file

@ -1,4 +1,9 @@
{ pkgs, lib, config, ... }:
{
pkgs,
lib,
config,
...
}:
let
port = 61001;
user = "matrix-synapse";
@ -10,7 +15,9 @@ in
{
sops.secrets."matrix-synapse-dtth/oidc-config".owner = user;
sops.secrets."matrix-synapse-dtth/appservice-discord".owner = user;
sops.secrets.matrix-discord-bridge = { mode = "0644"; };
sops.secrets.matrix-discord-bridge = {
mode = "0644";
};
cloud.postgresql.databases = [ user ];
cloud.traefik.hosts.matrix-synapse = {
@ -29,20 +36,33 @@ in
enable = true;
withJemalloc = true;
dataDir = "${config.fileSystems.data.mountPoint}/matrix-synapse-dtth";
extras = [ "systemd" "url-preview" "oidc" "postgres" ];
extras = [
"systemd"
"url-preview"
"oidc"
"postgres"
];
settings = {
server_name = "dtth.ch";
enable_registration = false;
public_baseurl = "https://${host}/";
listeners = [{
inherit port;
x_forwarded = true;
tls = false;
resources = [
{ names = [ "client" "federation" ]; compress = false; }
];
}];
listeners = [
{
inherit port;
x_forwarded = true;
tls = false;
resources = [
{
names = [
"client"
"federation"
];
compress = false;
}
];
}
];
database = {
name = "psycopg2";
args = {
@ -96,25 +116,32 @@ in
};
services.nginx.virtualHosts.synapse-dtth-wellknown = {
listen = [{ addr = "127.0.0.1"; port = port + 1; }];
listen = [
{
addr = "127.0.0.1";
port = port + 1;
}
];
# Check https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/docs/configuring-well-known.md
# for the file structure.
root = pkgs.symlinkJoin
{
name = "well-known-files-for-synapse";
paths = [
(pkgs.writeTextDir ".well-known/matrix/client" (builtins.toJSON {
root = pkgs.symlinkJoin {
name = "well-known-files-for-synapse";
paths = [
(pkgs.writeTextDir ".well-known/matrix/client" (
builtins.toJSON {
"m.homeserver".base_url = "https://${host}";
}))
(pkgs.writeTextDir ".well-known/matrix/server" (builtins.toJSON {
}
))
(pkgs.writeTextDir ".well-known/matrix/server" (
builtins.toJSON {
"m.server" = "${host}:443";
}))
];
};
}
))
];
};
# Enable CORS from anywhere since we want all clients to find us out
extraConfig = ''
add_header 'Access-Control-Allow-Origin' "*";
'';
};
}

View file

@ -1,4 +1,9 @@
{ pkgs, lib, config, ... }:
{
pkgs,
lib,
config,
...
}:
let
secrets = config.sops.secrets;
@ -9,8 +14,12 @@ let
storageMount = "/mnt/data/vikunja";
in
{
sops.secrets."vikunja/env" = { restartUnits = [ "vikunja.service" ]; };
sops.secrets."vikunja/provider-clientsecret" = { restartUnits = [ "vikunja.service" ]; };
sops.secrets."vikunja/env" = {
restartUnits = [ "vikunja.service" ];
};
sops.secrets."vikunja/provider-clientsecret" = {
restartUnits = [ "vikunja.service" ];
};
cloud.postgresql.databases = [ user ];
cloud.traefik.hosts.vikunja = {
inherit port host;
@ -23,7 +32,6 @@ in
};
users.groups."${user}" = { };
services.vikunja = {
inherit port;
enable = true;
@ -81,7 +89,11 @@ in
};
systemd.services.vikunja = {
serviceConfig.LoadCredential = [ "VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHENTIK_CLIENTSECRET_FILE:${secrets."vikunja/provider-clientsecret".path}" ];
serviceConfig.LoadCredential = [
"VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHENTIK_CLIENTSECRET_FILE:${
secrets."vikunja/provider-clientsecret".path
}"
];
serviceConfig.User = user;
serviceConfig.DynamicUser = lib.mkForce false;
serviceConfig.ReadWritePaths = [ storageMount ];
@ -96,4 +108,3 @@ in
mode = "0700";
};
}

View file

@ -1,4 +1,9 @@
{ config, pkgs, lib, ... }:
{
config,
pkgs,
lib,
...
}:
with lib;
let
host = "blog.dtth.ch";
@ -11,7 +16,9 @@ in
# traefik
cloud.traefik.hosts.writefreely-dtth = { inherit host port; };
sops.secrets."writefreely-oauth-secret" = { owner = user; };
sops.secrets."writefreely-oauth-secret" = {
owner = user;
};
users.users.${user} = {
isSystemUser = true;
@ -65,16 +72,18 @@ in
tokenEndpoint = "/application/o/token/";
inspectEndpoint = "/application/o/userinfo/";
authEndpoint = "/application/o/authorize/";
scopes = [ "email" "openid" "profile" ];
scopes = [
"email"
"openid"
"profile"
];
mapUserId = "nickname";
mapUsername = "preferred_username";
mapDisplayName = "name";
};
database.type = "sqlite3";
admin.name = "nki";
};
}

View file

@ -1,21 +1,36 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
inherit (builtins) toString;
inherit (lib) types mkIf mkOption mkDefault;
inherit (lib) optional optionals optionalAttrs optionalString;
inherit (lib)
types
mkIf
mkOption
mkDefault
;
inherit (lib)
optional
optionals
optionalAttrs
optionalString
;
inherit (pkgs) sqlite;
format = pkgs.formats.ini {
mkKeyValue = key: value:
mkKeyValue =
key: value:
let
value' = lib.optionalString (value != null)
(if builtins.isBool value then
if value == true then "true" else "false"
else
toString value);
in "${key} = ${value'}";
value' = lib.optionalString (value != null) (
if builtins.isBool value then if value == true then "true" else "false" else toString value
);
in
"${key} = ${value'}";
};
cfg = config.nki.services.writefreely;
@ -31,49 +46,58 @@ let
host = cfg.settings.app.host or "${hostProtocol}://${cfg.host}";
};
database = if cfg.database.type == "sqlite3" then {
type = "sqlite3";
filename = cfg.settings.database.filename or "writefreely.db";
database = cfg.database.name;
} else {
type = "mysql";
username = cfg.database.user;
password = "#dbpass#";
database = cfg.database.name;
host = cfg.database.host;
port = cfg.database.port;
tls = cfg.database.tls;
};
database =
if cfg.database.type == "sqlite3" then
{
type = "sqlite3";
filename = cfg.settings.database.filename or "writefreely.db";
database = cfg.database.name;
}
else
{
type = "mysql";
username = cfg.database.user;
password = "#dbpass#";
database = cfg.database.name;
host = cfg.database.host;
port = cfg.database.port;
tls = cfg.database.tls;
};
server = cfg.settings.server or { } // {
bind = cfg.settings.server.bind or "localhost";
gopher_port = cfg.settings.server.gopher_port or 0;
autocert = !cfg.nginx.enable && cfg.acme.enable;
templates_parent_dir =
cfg.settings.server.templates_parent_dir or cfg.package.src;
templates_parent_dir = cfg.settings.server.templates_parent_dir or cfg.package.src;
static_parent_dir = cfg.settings.server.static_parent_dir or assets;
pages_parent_dir =
cfg.settings.server.pages_parent_dir or cfg.package.src;
pages_parent_dir = cfg.settings.server.pages_parent_dir or cfg.package.src;
keys_parent_dir = cfg.settings.server.keys_parent_dir or cfg.stateDir;
};
"oauth.generic" = cfg.settings."oauth.generic" or { } // (if cfg.oauth.enable then {
client_id = cfg.oauth.clientId;
client_secret = "#oauth_client_secret#";
host = cfg.oauth.host;
display_name = cfg.oauth.displayName;
callback_proxy = cfg.oauth.callbackProxy;
callback_proxy_api = cfg.oauth.callbackProxyApi;
token_endpoint = cfg.oauth.tokenEndpoint;
inspect_endpoint = cfg.oauth.inspectEndpoint;
auth_endpoint = cfg.oauth.authEndpoint;
scope = lib.concatStringsSep " " cfg.oauth.scopes;
allow_disconnect = cfg.oauth.allowDisconnect;
map_user_id = cfg.oauth.mapUserId;
map_username = cfg.oauth.mapUsername;
map_display_name = cfg.oauth.mapDisplayName;
map_email = cfg.oauth.mapEmail;
} else { });
"oauth.generic" =
cfg.settings."oauth.generic" or { }
// (
if cfg.oauth.enable then
{
client_id = cfg.oauth.clientId;
client_secret = "#oauth_client_secret#";
host = cfg.oauth.host;
display_name = cfg.oauth.displayName;
callback_proxy = cfg.oauth.callbackProxy;
callback_proxy_api = cfg.oauth.callbackProxyApi;
token_endpoint = cfg.oauth.tokenEndpoint;
inspect_endpoint = cfg.oauth.inspectEndpoint;
auth_endpoint = cfg.oauth.authEndpoint;
scope = lib.concatStringsSep " " cfg.oauth.scopes;
allow_disconnect = cfg.oauth.allowDisconnect;
map_user_id = cfg.oauth.mapUserId;
map_username = cfg.oauth.mapUsername;
map_display_name = cfg.oauth.mapDisplayName;
map_email = cfg.oauth.mapEmail;
}
else
{ }
);
};
configFile = format.generate "config.ini" settings;
@ -104,13 +128,9 @@ let
withConfigFile = text: ''
db_pass=${
optionalString (cfg.database.passwordFile != null)
"$(head -n1 ${cfg.database.passwordFile})"
}
oauth_client_secret=${
optionalString cfg.oauth.enable
"$(head -n1 ${cfg.oauth.clientSecretFile})"
optionalString (cfg.database.passwordFile != null) "$(head -n1 ${cfg.database.passwordFile})"
}
oauth_client_secret=${optionalString cfg.oauth.enable "$(head -n1 ${cfg.oauth.clientSecretFile})"}
cp -f ${configFile} '${cfg.stateDir}/config.ini'
sed -e "s,#dbpass#,$db_pass,g" -i '${cfg.stateDir}/config.ini'
@ -120,7 +140,8 @@ let
${text}
'';
withMysql = text:
withMysql =
text:
withConfigFile ''
query () {
local result=$(${config.services.mysql.package}/bin/mysql \
@ -139,7 +160,8 @@ let
${text}
'';
withSqlite = text:
withSqlite =
text:
withConfigFile ''
query () {
local result=$(${sqlite}/bin/sqlite3 \
@ -152,10 +174,10 @@ let
${text}
'';
in {
in
{
options.nki.services.writefreely = {
enable =
lib.mkEnableOption "Writefreely, build a digital writing community";
enable = lib.mkEnableOption "Writefreely, build a digital writing community";
package = lib.mkOption {
type = lib.types.package;
@ -223,7 +245,10 @@ in {
database = {
type = mkOption {
type = types.enum [ "sqlite3" "mysql" ];
type = types.enum [
"sqlite3"
"mysql"
];
default = "sqlite3";
description = "The database provider to use.";
};
@ -416,13 +441,11 @@ in {
}
{
assertion = isMysqlLocal -> cfg.database.passwordFile != null;
message =
"services.writefreely.database.passwordFile must be set if services.writefreely.database.createLocally is set to true";
message = "services.writefreely.database.passwordFile must be set if services.writefreely.database.createLocally is set to true";
}
{
assertion = isSqlite -> !cfg.database.createLocally;
message =
"services.writefreely.database.createLocally has no use when services.writefreely.database.type is set to sqlite3";
message = "services.writefreely.database.createLocally has no use when services.writefreely.database.type is set to sqlite3";
}
];
@ -435,8 +458,7 @@ in {
};
};
groups =
optionalAttrs (cfg.group == "writefreely") { writefreely = { }; };
groups = optionalAttrs (cfg.group == "writefreely") { writefreely = { }; };
};
systemd.tmpfiles.settings."10-writefreely".${cfg.stateDir}.d = {
@ -445,7 +467,8 @@ in {
};
systemd.services.writefreely = {
after = [ "network.target" ]
after =
[ "network.target" ]
++ optional isSqlite "writefreely-sqlite-init.service"
++ optional isMysql "writefreely-mysql-init.service"
++ optional isMysqlLocal "mysql.service";
@ -458,10 +481,8 @@ in {
WorkingDirectory = cfg.stateDir;
Restart = "always";
RestartSec = 20;
ExecStart =
"${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' serve";
AmbientCapabilities =
optionalString (settings.server.port < 1024) "cap_net_bind_service";
ExecStart = "${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' serve";
AmbientCapabilities = optionalString (settings.server.port < 1024) "cap_net_bind_service";
};
preStart = ''
@ -485,31 +506,32 @@ in {
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.stateDir;
ReadOnlyPaths = optional (cfg.admin.initialPasswordFile != null)
cfg.admin.initialPasswordFile;
ReadOnlyPaths = optional (cfg.admin.initialPasswordFile != null) cfg.admin.initialPasswordFile;
};
script = let
migrateDatabase = optionalString cfg.database.migrate ''
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
'';
script =
let
migrateDatabase = optionalString cfg.database.migrate ''
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
'';
createAdmin = optionalString (cfg.admin.name != null) ''
if [[ $(query "SELECT COUNT(*) FROM users") == 0 ]]; then
admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile})
createAdmin = optionalString (cfg.admin.name != null) ''
if [[ $(query "SELECT COUNT(*) FROM users") == 0 ]]; then
admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile})
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
fi
'';
in
withSqlite ''
if ! test -f '${settings.database.filename}'; then
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
fi
${migrateDatabase}
${createAdmin}
'';
in withSqlite ''
if ! test -f '${settings.database.filename}'; then
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
fi
${migrateDatabase}
${createAdmin}
'';
};
systemd.services.writefreely-mysql-init = mkIf isMysql {
@ -521,57 +543,61 @@ in {
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.stateDir;
ReadOnlyPaths = optional isMysqlLocal cfg.database.passwordFile
++ optional (cfg.admin.initialPasswordFile != null)
cfg.admin.initialPasswordFile;
ReadOnlyPaths =
optional isMysqlLocal cfg.database.passwordFile
++ optional (cfg.admin.initialPasswordFile != null) cfg.admin.initialPasswordFile;
};
script = let
updateUser = optionalString isMysqlLocal ''
# WriteFreely currently *requires* a password for authentication, so we
# need to update the user in MySQL accordingly. By default MySQL users
# authenticate with auth_socket or unix_socket.
# See: https://github.com/writefreely/writefreely/issues/568
${config.services.mysql.package}/bin/mysql --skip-column-names --execute "ALTER USER '${cfg.database.user}'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('$db_pass'); FLUSH PRIVILEGES;"
'';
script =
let
updateUser = optionalString isMysqlLocal ''
# WriteFreely currently *requires* a password for authentication, so we
# need to update the user in MySQL accordingly. By default MySQL users
# authenticate with auth_socket or unix_socket.
# See: https://github.com/writefreely/writefreely/issues/568
${config.services.mysql.package}/bin/mysql --skip-column-names --execute "ALTER USER '${cfg.database.user}'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('$db_pass'); FLUSH PRIVILEGES;"
'';
migrateDatabase = optionalString cfg.database.migrate ''
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
'';
migrateDatabase = optionalString cfg.database.migrate ''
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
'';
createAdmin = optionalString (cfg.admin.name != null) ''
if [[ $(query 'SELECT COUNT(*) FROM users') == 0 ]]; then
admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile})
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
createAdmin = optionalString (cfg.admin.name != null) ''
if [[ $(query 'SELECT COUNT(*) FROM users') == 0 ]]; then
admin_pass=$(head -n1 ${cfg.admin.initialPasswordFile})
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
fi
'';
in
withMysql ''
${updateUser}
if [[ $(query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${cfg.database.name}'") == 0 ]]; then
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
fi
${migrateDatabase}
${createAdmin}
'';
in withMysql ''
${updateUser}
if [[ $(query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${cfg.database.name}'") == 0 ]]; then
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
fi
${migrateDatabase}
${createAdmin}
'';
};
services.mysql = mkIf isMysqlLocal {
enable = true;
package = mkDefault pkgs.mariadb;
ensureDatabases = [ cfg.database.name ];
ensureUsers = [{
name = cfg.database.user;
ensurePermissions = {
"${cfg.database.name}.*" = "ALL PRIVILEGES";
# WriteFreely requires the use of passwords, so we need permissions
# to `ALTER` the user to add password support and also to reload
# permissions so they can be used.
"*.*" = "CREATE USER, RELOAD";
};
}];
ensureUsers = [
{
name = cfg.database.user;
ensurePermissions = {
"${cfg.database.name}.*" = "ALL PRIVILEGES";
# WriteFreely requires the use of passwords, so we need permissions
# to `ALTER` the user to add password support and also to reload
# permissions so they can be used.
"*.*" = "CREATE USER, RELOAD";
};
}
];
};
services.nginx = lib.mkIf cfg.nginx.enable {