Compare commits
1 commit
master
...
tlater/aut
Author | SHA1 | Date | |
---|---|---|---|
aef71f548a |
69 changed files with 3658 additions and 3641 deletions
.git-blame-ignore-revs.gitignore.sops.yaml
checks
configuration
default.nix
flake.lockflake.nixhardware-specific
nginx.nixservices
afvalcalendar.nixauth.nixbackups.nixbattery-manager.nixconduit.nix
sops.nixconduit
crowdsec.nixfail2ban.nixfoundryvtt.nixgitea.niximmich.nixmetrics
nextcloud.nixpostgres.nixstarbound.nixwebserver.nixwireguard.nixkeys
modules
pkgs
_sources_nextcloud
_sources_pkgs
afvalcalendar
crowdsec
default.nixmkNextcloudApp.nixnextcloud-apps.tomlnvfetcher.tomlprometheus
starbound
|
@ -1,14 +0,0 @@
|
||||||
# Run this command to always ignore formatting commits in `git blame`
|
|
||||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
|
||||||
|
|
||||||
# Switch to nixfmt formatting
|
|
||||||
04f7a7ef1d38906163afc9cddfa8ce2b0ebf3b45
|
|
||||||
|
|
||||||
# Switch to nixpkgs-fmt formatting
|
|
||||||
fd138d45e6a2cad89fead6e9f246ba282070d6b7
|
|
||||||
|
|
||||||
# Switch to alejandra formatting
|
|
||||||
046a88905ddfa7f9edc3291c310dbb985dee34f9
|
|
||||||
|
|
||||||
# Apply wide linting
|
|
||||||
63b3cbe00be80ccb4b221aad64eb657ae5c96d70
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
||||||
/result
|
/result
|
||||||
*.qcow2
|
*.qcow2
|
||||||
/gcroots/
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
keys:
|
keys:
|
||||||
- &tlater B9C7AE554602D82B1AC2CFD0BC7BB2DB17C78E42!
|
- &tlater 535B61015823443941C744DD12264F6BBDFABA89
|
||||||
- &server_tlaternet 8a3737d48f1035fe6c3a0a8fd6a1976ca74c7f3b
|
- &server_tlaternet 8a3737d48f1035fe6c3a0a8fd6a1976ca74c7f3b
|
||||||
- &server_hetzner1 0af7641adb8aa843136cf6d047f71da3e5ad79f9
|
- &server_hetzner1 0af7641adb8aa843136cf6d047f71da3e5ad79f9
|
||||||
- &server_staging 2f5caa73e7ceea4fcc8d2881fde587e6737d2dbc
|
- &server_staging 2f5caa73e7ceea4fcc8d2881fde587e6737d2dbc
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
{
|
|
||||||
self,
|
|
||||||
nixpkgs,
|
|
||||||
deploy-rs,
|
|
||||||
system,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
|
||||||
|
|
||||||
statix' = pkgs.statix.overrideAttrs (old: {
|
|
||||||
patches = old.patches ++ [
|
|
||||||
(pkgs.fetchpatch {
|
|
||||||
url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
|
|
||||||
hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
|
|
||||||
})
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
runNuCheck =
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
packages,
|
|
||||||
check,
|
|
||||||
}:
|
|
||||||
pkgs.stdenvNoCC.mkDerivation {
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
src = nixpkgs.lib.cleanSourceWith {
|
|
||||||
src = self;
|
|
||||||
filter = nixpkgs.lib.cleanSourceFilter;
|
|
||||||
};
|
|
||||||
|
|
||||||
dontPatch = true;
|
|
||||||
dontConfigure = true;
|
|
||||||
dontBuild = true;
|
|
||||||
dontInstall = true;
|
|
||||||
dontFixup = true;
|
|
||||||
doCheck = true;
|
|
||||||
|
|
||||||
checkInputs = nixpkgs.lib.singleton pkgs.nushell ++ packages;
|
|
||||||
|
|
||||||
checkPhase = ''
|
|
||||||
nu ${check}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
|
||||||
nixpkgs.lib.recursiveUpdate {
|
|
||||||
lints = runNuCheck {
|
|
||||||
name = "lints";
|
|
||||||
|
|
||||||
packages = [
|
|
||||||
pkgs.deadnix
|
|
||||||
pkgs.nixfmt-rfc-style
|
|
||||||
pkgs.shellcheck
|
|
||||||
statix'
|
|
||||||
];
|
|
||||||
|
|
||||||
check = ./lints.nu;
|
|
||||||
};
|
|
||||||
} (deploy-rs.lib.${system}.deployChecks self.deploy)
|
|
|
@ -1,39 +0,0 @@
|
||||||
#!/usr/bin/env nu
|
|
||||||
|
|
||||||
let shell_files = ls **/*.sh | get name
|
|
||||||
let nix_files = ls **/*.nix | where name !~ "hardware-configuration.nix|_sources" | get name
|
|
||||||
|
|
||||||
let linters = [
|
|
||||||
([shellcheck] ++ $shell_files)
|
|
||||||
([nixfmt --check --strict] ++ $nix_files)
|
|
||||||
([deadnix --fail] ++ $nix_files)
|
|
||||||
([statix check] ++ $nix_files)
|
|
||||||
]
|
|
||||||
|
|
||||||
mkdir $env.out
|
|
||||||
|
|
||||||
def run-linter [linterArgs: list<string>] {
|
|
||||||
print $'Running ($linterArgs.0)...'
|
|
||||||
|
|
||||||
let exit_code = try {
|
|
||||||
^$linterArgs.0 ...($linterArgs | skip 1)
|
|
||||||
$env.LAST_EXIT_CODE
|
|
||||||
} catch {|e| $e.exit_code}
|
|
||||||
|
|
||||||
[$linterArgs.0, $exit_code]
|
|
||||||
}
|
|
||||||
|
|
||||||
let results = $linters | each {|linter| run-linter $linter}
|
|
||||||
|
|
||||||
print 'Linter results:'
|
|
||||||
|
|
||||||
let success = $results | each {|result|
|
|
||||||
match $result.1 {
|
|
||||||
0 => {print $'(ansi green)($result.0)(ansi reset)'}
|
|
||||||
_ => {print $'(ansi red)($result.0)(ansi reset)'}
|
|
||||||
}
|
|
||||||
|
|
||||||
$result.1
|
|
||||||
} | math sum
|
|
||||||
|
|
||||||
exit $success
|
|
|
@ -1,10 +1,11 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
modulesPath,
|
modulesPath,
|
||||||
flake-inputs,
|
flake-inputs,
|
||||||
...
|
...
|
||||||
}:
|
}: {
|
||||||
{
|
|
||||||
imports = [
|
imports = [
|
||||||
flake-inputs.disko.nixosModules.disko
|
flake-inputs.disko.nixosModules.disko
|
||||||
flake-inputs.sops-nix.nixosModules.sops
|
flake-inputs.sops-nix.nixosModules.sops
|
||||||
|
@ -13,40 +14,49 @@
|
||||||
"${modulesPath}/profiles/minimal.nix"
|
"${modulesPath}/profiles/minimal.nix"
|
||||||
(import ../modules)
|
(import ../modules)
|
||||||
|
|
||||||
|
./services/afvalcalendar.nix
|
||||||
|
./services/auth.nix
|
||||||
./services/backups.nix
|
./services/backups.nix
|
||||||
./services/battery-manager.nix
|
./services/battery-manager.nix
|
||||||
./services/conduit
|
./services/conduit.nix
|
||||||
./services/crowdsec.nix
|
./services/fail2ban.nix
|
||||||
./services/foundryvtt.nix
|
./services/foundryvtt.nix
|
||||||
./services/gitea.nix
|
./services/gitea.nix
|
||||||
./services/immich.nix
|
|
||||||
./services/metrics
|
./services/metrics
|
||||||
./services/nextcloud.nix
|
./services/nextcloud.nix
|
||||||
./services/webserver.nix
|
./services/webserver.nix
|
||||||
./services/wireguard.nix
|
./services/wireguard.nix
|
||||||
# ./services/starbound.nix -- Not currently used
|
./services/starbound.nix
|
||||||
./services/postgres.nix
|
./services/postgres.nix
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
./sops.nix
|
./sops.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
nixpkgs.overlays = [ (_: prev: { local = import ../pkgs { pkgs = prev; }; }) ];
|
nixpkgs.overlays = [
|
||||||
|
(final: prev: {
|
||||||
|
local = import ../pkgs {
|
||||||
|
pkgs = prev;
|
||||||
|
lib = prev.lib;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
nix = {
|
nix = {
|
||||||
|
package = pkgs.nixFlakes;
|
||||||
extraOptions = ''
|
extraOptions = ''
|
||||||
experimental-features = nix-command flakes
|
experimental-features = nix-command flakes
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Enable remote builds from tlater
|
# Enable remote builds from tlater
|
||||||
settings.trusted-users = [ "@wheel" ];
|
settings.trusted-users = ["@wheel"];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nixpkgs.config.allowUnfreePredicate = pkg:
|
||||||
|
builtins.elem (lib.getName pkg) ["steam-original" "steam-runtime" "steam-run" "steamcmd"];
|
||||||
|
|
||||||
# Optimization for minecraft servers, see:
|
# Optimization for minecraft servers, see:
|
||||||
# https://bugs.mojang.com/browse/MC-183518
|
# https://bugs.mojang.com/browse/MC-183518
|
||||||
boot.kernelParams = [
|
boot.kernelParams = ["highres=off" "nohz=off"];
|
||||||
"highres=off"
|
|
||||||
"nohz=off"
|
|
||||||
];
|
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
usePredictableInterfaceNames = false;
|
usePredictableInterfaceNames = false;
|
||||||
|
@ -97,15 +107,15 @@
|
||||||
|
|
||||||
users.users.tlater = {
|
users.users.tlater = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
extraGroups = [ "wheel" ];
|
extraGroups = ["wheel"];
|
||||||
openssh.authorizedKeys.keyFiles = [ ../keys/tlater.pub ];
|
openssh.authorizedKeys.keyFiles = [../keys/tlater.pub];
|
||||||
};
|
};
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
openssh = {
|
openssh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
allowSFTP = false;
|
allowSFTP = false;
|
||||||
ports = [ 2222 ];
|
ports = [2222];
|
||||||
startWhenNeeded = true;
|
startWhenNeeded = true;
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
|
@ -124,14 +134,14 @@
|
||||||
pam = {
|
pam = {
|
||||||
sshAgentAuth = {
|
sshAgentAuth = {
|
||||||
enable = true;
|
enable = true;
|
||||||
authorizedKeysFiles = [ "/etc/ssh/authorized_keys.d/%u" ];
|
authorizedKeysFiles = ["/etc/ssh/authorized_keys.d/%u"];
|
||||||
};
|
};
|
||||||
services.sudo.sshAgentAuth = true;
|
services.sudo.sshAgentAuth = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Remove some unneeded packages
|
# Remove some unneeded packages
|
||||||
environment.defaultPackages = [ ];
|
environment.defaultPackages = [];
|
||||||
|
|
||||||
system.stateVersion = "20.09";
|
system.stateVersion = "20.09";
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
# disables it by default.
|
# disables it by default.
|
||||||
#
|
#
|
||||||
# TODO(tlater): See if would be useful for anything?
|
# TODO(tlater): See if would be useful for anything?
|
||||||
boot.kernelParams = [ "nosgx" ];
|
boot.kernelParams = ["nosgx"];
|
||||||
|
|
||||||
networking.hostName = "hetzner-1";
|
networking.hostName = "hetzner-1";
|
||||||
services.nginx.domain = "tlater.net";
|
services.nginx.domain = "tlater.net";
|
||||||
|
@ -19,11 +19,15 @@
|
||||||
addresses = [
|
addresses = [
|
||||||
# IPv4
|
# IPv4
|
||||||
{
|
{
|
||||||
Address = "116.202.158.55/32";
|
addressConfig = {
|
||||||
Peer = "116.202.158.1/32"; # Gateway
|
Address = "116.202.158.55/32";
|
||||||
|
Peer = "116.202.158.1/32"; # Gateway
|
||||||
|
};
|
||||||
}
|
}
|
||||||
# IPv6
|
# IPv6
|
||||||
{ Address = "2a01:4f8:10b:3c85::2/64"; }
|
{
|
||||||
|
addressConfig.Address = "2a01:4f8:10b:3c85::2/64";
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
networkConfig = {
|
networkConfig = {
|
||||||
|
|
|
@ -1,106 +1,82 @@
|
||||||
{
|
{
|
||||||
disko.devices.disk =
|
disko.devices.disk = let
|
||||||
let
|
bootPartition = {
|
||||||
bootPartition = {
|
size = "1M";
|
||||||
size = "1M";
|
type = "EF02";
|
||||||
type = "EF02";
|
};
|
||||||
|
|
||||||
|
swapPartition = {
|
||||||
|
# 8G is apparently recommended for this much RAM, but we set up
|
||||||
|
# 4G on both disks for mirroring purposes.
|
||||||
|
#
|
||||||
|
# That'll still be 8G during normal operation, and it's probably
|
||||||
|
# not too bad to have slightly less swap if a disk dies.
|
||||||
|
size = "4G";
|
||||||
|
content = {
|
||||||
|
type = "swap";
|
||||||
|
randomEncryption = true;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
swapPartition = {
|
mountOptions = ["compress=zstd" "noatime"];
|
||||||
# 8G is apparently recommended for this much RAM, but we set up
|
in {
|
||||||
# 4G on both disks for mirroring purposes.
|
sda = {
|
||||||
#
|
type = "disk";
|
||||||
# That'll still be 8G during normal operation, and it's probably
|
device = "/dev/sda";
|
||||||
# not too bad to have slightly less swap if a disk dies.
|
content = {
|
||||||
size = "4G";
|
type = "gpt";
|
||||||
content = {
|
partitions = {
|
||||||
type = "swap";
|
boot = bootPartition;
|
||||||
randomEncryption = true;
|
swap = swapPartition;
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
mountOptions = [
|
disk1 = {
|
||||||
"compress=zstd"
|
size = "100%";
|
||||||
"noatime"
|
# Empty partition to combine in RAID0 with the other disk
|
||||||
];
|
|
||||||
in
|
|
||||||
{
|
|
||||||
sda = {
|
|
||||||
type = "disk";
|
|
||||||
device = "/dev/sda";
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
partitions = {
|
|
||||||
boot = bootPartition;
|
|
||||||
swap = swapPartition;
|
|
||||||
|
|
||||||
disk1 = {
|
|
||||||
size = "100%";
|
|
||||||
# Empty partition to combine in RAID0 with the other disk
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
sdb = {
|
sdb = {
|
||||||
type = "disk";
|
type = "disk";
|
||||||
device = "/dev/sdb";
|
device = "/dev/sdb";
|
||||||
content = {
|
content = {
|
||||||
type = "gpt";
|
type = "gpt";
|
||||||
partitions = {
|
partitions = {
|
||||||
boot = bootPartition;
|
boot = bootPartition;
|
||||||
swap = swapPartition;
|
swap = swapPartition;
|
||||||
|
|
||||||
disk2 = {
|
disk2 = {
|
||||||
size = "100%";
|
size = "100%";
|
||||||
content = {
|
content = {
|
||||||
type = "btrfs";
|
type = "btrfs";
|
||||||
# Hack to get multi-device btrfs going
|
# Hack to get multi-device btrfs going
|
||||||
# See https://github.com/nix-community/disko/issues/99
|
# See https://github.com/nix-community/disko/issues/99
|
||||||
extraArgs = [
|
extraArgs = ["-d" "raid1" "-m" "raid1" "--runtime-features" "quota" "/dev/sda3"];
|
||||||
"-d"
|
subvolumes = {
|
||||||
"raid1"
|
"/volume" = {};
|
||||||
"-m"
|
"/volume/root" = {
|
||||||
"raid1"
|
inherit mountOptions;
|
||||||
"--runtime-features"
|
mountpoint = "/";
|
||||||
"quota"
|
|
||||||
"/dev/sda3"
|
|
||||||
];
|
|
||||||
subvolumes = {
|
|
||||||
"/volume" = { };
|
|
||||||
"/volume/root" = {
|
|
||||||
inherit mountOptions;
|
|
||||||
mountpoint = "/";
|
|
||||||
};
|
|
||||||
"/volume/home" = {
|
|
||||||
inherit mountOptions;
|
|
||||||
mountpoint = "/home";
|
|
||||||
};
|
|
||||||
"/volume/var" = {
|
|
||||||
inherit mountOptions;
|
|
||||||
mountpoint = "/var";
|
|
||||||
};
|
|
||||||
"/volume/var/lib/private/matrix-conduit" = {
|
|
||||||
mountOptions = [
|
|
||||||
# Explicitly don't compress here, since
|
|
||||||
# conduwuit's database does compression by
|
|
||||||
# itself, and relies on being able to read the
|
|
||||||
# raw file data from disk (which is impossible
|
|
||||||
# if btrfs compresses it)
|
|
||||||
"noatime"
|
|
||||||
];
|
|
||||||
mountpoint = "/var/lib/private/matrix-conduit";
|
|
||||||
};
|
|
||||||
"/volume/nix-store" = {
|
|
||||||
inherit mountOptions;
|
|
||||||
mountpoint = "/nix";
|
|
||||||
};
|
|
||||||
"/snapshots" = { };
|
|
||||||
};
|
};
|
||||||
|
"/volume/home" = {
|
||||||
|
inherit mountOptions;
|
||||||
|
mountpoint = "/home";
|
||||||
|
};
|
||||||
|
"/volume/var" = {
|
||||||
|
inherit mountOptions;
|
||||||
|
mountpoint = "/var";
|
||||||
|
};
|
||||||
|
"/volume/nix-store" = {
|
||||||
|
inherit mountOptions;
|
||||||
|
mountpoint = "/nix";
|
||||||
|
};
|
||||||
|
"/snapshots" = {};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,19 @@
|
||||||
{ lib, ... }:
|
{lib, ...}: {
|
||||||
{
|
|
||||||
users.users.tlater.password = "insecure";
|
users.users.tlater.password = "insecure";
|
||||||
|
|
||||||
# Disable graphical tty so -curses works
|
# Disable graphical tty so -curses works
|
||||||
boot.kernelParams = [ "nomodeset" ];
|
boot.kernelParams = ["nomodeset"];
|
||||||
|
|
||||||
networking.hostName = "testvm";
|
networking.hostName = "testvm";
|
||||||
|
# Sets the base domain for nginx to a local domain so that we can
|
||||||
services = {
|
# easily test locally with the VM.
|
||||||
# Sets the base domain for nginx to a local domain so that we can
|
services.nginx.domain = "dev.local";
|
||||||
# easily test locally with the VM.
|
|
||||||
nginx.domain = "dev.local";
|
|
||||||
|
|
||||||
# Don't run this
|
|
||||||
batteryManager.enable = lib.mkForce false;
|
|
||||||
|
|
||||||
openssh.hostKeys = lib.mkForce [
|
|
||||||
{
|
|
||||||
type = "rsa";
|
|
||||||
bits = 4096;
|
|
||||||
path = "/etc/staging.key";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Use the staging secrets
|
# Use the staging secrets
|
||||||
sops.defaultSopsFile = lib.mkOverride 99 ../../keys/staging.yaml;
|
sops.defaultSopsFile = lib.mkOverride 99 ../../keys/staging.yaml;
|
||||||
|
|
||||||
systemd.network.networks."10-eth0" = {
|
systemd.network.networks."10-eth0" = {
|
||||||
matchConfig.Name = "eth0";
|
matchConfig.Name = "eth0";
|
||||||
gateway = [ "192.168.9.1" ];
|
|
||||||
networkConfig = {
|
networkConfig = {
|
||||||
Address = "192.168.9.2/24";
|
Address = "192.168.9.2/24";
|
||||||
};
|
};
|
||||||
|
@ -43,6 +27,14 @@
|
||||||
source = ../../keys/hosts/staging.key;
|
source = ../../keys/hosts/staging.key;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.openssh.hostKeys = lib.mkForce [
|
||||||
|
{
|
||||||
|
type = "rsa";
|
||||||
|
bits = 4096;
|
||||||
|
path = "/etc/staging.key";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
virtualisation.vmVariant = {
|
virtualisation.vmVariant = {
|
||||||
virtualisation = {
|
virtualisation = {
|
||||||
memorySize = 3941;
|
memorySize = 3941;
|
||||||
|
|
|
@ -1,78 +1,67 @@
|
||||||
{ config, lib, ... }:
|
|
||||||
{
|
{
|
||||||
services = {
|
config,
|
||||||
nginx = {
|
lib,
|
||||||
enable = true;
|
...
|
||||||
recommendedTlsSettings = true;
|
}: {
|
||||||
recommendedOptimisation = true;
|
services.nginx = {
|
||||||
recommendedGzipSettings = true;
|
enable = true;
|
||||||
recommendedProxySettings = true;
|
recommendedTlsSettings = true;
|
||||||
clientMaxBodySize = "10G";
|
recommendedOptimisation = true;
|
||||||
|
recommendedGzipSettings = true;
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
clientMaxBodySize = "10G";
|
||||||
|
|
||||||
statusPage = true; # For metrics, should be accessible only from localhost
|
statusPage = true; # For metrics, should be accessible only from localhost
|
||||||
|
|
||||||
commonHttpConfig = ''
|
commonHttpConfig = ''
|
||||||
log_format upstream_time '$remote_addr - $remote_user [$time_local] '
|
log_format upstream_time '$remote_addr - $remote_user [$time_local] '
|
||||||
'"$request" $status $body_bytes_sent '
|
'"$request" $status $body_bytes_sent '
|
||||||
'"$http_referer" "$http_user_agent" '
|
'"$http_referer" "$http_user_agent" '
|
||||||
'rt=$request_time uct="$upstream_connect_time" '
|
'rt=$request_time uct="$upstream_connect_time" '
|
||||||
'uht="$upstream_header_time" urt="$upstream_response_time"';
|
'uht="$upstream_header_time" urt="$upstream_response_time"';
|
||||||
'';
|
'';
|
||||||
};
|
|
||||||
|
|
||||||
logrotate.settings =
|
|
||||||
{
|
|
||||||
# Override the default, just keep fewer logs
|
|
||||||
nginx.rotate = 6;
|
|
||||||
}
|
|
||||||
// lib.mapAttrs' (
|
|
||||||
virtualHost: _:
|
|
||||||
lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" {
|
|
||||||
frequency = "daily";
|
|
||||||
rotate = 2;
|
|
||||||
compress = true;
|
|
||||||
delaycompress = true;
|
|
||||||
su = "${config.services.nginx.user} ${config.services.nginx.group}";
|
|
||||||
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
|
|
||||||
}
|
|
||||||
) config.services.nginx.virtualHosts;
|
|
||||||
|
|
||||||
backups.acme = {
|
|
||||||
user = "acme";
|
|
||||||
paths = lib.mapAttrsToList (
|
|
||||||
virtualHost: _: "/var/lib/acme/${virtualHost}"
|
|
||||||
) config.services.nginx.virtualHosts;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.tmpfiles.rules = lib.mapAttrsToList (
|
services.logrotate.settings =
|
||||||
virtualHost: _:
|
{
|
||||||
#
|
# Override the default, just keep fewer logs
|
||||||
"d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}"
|
nginx.rotate = 6;
|
||||||
) config.services.nginx.virtualHosts;
|
}
|
||||||
|
// lib.mapAttrs' (virtualHost: _:
|
||||||
|
lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" {
|
||||||
|
frequency = "daily";
|
||||||
|
rotate = 2;
|
||||||
|
compress = true;
|
||||||
|
delaycompress = true;
|
||||||
|
su = "${config.services.nginx.user} ${config.services.nginx.group}";
|
||||||
|
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
|
||||||
|
})
|
||||||
|
config.services.nginx.virtualHosts;
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules =
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
virtualHost: _:
|
||||||
|
#
|
||||||
|
"d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}"
|
||||||
|
)
|
||||||
|
config.services.nginx.virtualHosts;
|
||||||
|
|
||||||
security.acme = {
|
security.acme = {
|
||||||
defaults.email = "tm@tlater.net";
|
defaults.email = "tm@tlater.net";
|
||||||
acceptTerms = true;
|
acceptTerms = true;
|
||||||
|
|
||||||
certs."tlater.net" = {
|
certs."tlater.net" = {
|
||||||
extraDomainNames = [
|
extraDomainNames = ["*.tlater.net"];
|
||||||
"*.tlater.net"
|
dnsProvider = "hetzner";
|
||||||
"tlater.com"
|
group = "nginx";
|
||||||
"*.tlater.com"
|
credentialFiles."HETZNER_API_KEY_FILE" = config.sops.secrets."hetzner-api".path;
|
||||||
];
|
|
||||||
dnsProvider = "porkbun";
|
|
||||||
group = "ssl-cert";
|
|
||||||
credentialFiles = {
|
|
||||||
PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
|
|
||||||
PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups.ssl-cert = { };
|
services.backups.acme = {
|
||||||
|
user = "acme";
|
||||||
systemd.services.nginx.serviceConfig.SupplementaryGroups = [
|
paths =
|
||||||
config.security.acme.certs."tlater.net".group
|
lib.mapAttrsToList (virtualHost: _: "/var/lib/acme/${virtualHost}")
|
||||||
];
|
config.services.nginx.virtualHosts;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
67
configuration/services/afvalcalendar.nix
Normal file
67
configuration/services/afvalcalendar.nix
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
systemd.services.afvalcalendar = {
|
||||||
|
description = "Enschede afvalcalendar -> ical converter";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
after = ["network.target"];
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
${pkgs.local.afvalcalendar}/bin/afvalcalendar > /srv/afvalcalendar/afvalcalendar.ical
|
||||||
|
'';
|
||||||
|
|
||||||
|
startAt = "daily";
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
DynamicUser = true;
|
||||||
|
ProtectHome = true; # Override the default (read-only)
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateIPC = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = ["@system-service" "~@privileged @resources @setuid @keyring"];
|
||||||
|
|
||||||
|
Umask = 0002;
|
||||||
|
SupplementaryGroups = "afvalcalendar-hosting";
|
||||||
|
|
||||||
|
ReadWritePaths = "/srv/afvalcalendar";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."afvalcalendar.${config.services.nginx.domain}" = {
|
||||||
|
forceSSL = true;
|
||||||
|
useACMEHost = "tlater.net";
|
||||||
|
enableHSTS = true;
|
||||||
|
|
||||||
|
root = "/srv/afvalcalendar";
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.afvalcalendar-hosting = {};
|
||||||
|
systemd.tmpfiles.settings."10-afvalcalendar" = {
|
||||||
|
"/srv/afvalcalendar".d = {
|
||||||
|
user = "nginx";
|
||||||
|
group = "afvalcalendar-hosting";
|
||||||
|
mode = "0775";
|
||||||
|
};
|
||||||
|
|
||||||
|
"/srv/afvalcalendar/afvalcalendar.ical".f = {
|
||||||
|
user = "nginx";
|
||||||
|
group = "afvalcalendar-hosting";
|
||||||
|
mode = "0775";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
95
configuration/services/auth.nix
Normal file
95
configuration/services/auth.nix
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
user = config.services.authelia.instances.main.user;
|
||||||
|
domain = "auth.${config.services.nginx.domain}";
|
||||||
|
in {
|
||||||
|
services.authelia.instances.main = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
theme = "auto";
|
||||||
|
|
||||||
|
access_control.default_policy = "one_factor";
|
||||||
|
|
||||||
|
authentication_backend = {
|
||||||
|
password_reset.disable = true;
|
||||||
|
file.path = "/var/lib/authelia-main/users.yml";
|
||||||
|
};
|
||||||
|
|
||||||
|
notifier.filesystem.filename = "/var/lib/authelia-main/notification.txt";
|
||||||
|
|
||||||
|
session = {
|
||||||
|
domain = config.services.nginx.domain;
|
||||||
|
redis.host = config.services.redis.servers.authelia.unixSocket;
|
||||||
|
};
|
||||||
|
|
||||||
|
storage.postgres = {
|
||||||
|
host = "/run/postgresql";
|
||||||
|
port = 5432;
|
||||||
|
database = user;
|
||||||
|
username = user;
|
||||||
|
|
||||||
|
password = "unnecessary";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
secrets = {
|
||||||
|
storageEncryptionKeyFile = config.sops.secrets."authelia/storageEncryptionKey".path; # Database
|
||||||
|
sessionSecretFile = config.sops.secrets."authelia/sessionSecret".path; # Redis
|
||||||
|
jwtSecretFile = config.sops.secrets."authelia/jwtSecret".path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.authelia-main.after = ["postgresql.service"];
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
# TODO(tlater): Possibly remove on next authelia release
|
||||||
|
additionalModules = with pkgs.nginxModules; [
|
||||||
|
develkit
|
||||||
|
set-misc
|
||||||
|
];
|
||||||
|
|
||||||
|
virtualHosts."${domain}" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
enableHSTS = true;
|
||||||
|
|
||||||
|
locations = {
|
||||||
|
"/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:9091";
|
||||||
|
recommendedProxySettings = false;
|
||||||
|
enableAutheliaProxy = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
"/api/verify" = {
|
||||||
|
proxyPass = "http://127.0.0.1:9091";
|
||||||
|
recommendedProxySettings = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.redis.servers.authelia = {
|
||||||
|
inherit user;
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets = {
|
||||||
|
"authelia/storageEncryptionKey" = {
|
||||||
|
owner = user;
|
||||||
|
group = user;
|
||||||
|
};
|
||||||
|
|
||||||
|
"authelia/sessionSecret" = {
|
||||||
|
owner = user;
|
||||||
|
group = user;
|
||||||
|
};
|
||||||
|
|
||||||
|
"authelia/jwtSecret" = {
|
||||||
|
owner = user;
|
||||||
|
group = user;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,33 +3,27 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
inherit (lib) types optional singleton;
|
inherit (lib) types optional singleton;
|
||||||
mkShutdownScript =
|
mkShutdownScript = service:
|
||||||
service:
|
|
||||||
pkgs.writeShellScript "backup-${service}-shutdown" ''
|
pkgs.writeShellScript "backup-${service}-shutdown" ''
|
||||||
if systemctl is-active --quiet '${service}'; then
|
if systemctl is-active --quiet '${service}'; then
|
||||||
touch '/tmp/${service}-was-active'
|
touch '/tmp/${service}-was-active'
|
||||||
systemctl stop '${service}'
|
systemctl stop '${service}'
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
mkRestartScript =
|
mkRestartScript = service:
|
||||||
service:
|
|
||||||
pkgs.writeShellScript "backup-${service}-restart" ''
|
pkgs.writeShellScript "backup-${service}-restart" ''
|
||||||
if [ -f '/tmp/${service}-was-active' ]; then
|
if [ -f '/tmp/${service}-was-active' ]; then
|
||||||
rm '/tmp/${service}-was-active'
|
rm '/tmp/${service}-was-active'
|
||||||
systemctl start '${service}'
|
systemctl start '${service}'
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
writeScript =
|
writeScript = name: packages: text:
|
||||||
name: packages: text:
|
lib.getExe (pkgs.writeShellApplication {
|
||||||
lib.getExe (
|
inherit name text;
|
||||||
pkgs.writeShellApplication {
|
runtimeInputs = packages;
|
||||||
inherit name text;
|
});
|
||||||
runtimeInputs = packages;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
# *NOT* a TOML file, for some reason quotes are interpreted
|
# *NOT* a TOML file, for some reason quotes are interpreted
|
||||||
# *literally
|
# *literally
|
||||||
|
@ -48,98 +42,96 @@ let
|
||||||
RESTIC_REPOSITORY = "rclone:storagebox:backups";
|
RESTIC_REPOSITORY = "rclone:storagebox:backups";
|
||||||
RCLONE_CONFIG = rcloneConfig;
|
RCLONE_CONFIG = rcloneConfig;
|
||||||
};
|
};
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
options = {
|
options = {
|
||||||
services.backups = lib.mkOption {
|
services.backups = lib.mkOption {
|
||||||
description = lib.mdDoc ''
|
description = lib.mdDoc ''
|
||||||
Configure restic backups with a specific tag.
|
Configure restic backups with a specific tag.
|
||||||
'';
|
'';
|
||||||
type = types.attrsOf (
|
type = types.attrsOf (types.submodule ({
|
||||||
types.submodule (
|
config,
|
||||||
{ name, ... }:
|
name,
|
||||||
{
|
...
|
||||||
options = {
|
}: {
|
||||||
user = lib.mkOption {
|
options = {
|
||||||
type = types.str;
|
user = lib.mkOption {
|
||||||
description = ''
|
type = types.str;
|
||||||
The user as which to run the backup.
|
description = ''
|
||||||
'';
|
The user as which to run the backup.
|
||||||
};
|
'';
|
||||||
paths = lib.mkOption {
|
};
|
||||||
type = types.listOf types.str;
|
paths = lib.mkOption {
|
||||||
description = ''
|
type = types.listOf types.str;
|
||||||
The paths to back up.
|
description = ''
|
||||||
'';
|
The paths to back up.
|
||||||
};
|
'';
|
||||||
tag = lib.mkOption {
|
};
|
||||||
type = types.str;
|
tag = lib.mkOption {
|
||||||
description = ''
|
type = types.str;
|
||||||
The restic tag to mark the backup with.
|
description = ''
|
||||||
'';
|
The restic tag to mark the backup with.
|
||||||
default = name;
|
'';
|
||||||
};
|
default = name;
|
||||||
preparation = {
|
};
|
||||||
packages = lib.mkOption {
|
preparation = {
|
||||||
type = types.listOf types.package;
|
packages = lib.mkOption {
|
||||||
default = [ ];
|
type = types.listOf types.package;
|
||||||
description = ''
|
default = [];
|
||||||
The list of packages to make available in the
|
description = ''
|
||||||
preparation script.
|
The list of packages to make available in the
|
||||||
'';
|
preparation script.
|
||||||
};
|
'';
|
||||||
text = lib.mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
The preparation script to run before the backup.
|
|
||||||
|
|
||||||
This should include things like database dumps and
|
|
||||||
enabling maintenance modes. If a service needs to be
|
|
||||||
shut down for backups, use `pauseServices` instead.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
cleanup = {
|
|
||||||
packages = lib.mkOption {
|
|
||||||
type = types.listOf types.package;
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
The list of packages to make available in the
|
|
||||||
cleanup script.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
text = lib.mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
The cleanup script to run after the backup.
|
|
||||||
|
|
||||||
This should do things like cleaning up database dumps
|
|
||||||
and disabling maintenance modes.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
pauseServices = lib.mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
The systemd services that need to be shut down before
|
|
||||||
the backup can run. Services will be restarted after the
|
|
||||||
backup is complete.
|
|
||||||
|
|
||||||
This is intended to be used for services that do not
|
|
||||||
support hot backups.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
text = lib.mkOption {
|
||||||
)
|
type = types.nullOr types.str;
|
||||||
);
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The preparation script to run before the backup.
|
||||||
|
|
||||||
|
This should include things like database dumps and
|
||||||
|
enabling maintenance modes. If a service needs to be
|
||||||
|
shut down for backups, use `pauseServices` instead.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
cleanup = {
|
||||||
|
packages = lib.mkOption {
|
||||||
|
type = types.listOf types.package;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
The list of packages to make available in the
|
||||||
|
cleanup script.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
text = lib.mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The cleanup script to run after the backup.
|
||||||
|
|
||||||
|
This should do things like cleaning up database dumps
|
||||||
|
and disabling maintenance modes.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
pauseServices = lib.mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
The systemd services that need to be shut down before
|
||||||
|
the backup can run. Services will be restarted after the
|
||||||
|
backup is complete.
|
||||||
|
|
||||||
|
This is intended to be used for services that do not
|
||||||
|
support hot backups.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf (config.services.backups != { }) {
|
config = lib.mkIf (config.services.backups != {}) {
|
||||||
systemd.services =
|
systemd.services =
|
||||||
{
|
{
|
||||||
restic-prune = {
|
restic-prune = {
|
||||||
|
@ -172,15 +164,16 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// lib.mapAttrs' (
|
// lib.mapAttrs' (name: backup:
|
||||||
name: backup:
|
|
||||||
lib.nameValuePair "backup-${name}" {
|
lib.nameValuePair "backup-${name}" {
|
||||||
# Don't want to restart mid-backup
|
# Don't want to restart mid-backup
|
||||||
restartIfChanged = false;
|
restartIfChanged = false;
|
||||||
|
|
||||||
environment = resticEnv // {
|
environment =
|
||||||
RESTIC_CACHE_DIR = "%C/backup-${name}";
|
resticEnv
|
||||||
};
|
// {
|
||||||
|
RESTIC_CACHE_DIR = "%C/backup-${name}";
|
||||||
|
};
|
||||||
|
|
||||||
path = with pkgs; [
|
path = with pkgs; [
|
||||||
coreutils
|
coreutils
|
||||||
|
@ -203,60 +196,47 @@ in
|
||||||
PrivateTmp = true;
|
PrivateTmp = true;
|
||||||
|
|
||||||
ExecStart = [
|
ExecStart = [
|
||||||
(lib.concatStringsSep " " (
|
(lib.concatStringsSep " " (["${pkgs.restic}/bin/restic" "backup" "--tag" name] ++ backup.paths))
|
||||||
[
|
|
||||||
"${pkgs.restic}/bin/restic"
|
|
||||||
"backup"
|
|
||||||
"--tag"
|
|
||||||
name
|
|
||||||
]
|
|
||||||
++ backup.paths
|
|
||||||
))
|
|
||||||
];
|
];
|
||||||
|
|
||||||
ExecStartPre =
|
ExecStartPre =
|
||||||
map (service: "+${mkShutdownScript service}") backup.pauseServices
|
map (service: "+${mkShutdownScript service}") backup.pauseServices
|
||||||
++ singleton (
|
++ singleton (writeScript "backup-${name}-repo-init" [] ''
|
||||||
writeScript "backup-${name}-repo-init" [ ] ''
|
restic snapshots || restic init
|
||||||
restic snapshots || restic init
|
'')
|
||||||
''
|
++ optional (backup.preparation.text != null)
|
||||||
)
|
(writeScript "backup-${name}-prepare" backup.preparation.packages backup.preparation.text);
|
||||||
++ optional (backup.preparation.text != null) (
|
|
||||||
writeScript "backup-${name}-prepare" backup.preparation.packages backup.preparation.text
|
|
||||||
);
|
|
||||||
|
|
||||||
# TODO(tlater): Add repo pruning/checking
|
# TODO(tlater): Add repo pruning/checking
|
||||||
ExecStopPost =
|
ExecStopPost =
|
||||||
map (service: "+${mkRestartScript service}") backup.pauseServices
|
map (service: "+${mkRestartScript service}") backup.pauseServices
|
||||||
++ optional (backup.cleanup.text != null) (
|
++ optional (backup.cleanup.text != null)
|
||||||
writeScript "backup-${name}-cleanup" backup.cleanup.packages backup.cleanup.text
|
(writeScript "backup-${name}-cleanup" backup.cleanup.packages backup.cleanup.text);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
})
|
||||||
) config.services.backups;
|
config.services.backups;
|
||||||
|
|
||||||
systemd.timers =
|
systemd.timers =
|
||||||
{
|
{
|
||||||
restic-prune = {
|
restic-prune = {
|
||||||
wantedBy = [ "timers.target" ];
|
wantedBy = ["timers.target"];
|
||||||
timerConfig.OnCalendar = "Thursday 03:00:00 UTC";
|
timerConfig.OnCalendar = "Thursday 03:00:00 UTC";
|
||||||
# Don't make this persistent, in case the server was offline
|
# Don't make this persistent, in case the server was offline
|
||||||
# for a while. This job cannot run at the same time as any
|
# for a while. This job cannot run at the same time as any
|
||||||
# of the backup jobs.
|
# of the backup jobs.
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// lib.mapAttrs' (
|
// lib.mapAttrs' (name: backup:
|
||||||
name: _:
|
|
||||||
lib.nameValuePair "backup-${name}" {
|
lib.nameValuePair "backup-${name}" {
|
||||||
wantedBy = [ "timers.target" ];
|
wantedBy = ["timers.target"];
|
||||||
timerConfig = {
|
timerConfig = {
|
||||||
OnCalendar = "Wednesday 02:30:00 UTC";
|
OnCalendar = "Wednesday 02:30:00 UTC";
|
||||||
RandomizedDelaySec = "1h";
|
RandomizedDelaySec = "1h";
|
||||||
FixedRandomDelay = true;
|
FixedRandomDelay = true;
|
||||||
Persistent = true;
|
Persistent = true;
|
||||||
};
|
};
|
||||||
}
|
})
|
||||||
) config.services.backups;
|
config.services.backups;
|
||||||
|
|
||||||
users = {
|
users = {
|
||||||
# This user is only used to own the ssh key, because apparently
|
# This user is only used to own the ssh key, because apparently
|
||||||
|
@ -265,7 +245,7 @@ in
|
||||||
group = "backup";
|
group = "backup";
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
};
|
};
|
||||||
groups.backup = { };
|
groups.backup = {};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
{ config, flake-inputs, ... }:
|
|
||||||
{
|
{
|
||||||
imports = [ flake-inputs.sonnenshift.nixosModules.default ];
|
config,
|
||||||
|
flake-inputs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
imports = [
|
||||||
|
flake-inputs.sonnenshift.nixosModules.default
|
||||||
|
];
|
||||||
|
|
||||||
services.batteryManager = {
|
services.batteryManager = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
battery = "3ca39300-c523-4315-b9a3-d030f85a9373";
|
||||||
|
|
||||||
emailFile = "${config.sops.secrets."battery-manager/email".path}";
|
emailFile = "${config.sops.secrets."battery-manager/email".path}";
|
||||||
passwordFile = "${config.sops.secrets."battery-manager/password".path}";
|
passwordFile = "${config.sops.secrets."battery-manager/password".path}";
|
||||||
|
|
||||||
settings = {
|
|
||||||
battery_id = "3ca39300-c523-4315-b9a3-d030f85a9373";
|
|
||||||
log_level = "DEBUG";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
254
configuration/services/conduit.nix
Normal file
254
configuration/services/conduit.nix
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit (lib.strings) concatMapStringsSep;
|
||||||
|
|
||||||
|
cfg = config.services.matrix-conduit;
|
||||||
|
domain = "matrix.${config.services.nginx.domain}";
|
||||||
|
turn-realm = "turn.${config.services.nginx.domain}";
|
||||||
|
in {
|
||||||
|
services.matrix-conduit = {
|
||||||
|
enable = true;
|
||||||
|
settings.global = {
|
||||||
|
address = "127.0.0.1";
|
||||||
|
server_name = domain;
|
||||||
|
database_backend = "rocksdb";
|
||||||
|
|
||||||
|
turn_uris = let
|
||||||
|
address = "${config.services.coturn.realm}:${toString config.services.coturn.listening-port}";
|
||||||
|
tls-address = "${config.services.coturn.realm}:${toString config.services.coturn.tls-listening-port}";
|
||||||
|
in [
|
||||||
|
"turn:${address}?transport=udp"
|
||||||
|
"turn:${address}?transport=tcp"
|
||||||
|
"turns:${tls-address}?transport=udp"
|
||||||
|
"turns:${tls-address}?transport=tcp"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.heisenbridge = let
|
||||||
|
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
|
||||||
|
registrationFile = builtins.toFile "heisenbridge-registration.yaml" (builtins.toJSON {
|
||||||
|
id = "heisenbridge";
|
||||||
|
url = "http://127.0.0.1:9898";
|
||||||
|
as_token = "@AS_TOKEN@";
|
||||||
|
hs_token = "@HS_TOKEN@";
|
||||||
|
rate_limited = false;
|
||||||
|
sender_localpart = "heisenbridge";
|
||||||
|
namespaces = {
|
||||||
|
users = [
|
||||||
|
{
|
||||||
|
regex = "@irc_.*";
|
||||||
|
exclusive = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
regex = "@heisenbridge:.*";
|
||||||
|
exclusive = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
aliases = [];
|
||||||
|
rooms = [];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
# TODO(tlater): Starting with systemd 253 it will become possible
|
||||||
|
# to do the credential setup as part of ExecStartPre/preStart
|
||||||
|
# instead.
|
||||||
|
#
|
||||||
|
# This will also make it possible to actually set caps on the
|
||||||
|
# heisenbridge process using systemd, so that we can run the
|
||||||
|
# identd process.
|
||||||
|
execScript = pkgs.writeShellScript "heisenbridge" ''
|
||||||
|
cp ${registrationFile} "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml"
|
||||||
|
chmod 600 $RUNTIME_DIRECTORY/heisenbridge-registration.yaml
|
||||||
|
${replaceSecretBin} '@AS_TOKEN@' "$CREDENTIALS_DIRECTORY/heisenbridge_as-token" "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml"
|
||||||
|
${replaceSecretBin} '@HS_TOKEN@' "$CREDENTIALS_DIRECTORY/heisenbridge_hs-token" "$RUNTIME_DIRECTORY/heisenbridge-registration.yaml"
|
||||||
|
chmod 400 $RUNTIME_DIRECTORY/heisenbridge-registration.yaml
|
||||||
|
|
||||||
|
${pkgs.heisenbridge}/bin/heisenbridge \
|
||||||
|
--config $RUNTIME_DIRECTORY/heisenbridge-registration.yaml \
|
||||||
|
--owner @tlater:matrix.tlater.net \
|
||||||
|
'http://localhost:${toString cfg.settings.global.port}'
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
description = "Matrix<->IRC bridge";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
after = ["conduit.service"];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
|
||||||
|
LoadCredential = "heisenbridge:/run/secrets/heisenbridge";
|
||||||
|
|
||||||
|
ExecStart = execScript;
|
||||||
|
|
||||||
|
DynamicUser = true;
|
||||||
|
RuntimeDirectory = "heisenbridge";
|
||||||
|
RuntimeDirectoryMode = "0700";
|
||||||
|
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
RestrictAddressFamilies = ["AF_INET AF_INET6"];
|
||||||
|
LockPersonality = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
ProcSubset = "pid";
|
||||||
|
UMask = 0077;
|
||||||
|
|
||||||
|
# For the identd port
|
||||||
|
# CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"];
|
||||||
|
# AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Pass in the TURN secret via EnvironmentFile, not supported by
|
||||||
|
# upstream module currently.
|
||||||
|
#
|
||||||
|
# See also https://gitlab.com/famedly/conduit/-/issues/314
|
||||||
|
systemd.services.conduit.serviceConfig.EnvironmentFile = config.sops.secrets."turn/env".path;
|
||||||
|
|
||||||
|
services.coturn = {
|
||||||
|
enable = true;
|
||||||
|
no-cli = true;
|
||||||
|
use-auth-secret = true;
|
||||||
|
static-auth-secret-file = config.sops.secrets."turn/secret".path;
|
||||||
|
realm = turn-realm;
|
||||||
|
relay-ips = [
|
||||||
|
"116.202.158.55"
|
||||||
|
];
|
||||||
|
|
||||||
|
# SSL config
|
||||||
|
#
|
||||||
|
# TODO(tlater): Switch to letsencrypt once google fix:
|
||||||
|
# https://github.com/vector-im/element-android/issues/1533
|
||||||
|
pkey = config.sops.secrets."turn/ssl-key".path;
|
||||||
|
cert = config.sops.secrets."turn/ssl-cert".path;
|
||||||
|
|
||||||
|
# Based on suggestions from
|
||||||
|
# https://github.com/matrix-org/synapse/blob/develop/docs/turn-howto.md
|
||||||
|
# and
|
||||||
|
# https://www.foxypossibilities.com/2018/05/19/setting-up-a-turn-sever-for-matrix-on-nixos/
|
||||||
|
no-tcp-relay = true;
|
||||||
|
secure-stun = true;
|
||||||
|
extraConfig = ''
|
||||||
|
# Deny various local IP ranges, see
|
||||||
|
# https://www.rtcsec.com/article/cve-2020-26262-bypass-of-coturns-access-control-protection/
|
||||||
|
no-multicast-peers
|
||||||
|
denied-peer-ip=0.0.0.0-0.255.255.255
|
||||||
|
denied-peer-ip=10.0.0.0-10.255.255.255
|
||||||
|
denied-peer-ip=100.64.0.0-100.127.255.255
|
||||||
|
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||||
|
denied-peer-ip=169.254.0.0-169.254.255.255
|
||||||
|
denied-peer-ip=172.16.0.0-172.31.255.255
|
||||||
|
denied-peer-ip=192.0.0.0-192.0.0.255
|
||||||
|
denied-peer-ip=192.0.2.0-192.0.2.255
|
||||||
|
denied-peer-ip=192.88.99.0-192.88.99.255
|
||||||
|
denied-peer-ip=192.168.0.0-192.168.255.255
|
||||||
|
denied-peer-ip=198.18.0.0-198.19.255.255
|
||||||
|
denied-peer-ip=198.51.100.0-198.51.100.255
|
||||||
|
denied-peer-ip=203.0.113.0-203.0.113.255
|
||||||
|
denied-peer-ip=240.0.0.0-255.255.255.255 denied-peer-ip=::1
|
||||||
|
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
|
||||||
|
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
|
||||||
|
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
|
||||||
|
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||||
|
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||||
|
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||||
|
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||||
|
|
||||||
|
# *Allow* any IP addresses that we explicitly set as relay IPs
|
||||||
|
${concatMapStringsSep "\n" (ip: "allowed-peer-ip=${ip}") config.services.coturn.relay-ips}
|
||||||
|
|
||||||
|
# Various other security settings
|
||||||
|
no-tlsv1
|
||||||
|
no-tlsv1_1
|
||||||
|
|
||||||
|
# Monitoring
|
||||||
|
prometheus
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."${domain}" = {
|
||||||
|
useACMEHost = "tlater.net";
|
||||||
|
|
||||||
|
listen = [
|
||||||
|
{
|
||||||
|
addr = "0.0.0.0";
|
||||||
|
port = 80;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
addr = "[::0]";
|
||||||
|
port = 80;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
addr = "0.0.0.0";
|
||||||
|
port = 443;
|
||||||
|
ssl = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
addr = "[::0]";
|
||||||
|
port = 443;
|
||||||
|
ssl = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
addr = "0.0.0.0";
|
||||||
|
port = 8448;
|
||||||
|
ssl = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
addr = "[::0]";
|
||||||
|
port = 8448;
|
||||||
|
ssl = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
forceSSL = true;
|
||||||
|
enableHSTS = true;
|
||||||
|
extraConfig = ''
|
||||||
|
merge_slashes off;
|
||||||
|
'';
|
||||||
|
|
||||||
|
locations = {
|
||||||
|
"/_matrix" = {
|
||||||
|
proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
|
||||||
|
# Recommended by conduit
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_buffering off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Add Element X support
|
||||||
|
# TODO(tlater): Remove when no longer required: https://github.com/vector-im/element-x-android/issues/1085
|
||||||
|
"=/.well-known/matrix/client" = {
|
||||||
|
alias = pkgs.writeText "well-known-matrix-client" (builtins.toJSON {
|
||||||
|
"m.homeserver".base_url = "https://${domain}";
|
||||||
|
"org.matrix.msc3575.proxy".url = "https://${domain}";
|
||||||
|
});
|
||||||
|
|
||||||
|
extraConfig = ''
|
||||||
|
default_type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.backups.conduit = {
|
||||||
|
user = "root";
|
||||||
|
paths = [
|
||||||
|
"/var/lib/private/matrix-conduit/"
|
||||||
|
];
|
||||||
|
# Other services store their data in conduit, so no other services
|
||||||
|
# need to be shut down currently.
|
||||||
|
pauseServices = ["conduit.service"];
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,182 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
inherit (lib.strings) concatMapStringsSep;
|
|
||||||
|
|
||||||
cfg = config.services.matrix-conduit;
|
|
||||||
domain = "matrix.${config.services.nginx.domain}";
|
|
||||||
turn-realm = "turn.${config.services.nginx.domain}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./heisenbridge.nix
|
|
||||||
./matrix-hookshot.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
services = {
|
|
||||||
matrix-conduit = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.matrix-continuwuity;
|
|
||||||
settings.global = {
|
|
||||||
address = "127.0.0.1";
|
|
||||||
server_name = domain;
|
|
||||||
new_user_displayname_suffix = "🦆";
|
|
||||||
allow_check_for_updates = true;
|
|
||||||
|
|
||||||
# Set up delegation: https://docs.conduit.rs/delegation.html#automatic-recommended
|
|
||||||
# This is primarily to make sliding sync work
|
|
||||||
well_known = {
|
|
||||||
client = "https://${domain}";
|
|
||||||
server = "${domain}:443";
|
|
||||||
};
|
|
||||||
|
|
||||||
turn_uris =
|
|
||||||
let
|
|
||||||
address = "${config.services.coturn.realm}:${toString config.services.coturn.listening-port}";
|
|
||||||
tls-address = "${config.services.coturn.realm}:${toString config.services.coturn.tls-listening-port}";
|
|
||||||
in
|
|
||||||
[
|
|
||||||
"turn:${address}?transport=udp"
|
|
||||||
"turn:${address}?transport=tcp"
|
|
||||||
"turns:${tls-address}?transport=udp"
|
|
||||||
"turns:${tls-address}?transport=tcp"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
coturn = {
|
|
||||||
enable = true;
|
|
||||||
no-cli = true;
|
|
||||||
use-auth-secret = true;
|
|
||||||
static-auth-secret-file = config.sops.secrets."turn/secret".path;
|
|
||||||
realm = turn-realm;
|
|
||||||
relay-ips = [ "116.202.158.55" ];
|
|
||||||
|
|
||||||
# SSL config
|
|
||||||
pkey = "${config.security.acme.certs."tlater.net".directory}/key.pem";
|
|
||||||
cert = "${config.security.acme.certs."tlater.net".directory}/fullchain.pem";
|
|
||||||
|
|
||||||
# Based on suggestions from
|
|
||||||
# https://github.com/matrix-org/synapse/blob/develop/docs/turn-howto.md
|
|
||||||
# and
|
|
||||||
# https://www.foxypossibilities.com/2018/05/19/setting-up-a-turn-sever-for-matrix-on-nixos/
|
|
||||||
no-tcp-relay = true;
|
|
||||||
secure-stun = true;
|
|
||||||
extraConfig = ''
|
|
||||||
# Deny various local IP ranges, see
|
|
||||||
# https://www.rtcsec.com/article/cve-2020-26262-bypass-of-coturns-access-control-protection/
|
|
||||||
no-multicast-peers
|
|
||||||
denied-peer-ip=0.0.0.0-0.255.255.255
|
|
||||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
|
||||||
denied-peer-ip=100.64.0.0-100.127.255.255
|
|
||||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
|
||||||
denied-peer-ip=169.254.0.0-169.254.255.255
|
|
||||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
|
||||||
denied-peer-ip=192.0.0.0-192.0.0.255
|
|
||||||
denied-peer-ip=192.0.2.0-192.0.2.255
|
|
||||||
denied-peer-ip=192.88.99.0-192.88.99.255
|
|
||||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
|
||||||
denied-peer-ip=198.18.0.0-198.19.255.255
|
|
||||||
denied-peer-ip=198.51.100.0-198.51.100.255
|
|
||||||
denied-peer-ip=203.0.113.0-203.0.113.255
|
|
||||||
denied-peer-ip=240.0.0.0-255.255.255.255 denied-peer-ip=::1
|
|
||||||
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
|
|
||||||
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
|
|
||||||
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
|
|
||||||
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
|
|
||||||
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
|
||||||
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
|
||||||
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
|
||||||
|
|
||||||
# *Allow* any IP addresses that we explicitly set as relay IPs
|
|
||||||
${concatMapStringsSep "\n" (ip: "allowed-peer-ip=${ip}") config.services.coturn.relay-ips}
|
|
||||||
|
|
||||||
# Various other security settings
|
|
||||||
no-tlsv1
|
|
||||||
no-tlsv1_1
|
|
||||||
|
|
||||||
# Monitoring
|
|
||||||
prometheus
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
nginx.virtualHosts."${domain}" = {
|
|
||||||
useACMEHost = "tlater.net";
|
|
||||||
|
|
||||||
listen = [
|
|
||||||
{
|
|
||||||
addr = "0.0.0.0";
|
|
||||||
port = 80;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
addr = "[::0]";
|
|
||||||
port = 80;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
addr = "0.0.0.0";
|
|
||||||
port = 443;
|
|
||||||
ssl = true;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
addr = "[::0]";
|
|
||||||
port = 443;
|
|
||||||
ssl = true;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
addr = "0.0.0.0";
|
|
||||||
port = 8448;
|
|
||||||
ssl = true;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
addr = "[::0]";
|
|
||||||
port = 8448;
|
|
||||||
ssl = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
forceSSL = true;
|
|
||||||
enableHSTS = true;
|
|
||||||
extraConfig = ''
|
|
||||||
merge_slashes off;
|
|
||||||
'';
|
|
||||||
|
|
||||||
locations = {
|
|
||||||
"/_matrix" = {
|
|
||||||
proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
|
|
||||||
# Recommended by conduit
|
|
||||||
extraConfig = ''
|
|
||||||
proxy_buffering off;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
"/.well-known/matrix" = {
|
|
||||||
proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
backups.conduit = {
|
|
||||||
user = "root";
|
|
||||||
paths = [ "/var/lib/private/matrix-conduit/" ];
|
|
||||||
# Other services store their data in conduit, so no other services
|
|
||||||
# need to be shut down currently.
|
|
||||||
pauseServices = [ "conduit.service" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.conduit.serviceConfig = {
|
|
||||||
ExecStart = lib.mkForce "${config.services.matrix-conduit.package}/bin/conduwuit";
|
|
||||||
# Pass in the TURN secret via EnvironmentFile, not supported by
|
|
||||||
# upstream module currently.
|
|
||||||
#
|
|
||||||
# See also https://gitlab.com/famedly/conduit/-/issues/314
|
|
||||||
EnvironmentFile = config.sops.secrets."turn/env".path;
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.coturn.serviceConfig.SupplementaryGroups = [
|
|
||||||
config.security.acme.certs."tlater.net".group
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
conduitCfg = config.services.matrix-conduit;
|
|
||||||
matrixLib = pkgs.callPackage ./lib.nix { };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
systemd.services.heisenbridge =
|
|
||||||
let
|
|
||||||
registration = matrixLib.writeRegistrationScript {
|
|
||||||
id = "heisenbridge";
|
|
||||||
url = "http://127.0.0.1:9898";
|
|
||||||
sender_localpart = "heisenbridge";
|
|
||||||
|
|
||||||
namespaces = {
|
|
||||||
users = [
|
|
||||||
{
|
|
||||||
regex = "@irc_.*";
|
|
||||||
exclusive = true;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
regex = "@heisenbridge:.*";
|
|
||||||
exclusive = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
aliases = [ ];
|
|
||||||
rooms = [ ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
description = "Matrix<->IRC bridge";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [ "conduit.service" ];
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "exec";
|
|
||||||
|
|
||||||
LoadCredential = "heisenbridge:/run/secrets/heisenbridge";
|
|
||||||
|
|
||||||
inherit (registration) ExecStartPre;
|
|
||||||
ExecStart = lib.concatStringsSep " " [
|
|
||||||
"${lib.getExe pkgs.heisenbridge}"
|
|
||||||
"--config \${RUNTIME_DIRECTORY}/heisenbridge-registration.yaml"
|
|
||||||
"--owner @tlater:matrix.tlater.net"
|
|
||||||
"http://localhost:${toString conduitCfg.settings.global.port}"
|
|
||||||
];
|
|
||||||
|
|
||||||
DynamicUser = true;
|
|
||||||
RuntimeDirectory = "heisenbridge";
|
|
||||||
RuntimeDirectoryMode = "0700";
|
|
||||||
|
|
||||||
RestrictNamespaces = true;
|
|
||||||
PrivateUsers = true;
|
|
||||||
ProtectHostname = true;
|
|
||||||
ProtectClock = true;
|
|
||||||
ProtectKernelTunables = true;
|
|
||||||
ProtectKernelModules = true;
|
|
||||||
ProtectKernelLogs = true;
|
|
||||||
ProtectControlGroups = true;
|
|
||||||
RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
|
|
||||||
LockPersonality = true;
|
|
||||||
RestrictRealtime = true;
|
|
||||||
ProtectProc = "invisible";
|
|
||||||
ProcSubset = "pid";
|
|
||||||
UMask = 77;
|
|
||||||
|
|
||||||
# For the identd port
|
|
||||||
# CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
|
|
||||||
# AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
writeShellScript,
|
|
||||||
formats,
|
|
||||||
replace-secret,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
replaceSecretBin = "${lib.getExe replace-secret}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# Write a script that will set up the service's registration.yaml
|
|
||||||
# with secrets from systemd credentials.
|
|
||||||
#
|
|
||||||
# The credentials should be named `${id}_as-token` and
|
|
||||||
# `${id}_hs-token`.
|
|
||||||
#
|
|
||||||
# This registration file needs to be manually added to conduit by
|
|
||||||
# messaging the admin with the yaml file.
|
|
||||||
#
|
|
||||||
# TODO(tlater): Conduwuit seems to support a CLI interface for this,
|
|
||||||
# may want to migrate to that sometime.
|
|
||||||
writeRegistrationScript =
|
|
||||||
{
|
|
||||||
id, # Must be unique among all registered appservices/bots
|
|
||||||
url, # The URL on which the service listens
|
|
||||||
sender_localpart,
|
|
||||||
rate_limited ? false,
|
|
||||||
namespaces ? {
|
|
||||||
aliases = [ ];
|
|
||||||
rooms = [ ];
|
|
||||||
users = [ ];
|
|
||||||
},
|
|
||||||
extraSettings ? { },
|
|
||||||
# The location to place the file; assumes systemd runtime dir
|
|
||||||
runtimeRegistration ? "$RUNTIME_DIRECTORY/${id}-registration.yaml",
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
registrationFile = (formats.yaml { }).generate "${id}-registration.yaml" (
|
|
||||||
{
|
|
||||||
inherit
|
|
||||||
id
|
|
||||||
url
|
|
||||||
sender_localpart
|
|
||||||
rate_limited
|
|
||||||
namespaces
|
|
||||||
;
|
|
||||||
|
|
||||||
as_token = "@AS_TOKEN@";
|
|
||||||
hs_token = "@HS_TOKEN@";
|
|
||||||
}
|
|
||||||
// extraSettings
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit runtimeRegistration;
|
|
||||||
ExecStartPre = writeShellScript "${id}-registration-setup.sh" ''
|
|
||||||
cp -f ${registrationFile} "${runtimeRegistration}"
|
|
||||||
chmod 600 "${runtimeRegistration}"
|
|
||||||
|
|
||||||
# Write actual secrets into config
|
|
||||||
${replaceSecretBin} '@AS_TOKEN@' "$CREDENTIALS_DIRECTORY/${id}_as-token" "${runtimeRegistration}"
|
|
||||||
${replaceSecretBin} '@HS_TOKEN@' "$CREDENTIALS_DIRECTORY/${id}_hs-token" "${runtimeRegistration}"
|
|
||||||
|
|
||||||
chmod 400 "${runtimeRegistration}"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
matrixLib = pkgs.callPackage ./lib.nix { };
|
|
||||||
|
|
||||||
cfg = config.services.matrix-hookshot;
|
|
||||||
conduitCfg = config.services.matrix-conduit;
|
|
||||||
|
|
||||||
domain = conduitCfg.settings.global.server_name;
|
|
||||||
|
|
||||||
registration = matrixLib.writeRegistrationScript {
|
|
||||||
id = "matrix-hookshot";
|
|
||||||
url = "http://127.0.0.1:9993";
|
|
||||||
sender_localpart = "hookshot";
|
|
||||||
|
|
||||||
namespaces = {
|
|
||||||
aliases = [ ];
|
|
||||||
rooms = [ ];
|
|
||||||
users = [
|
|
||||||
{
|
|
||||||
regex = "@${cfg.settings.generic.userIdPrefix}.*:${domain}";
|
|
||||||
exclusive = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Encryption support
|
|
||||||
# TODO(tlater): Enable when
|
|
||||||
# https://github.com/matrix-org/matrix-hookshot/issues/1060 is
|
|
||||||
# fixed
|
|
||||||
# extraSettings = {
|
|
||||||
# "de.sorunome.msc2409.push_ephemeral" = true;
|
|
||||||
# push_ephemeral = true;
|
|
||||||
# "org.matrix.msc3202" = true;
|
|
||||||
# };
|
|
||||||
|
|
||||||
runtimeRegistration = "${cfg.registrationFile}";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# users = {
|
|
||||||
# users.matrix-hookshot = {
|
|
||||||
# home = "/run/matrix-hookshot";
|
|
||||||
# group = "matrix-hookshot";
|
|
||||||
# isSystemUser = true;
|
|
||||||
# };
|
|
||||||
|
|
||||||
# groups.matrix-hookshot = { };
|
|
||||||
# };
|
|
||||||
|
|
||||||
systemd.services.matrix-hookshot = {
|
|
||||||
serviceConfig = {
|
|
||||||
Type = lib.mkForce "exec";
|
|
||||||
|
|
||||||
LoadCredential = "matrix-hookshot:/run/secrets/matrix-hookshot";
|
|
||||||
inherit (registration) ExecStartPre;
|
|
||||||
|
|
||||||
# Some library in matrix-hookshot wants a home directory
|
|
||||||
Environment = [ "HOME=/run/matrix-hookshot" ];
|
|
||||||
|
|
||||||
# User = "matrix-hookshot";
|
|
||||||
DynamicUser = true;
|
|
||||||
StateDirectory = "matrix-hookshot";
|
|
||||||
RuntimeDirectory = "matrix-hookshot";
|
|
||||||
RuntimeDirectoryMode = "0700";
|
|
||||||
|
|
||||||
RestrictNamespaces = true;
|
|
||||||
PrivateUsers = true;
|
|
||||||
ProtectHostname = true;
|
|
||||||
ProtectClock = true;
|
|
||||||
ProtectKernelTunables = true;
|
|
||||||
ProtectKernelModules = true;
|
|
||||||
ProtectKernelLogs = true;
|
|
||||||
ProtectControlGroups = true;
|
|
||||||
RestrictAddressFamilies = [
|
|
||||||
# "AF_UNIX"
|
|
||||||
"AF_INET"
|
|
||||||
"AF_INET6"
|
|
||||||
];
|
|
||||||
LockPersonality = true;
|
|
||||||
RestrictRealtime = true;
|
|
||||||
ProtectProc = "invisible";
|
|
||||||
ProcSubset = "pid";
|
|
||||||
UMask = 77;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# services.redis.servers.matrix-hookshot = {
|
|
||||||
# enable = true;
|
|
||||||
# user = "matrix-hookshot";
|
|
||||||
# };
|
|
||||||
|
|
||||||
services.matrix-hookshot = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
serviceDependencies = [ "conduit.service" ];
|
|
||||||
|
|
||||||
registrationFile = "/run/matrix-hookshot/registration.yaml";
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
bridge = {
|
|
||||||
inherit domain;
|
|
||||||
url = "http://localhost:${toString conduitCfg.settings.global.port}";
|
|
||||||
mediaUrl = conduitCfg.settings.global.well_known.client;
|
|
||||||
port = 9993;
|
|
||||||
bindAddress = "127.0.0.1";
|
|
||||||
};
|
|
||||||
|
|
||||||
bot.displayname = "Hookshot";
|
|
||||||
|
|
||||||
# cache.redisUri = "redis://${config.services.redis.servers.matrix-hookshot.unixSocket}";
|
|
||||||
|
|
||||||
generic = {
|
|
||||||
enabled = true;
|
|
||||||
outbound = false;
|
|
||||||
# Only allow webhooks from localhost for the moment
|
|
||||||
urlPrefix = "http://127.0.0.1:9000/webhook";
|
|
||||||
userIdPrefix = "_webhooks_";
|
|
||||||
allowJsTransformationFunctions = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# TODO(tlater): Enable when
|
|
||||||
# https://github.com/matrix-org/matrix-hookshot/issues/1060 is
|
|
||||||
# fixed
|
|
||||||
# encryption.storagePath = "/var/lib/matrix-hookshot/cryptostore";
|
|
||||||
|
|
||||||
permissions = [
|
|
||||||
{
|
|
||||||
actor = "matrix.tlater.net";
|
|
||||||
services = [
|
|
||||||
{
|
|
||||||
service = "*";
|
|
||||||
level = "notifications";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
actor = "@tlater:matrix.tlater.net";
|
|
||||||
services = [
|
|
||||||
{
|
|
||||||
service = "*";
|
|
||||||
level = "admin";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
listeners = [
|
|
||||||
{
|
|
||||||
port = 9000;
|
|
||||||
resources = [ "webhooks" ];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
port = 9001;
|
|
||||||
resources = [ "metrics" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
metrics.enabled = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
security.crowdsec = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
parserWhitelist = [ "10.45.249.2" ];
|
|
||||||
|
|
||||||
extraGroups = [
|
|
||||||
"systemd-journal"
|
|
||||||
"nginx"
|
|
||||||
];
|
|
||||||
|
|
||||||
acquisitions = [
|
|
||||||
{
|
|
||||||
source = "journalctl";
|
|
||||||
labels.type = "syslog";
|
|
||||||
journalctl_filter = [ "SYSLOG_IDENTIFIER=Nextcloud" ];
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
source = "journalctl";
|
|
||||||
labels.type = "syslog";
|
|
||||||
journalctl_filter = [ "SYSLOG_IDENTIFIER=sshd-session" ];
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
labels.type = "nginx";
|
|
||||||
filenames =
|
|
||||||
[ "/var/log/nginx/*.log" ]
|
|
||||||
++ lib.mapAttrsToList (
|
|
||||||
vHost: _: "/var/log/nginx/${vHost}/access.log"
|
|
||||||
) config.services.nginx.virtualHosts;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
remediationComponents.firewallBouncer = {
|
|
||||||
enable = true;
|
|
||||||
settings.prometheus = {
|
|
||||||
enabled = true;
|
|
||||||
listen_addr = "127.0.0.1";
|
|
||||||
listen_port = "60601";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Add whitelists for matrix
|
|
||||||
systemd.tmpfiles.settings."10-matrix" =
|
|
||||||
let
|
|
||||||
stateDir = config.security.crowdsec.stateDirectory;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
"${stateDir}/config/postoverflows".d = {
|
|
||||||
user = "crowdsec";
|
|
||||||
group = "crowdsec";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
|
|
||||||
"${stateDir}/config/postoverflows/s01-whitelist".d = {
|
|
||||||
user = "crowdsec";
|
|
||||||
group = "crowdsec";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
|
|
||||||
"${stateDir}/config/postoverflows/s01-whitelist/matrix-whitelist.yaml"."L+".argument =
|
|
||||||
((pkgs.formats.yaml { }).generate "crowdsec-matrix-whitelist.yaml" {
|
|
||||||
name = "tetsumaki/matrix";
|
|
||||||
description = "custom matrix whitelist";
|
|
||||||
whitelist = {
|
|
||||||
reason = "whitelist false positive for matrix";
|
|
||||||
expression = [
|
|
||||||
"evt.Overflow.Alert.Events[0].GetMeta('target_fqdn') == '${config.services.matrix-conduit.settings.global.server_name}'"
|
|
||||||
"evt.Overflow.Alert.GetScenario() in ['crowdsecurity/http-probing', 'crowdsecurity/http-crawl-non_statics']"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}).outPath;
|
|
||||||
};
|
|
||||||
}
|
|
42
configuration/services/fail2ban.nix
Normal file
42
configuration/services/fail2ban.nix
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{pkgs, ...}: {
|
||||||
|
services.fail2ban = {
|
||||||
|
enable = true;
|
||||||
|
extraPackages = [pkgs.ipset];
|
||||||
|
banaction = "iptables-ipset-proto6-allports";
|
||||||
|
bantime-increment.enable = true;
|
||||||
|
|
||||||
|
jails = {
|
||||||
|
nginx-botsearch = ''
|
||||||
|
enabled = true
|
||||||
|
logpath = /var/log/nginx/access.log
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ignoreIP = [
|
||||||
|
"127.0.0.0/8"
|
||||||
|
"10.0.0.0/8"
|
||||||
|
"172.16.0.0/12"
|
||||||
|
"192.168.0.0/16"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Allow metrics services to connect to the socket as well
|
||||||
|
users.groups.fail2ban = {};
|
||||||
|
systemd.services.fail2ban.serviceConfig = {
|
||||||
|
ExecStartPost =
|
||||||
|
"+"
|
||||||
|
+ (pkgs.writeShellScript "fail2ban-post-start" ''
|
||||||
|
while ! [ -S /var/run/fail2ban/fail2ban.sock ]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
while ! ${pkgs.netcat}/bin/nc -zU /var/run/fail2ban/fail2ban.sock; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/chown root:fail2ban /var/run/fail2ban /var/run/fail2ban/fail2ban.sock
|
||||||
|
${pkgs.coreutils}/bin/chmod 660 /var/run/fail2ban/fail2ban.sock
|
||||||
|
${pkgs.coreutils}/bin/chmod 710 /var/run/fail2ban
|
||||||
|
'');
|
||||||
|
};
|
||||||
|
}
|
|
@ -2,48 +2,42 @@
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
flake-inputs,
|
flake-inputs,
|
||||||
pkgs,
|
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
domain = "foundryvtt.${config.services.nginx.domain}";
|
domain = "foundryvtt.${config.services.nginx.domain}";
|
||||||
in
|
in {
|
||||||
{
|
imports = [flake-inputs.foundryvtt.nixosModules.foundryvtt];
|
||||||
imports = [ flake-inputs.foundryvtt.nixosModules.foundryvtt ];
|
|
||||||
|
|
||||||
services = {
|
services.foundryvtt = {
|
||||||
foundryvtt = {
|
enable = true;
|
||||||
enable = true;
|
hostName = domain;
|
||||||
hostName = domain;
|
minifyStaticFiles = true;
|
||||||
minifyStaticFiles = true;
|
proxySSL = true;
|
||||||
proxySSL = true;
|
proxyPort = 443;
|
||||||
proxyPort = 443;
|
|
||||||
package = flake-inputs.foundryvtt.packages.${pkgs.system}.foundryvtt_13;
|
|
||||||
};
|
|
||||||
|
|
||||||
nginx.virtualHosts."${domain}" =
|
|
||||||
let
|
|
||||||
inherit (config.services.foundryvtt) port;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
forceSSL = true;
|
|
||||||
useACMEHost = "tlater.net";
|
|
||||||
enableHSTS = true;
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyWebsockets = true;
|
|
||||||
proxyPass = "http://localhost:${toString port}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
backups.foundryvtt = {
|
|
||||||
user = "foundryvtt";
|
|
||||||
paths = [ config.services.foundryvtt.dataDir ];
|
|
||||||
pauseServices = [ "foundryvtt.service" ];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Want to start it manually when I need it, not have it constantly
|
# Want to start it manually when I need it, not have it constantly
|
||||||
# running
|
# running
|
||||||
systemd.services.foundryvtt.wantedBy = lib.mkForce [ ];
|
systemd.services.foundryvtt.wantedBy = lib.mkForce [];
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."${domain}" = let
|
||||||
|
inherit (config.services.foundryvtt) port;
|
||||||
|
in {
|
||||||
|
forceSSL = true;
|
||||||
|
useACMEHost = "tlater.net";
|
||||||
|
enableHSTS = true;
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
proxyWebsockets = true;
|
||||||
|
proxyPass = "http://localhost:${toString port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.backups.foundryvtt = {
|
||||||
|
user = "foundryvtt";
|
||||||
|
paths = [
|
||||||
|
config.services.foundryvtt.dataDir
|
||||||
|
];
|
||||||
|
pauseServices = ["foundryvtt.service"];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,81 +3,93 @@
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
domain = "gitea.${config.services.nginx.domain}";
|
domain = "gitea.${config.services.nginx.domain}";
|
||||||
in
|
in {
|
||||||
{
|
services.forgejo = {
|
||||||
services = {
|
enable = true;
|
||||||
forgejo = {
|
database.type = "postgres";
|
||||||
enable = true;
|
|
||||||
database.type = "postgres";
|
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
server = {
|
server = {
|
||||||
DOMAIN = domain;
|
DOMAIN = domain;
|
||||||
HTTP_ADDR = "127.0.0.1";
|
HTTP_ADDR = "127.0.0.1";
|
||||||
ROOT_URL = "https://${domain}/";
|
ROOT_URL = "https://${domain}/";
|
||||||
SSH_PORT = 2222;
|
SSH_PORT = 2222;
|
||||||
};
|
|
||||||
|
|
||||||
metrics = {
|
|
||||||
ENABLED = true;
|
|
||||||
TOKEN = "#metricstoken#";
|
|
||||||
};
|
|
||||||
service.DISABLE_REGISTRATION = true;
|
|
||||||
session.COOKIE_SECURE = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Set up SSL
|
|
||||||
nginx.virtualHosts."${domain}" =
|
|
||||||
let
|
|
||||||
httpAddress = config.services.forgejo.settings.server.HTTP_ADDR;
|
|
||||||
httpPort = config.services.forgejo.settings.server.HTTP_PORT;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
forceSSL = true;
|
|
||||||
useACMEHost = "tlater.net";
|
|
||||||
enableHSTS = true;
|
|
||||||
|
|
||||||
locations."/".proxyPass = "http://${httpAddress}:${toString httpPort}";
|
|
||||||
locations."/metrics" = {
|
|
||||||
extraConfig = ''
|
|
||||||
access_log off;
|
|
||||||
allow 127.0.0.1;
|
|
||||||
${lib.optionalString config.networking.enableIPv6 "allow ::1;"}
|
|
||||||
deny all;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
backups.forgejo = {
|
metrics = {
|
||||||
user = "forgejo";
|
ENABLED = true;
|
||||||
paths = [
|
TOKEN = "#metricstoken#";
|
||||||
"/var/lib/forgejo/forgejo-db.sql"
|
|
||||||
"/var/lib/forgejo/repositories/"
|
|
||||||
"/var/lib/forgejo/data/"
|
|
||||||
"/var/lib/forgejo/custom/"
|
|
||||||
# Conf is backed up via nix
|
|
||||||
];
|
|
||||||
preparation = {
|
|
||||||
packages = [ config.services.postgresql.package ];
|
|
||||||
text = "pg_dump ${config.services.forgejo.database.name} --file=/var/lib/forgejo/forgejo-db.sql";
|
|
||||||
};
|
};
|
||||||
cleanup = {
|
service.DISABLE_REGISTRATION = true;
|
||||||
packages = [ pkgs.coreutils ];
|
session.COOKIE_SECURE = true;
|
||||||
text = "rm /var/lib/forgejo/forgejo-db.sql";
|
|
||||||
};
|
|
||||||
pauseServices = [ "forgejo.service" ];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.forgejo.serviceConfig.ExecStartPre =
|
systemd.services.forgejo.serviceConfig.ExecStartPre = let
|
||||||
let
|
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
|
||||||
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
|
secretPath = config.sops.secrets."forgejo/metrics-token".path;
|
||||||
secretPath = config.sops.secrets."forgejo/metrics-token".path;
|
runConfig = "${config.services.forgejo.customDir}/conf/app.ini";
|
||||||
runConfig = "${config.services.forgejo.customDir}/conf/app.ini";
|
in [
|
||||||
in
|
"+${replaceSecretBin} '#metricstoken#' '${secretPath}' '${runConfig}'"
|
||||||
[ "+${replaceSecretBin} '#metricstoken#' '${secretPath}' '${runConfig}'" ];
|
];
|
||||||
|
|
||||||
|
# Set up SSL
|
||||||
|
services.nginx.virtualHosts."${domain}" = let
|
||||||
|
httpAddress = config.services.forgejo.settings.server.HTTP_ADDR;
|
||||||
|
httpPort = config.services.forgejo.settings.server.HTTP_PORT;
|
||||||
|
in {
|
||||||
|
forceSSL = true;
|
||||||
|
useACMEHost = "tlater.net";
|
||||||
|
enableHSTS = true;
|
||||||
|
|
||||||
|
locations."/".proxyPass = "http://${httpAddress}:${toString httpPort}";
|
||||||
|
locations."/metrics" = {
|
||||||
|
extraConfig = ''
|
||||||
|
access_log off;
|
||||||
|
allow 127.0.0.1;
|
||||||
|
${lib.optionalString config.networking.enableIPv6 "allow ::1;"}
|
||||||
|
deny all;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Block repeated failed login attempts
|
||||||
|
#
|
||||||
|
# TODO(tlater): Update this - we switched to forgejo, who knows what
|
||||||
|
# the new matches are.
|
||||||
|
# environment.etc = {
|
||||||
|
# "fail2ban/filter.d/gitea.conf".text = ''
|
||||||
|
# [Definition]
|
||||||
|
# failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
|
||||||
|
# journalmatch = _SYSTEMD_UNIT=forgejo.service + _COMM=forgejo + SYSLOG_IDENTIFIER=forgejo
|
||||||
|
# '';
|
||||||
|
# };
|
||||||
|
|
||||||
|
# services.fail2ban.jails = {
|
||||||
|
# gitea = ''
|
||||||
|
# enabled = true
|
||||||
|
# '';
|
||||||
|
# };
|
||||||
|
|
||||||
|
services.backups.forgejo = {
|
||||||
|
user = "forgejo";
|
||||||
|
paths = [
|
||||||
|
"/var/lib/forgejo/forgejo-db.sql"
|
||||||
|
"/var/lib/forgejo/repositories/"
|
||||||
|
"/var/lib/forgejo/data/"
|
||||||
|
"/var/lib/forgejo/custom/"
|
||||||
|
# Conf is backed up via nix
|
||||||
|
];
|
||||||
|
preparation = {
|
||||||
|
packages = [config.services.postgresql.package];
|
||||||
|
text = "pg_dump ${config.services.forgejo.database.name} --file=/var/lib/forgejo/forgejo-db.sql";
|
||||||
|
};
|
||||||
|
cleanup = {
|
||||||
|
packages = [pkgs.coreutils];
|
||||||
|
text = "rm /var/lib/forgejo/forgejo-db.sql";
|
||||||
|
};
|
||||||
|
pauseServices = ["forgejo.service"];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
hostName = "immich.${config.services.nginx.domain}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services = {
|
|
||||||
immich = {
|
|
||||||
enable = true;
|
|
||||||
settings.server.externalDomain = "https://${hostName}";
|
|
||||||
|
|
||||||
environment.IMMICH_TELEMETRY_INCLUDE = "all";
|
|
||||||
};
|
|
||||||
|
|
||||||
nginx.virtualHosts.${hostName} =
|
|
||||||
let
|
|
||||||
local = "http://${config.services.immich.host}:${toString config.services.immich.port}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
forceSSL = true;
|
|
||||||
useACMEHost = "tlater.net";
|
|
||||||
enableHSTS = true;
|
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = local;
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
locations."/metrics" = {
|
|
||||||
extraConfig = ''
|
|
||||||
access_log off;
|
|
||||||
allow 127.0.0.1;
|
|
||||||
${lib.optionalString config.networking.enableIPv6 "allow ::1;"}
|
|
||||||
deny all;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
backups.immich =
|
|
||||||
let
|
|
||||||
db-dump = "${config.services.immich.mediaLocation}/immich-db.sql";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
user = "immich";
|
|
||||||
paths = [ config.services.immich.mediaLocation ];
|
|
||||||
|
|
||||||
preparation = {
|
|
||||||
packages = [ config.services.postgresql.package ];
|
|
||||||
text = ''
|
|
||||||
pg_dump ${config.services.immich.database.name} --clean --if-exists --file=${db-dump}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
cleanup = {
|
|
||||||
packages = [ pkgs.coreutils ];
|
|
||||||
text = "rm ${db-dump}";
|
|
||||||
};
|
|
||||||
pauseServices = [
|
|
||||||
"immich-server.service"
|
|
||||||
"immich-machine-learning.service"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -5,6 +5,5 @@
|
||||||
./exporters.nix
|
./exporters.nix
|
||||||
./grafana.nix
|
./grafana.nix
|
||||||
./victoriametrics.nix
|
./victoriametrics.nix
|
||||||
./victorialogs.nix
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,49 +3,23 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
let
|
yaml = pkgs.formats.yaml {};
|
||||||
yaml = pkgs.formats.yaml { };
|
in {
|
||||||
in
|
|
||||||
{
|
|
||||||
services.prometheus = {
|
services.prometheus = {
|
||||||
exporters = {
|
exporters = {
|
||||||
blackbox = {
|
|
||||||
enable = true;
|
|
||||||
listenAddress = "127.0.0.1";
|
|
||||||
configFile = yaml.generate "blackbox.yaml" {
|
|
||||||
modules = {
|
|
||||||
http_2xx = {
|
|
||||||
prober = "http";
|
|
||||||
timeout = "5s";
|
|
||||||
http.preferred_ip_protocol = "ip4";
|
|
||||||
};
|
|
||||||
|
|
||||||
turn_server = {
|
|
||||||
prober = "tcp";
|
|
||||||
timeout = "5s";
|
|
||||||
tcp = {
|
|
||||||
preferred_ip_protocol = "ip4";
|
|
||||||
source_ip_address = "116.202.158.55";
|
|
||||||
tls = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Periodically check domain registration status
|
# Periodically check domain registration status
|
||||||
domain = {
|
domain = {
|
||||||
enable = true;
|
enable = true;
|
||||||
listenAddress = "127.0.0.1";
|
listenAddress = "127.0.0.1";
|
||||||
extraFlags =
|
extraFlags = let
|
||||||
let
|
conf.domains = [
|
||||||
conf.domains = [
|
"tlater.net"
|
||||||
"tlater.net"
|
"tlater.com"
|
||||||
"tlater.com"
|
];
|
||||||
];
|
in [
|
||||||
in
|
"--config=${yaml.generate "domains.yml" conf}"
|
||||||
[ "--config=${yaml.generate "domains.yml" conf}" ];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# System statistics
|
# System statistics
|
||||||
|
@ -74,29 +48,54 @@ in
|
||||||
listenAddress = "127.0.0.1";
|
listenAddress = "127.0.0.1";
|
||||||
group = "nginx";
|
group = "nginx";
|
||||||
|
|
||||||
settings.namespaces = lib.mapAttrsToList (name: _: {
|
settings.namespaces =
|
||||||
inherit name;
|
lib.mapAttrsToList (name: virtualHost: {
|
||||||
metrics_override.prefix = "nginxlog";
|
inherit name;
|
||||||
namespace_label = "vhost";
|
metrics_override.prefix = "nginxlog";
|
||||||
|
namespace_label = "vhost";
|
||||||
|
|
||||||
format = lib.concatStringsSep " " [
|
format = lib.concatStringsSep " " [
|
||||||
"$remote_addr - $remote_user [$time_local]"
|
"$remote_addr - $remote_user [$time_local]"
|
||||||
''"$request" $status $body_bytes_sent''
|
''"$request" $status $body_bytes_sent''
|
||||||
''"$http_referer" "$http_user_agent"''
|
''"$http_referer" "$http_user_agent"''
|
||||||
''rt=$request_time uct="$upstream_connect_time"''
|
''rt=$request_time uct="$upstream_connect_time"''
|
||||||
''uht="$upstream_header_time" urt="$upstream_response_time"''
|
''uht="$upstream_header_time" urt="$upstream_response_time"''
|
||||||
];
|
];
|
||||||
|
|
||||||
source.files = [ "/var/log/nginx/${name}/access.log" ];
|
source.files = [
|
||||||
}) config.services.nginx.virtualHosts;
|
"/var/log/nginx/${name}/access.log"
|
||||||
|
];
|
||||||
|
})
|
||||||
|
config.services.nginx.virtualHosts;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
extraExporters = {
|
||||||
|
fail2ban = let
|
||||||
|
cfg = config.services.prometheus.extraExporters.fail2ban;
|
||||||
|
in {
|
||||||
|
port = 9191;
|
||||||
|
serviceOpts = {
|
||||||
|
after = ["fail2ban.service"];
|
||||||
|
requires = ["fail2ban.service"];
|
||||||
|
serviceConfig = {
|
||||||
|
Group = "fail2ban";
|
||||||
|
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
|
||||||
|
ExecStart = lib.concatStringsSep " " [
|
||||||
|
"${pkgs.local.prometheus-fail2ban-exporter}/bin/fail2ban-prometheus-exporter"
|
||||||
|
"--collector.f2b.socket=/var/run/fail2ban/fail2ban.sock"
|
||||||
|
"--web.listen-address='${cfg.listenAddress}:${toString cfg.port}'"
|
||||||
|
"--collector.f2b.exit-on-socket-connection-error=true"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO(tlater):
|
# TODO(tlater):
|
||||||
# - wireguard (?)
|
# - wireguard (?)
|
||||||
# - postgres (?)
|
# - postgres (?)
|
||||||
|
# - blackbox (?) (curl to see if http and similar is up)
|
||||||
# - ssl_exporter (?)
|
# - ssl_exporter (?)
|
||||||
};
|
};
|
||||||
|
|
||||||
services.dbus.implementation = "broker";
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
{ pkgs, config, ... }:
|
{config, ...}: let
|
||||||
let
|
|
||||||
domain = "metrics.${config.services.nginx.domain}";
|
domain = "metrics.${config.services.nginx.domain}";
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
services.grafana = {
|
services.grafana = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings = {
|
settings = {
|
||||||
server = {
|
server.http_port = 3001; # Default overlaps with gitea
|
||||||
http_port = 3001; # Default overlaps with gitea
|
|
||||||
root_url = "https://metrics.tlater.net";
|
|
||||||
};
|
|
||||||
|
|
||||||
security = {
|
security = {
|
||||||
admin_user = "tlater";
|
admin_user = "tlater";
|
||||||
|
@ -28,11 +23,6 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
declarativePlugins = [
|
|
||||||
pkgs.grafanaPlugins.victoriametrics-metrics-datasource
|
|
||||||
pkgs.grafanaPlugins.victoriametrics-logs-datasource
|
|
||||||
];
|
|
||||||
|
|
||||||
provision = {
|
provision = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
|
@ -40,16 +30,7 @@ in
|
||||||
{
|
{
|
||||||
name = "Victoriametrics - tlater.net";
|
name = "Victoriametrics - tlater.net";
|
||||||
url = "http://localhost:8428";
|
url = "http://localhost:8428";
|
||||||
type = "victoriametrics-metrics-datasource";
|
type = "prometheus";
|
||||||
access = "proxy";
|
|
||||||
isDefault = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
name = "Victorialogs - tlater.net";
|
|
||||||
url = "http://${config.services.victorialogs.bindAddress}";
|
|
||||||
type = "victoriametrics-logs-datasource";
|
|
||||||
access = "proxy";
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@ -59,12 +40,7 @@ in
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
useACMEHost = "tlater.net";
|
useACMEHost = "tlater.net";
|
||||||
enableHSTS = true;
|
enableHSTS = true;
|
||||||
locations = {
|
enableAuthorization = true;
|
||||||
"/".proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
|
locations."/".proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
|
||||||
"/api/live" = {
|
|
||||||
proxyWebsockets = true;
|
|
||||||
proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,235 +3,202 @@
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
inherit (lib) types mkOption mkDefault;
|
inherit (lib) types mkOption mkDefault;
|
||||||
yaml = pkgs.formats.yaml { };
|
yaml = pkgs.formats.yaml {};
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
options = {
|
options = {
|
||||||
services.prometheus = {
|
services.prometheus = {
|
||||||
extraExporters = mkOption {
|
extraExporters = mkOption {
|
||||||
default = { };
|
type = types.attrsOf (types.submodule {
|
||||||
type = types.attrsOf (
|
options = {
|
||||||
types.submodule {
|
port = mkOption {
|
||||||
options = {
|
type = types.int;
|
||||||
port = mkOption {
|
description = "The port on which this exporter listens.";
|
||||||
type = types.int;
|
|
||||||
description = "The port on which this exporter listens.";
|
|
||||||
};
|
|
||||||
listenAddress = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "127.0.0.1";
|
|
||||||
description = "Address to listen on.";
|
|
||||||
};
|
|
||||||
serviceOpts = mkOption {
|
|
||||||
type = types.attrs;
|
|
||||||
description = "An attrset to be merged with the exporter's systemd service.";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
listenAddress = mkOption {
|
||||||
);
|
type = types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "Address to listen on.";
|
||||||
|
};
|
||||||
|
serviceOpts = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
description = "An attrset to be merged with the exporter's systemd service.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.victoriametrics.scrapeConfigs = mkOption {
|
services.victoriametrics.scrapeConfigs = mkOption {
|
||||||
type = types.attrsOf (
|
type = types.attrsOf (types.submodule ({
|
||||||
types.submodule (
|
name,
|
||||||
{ name, ... }:
|
self,
|
||||||
{
|
...
|
||||||
options = {
|
}: {
|
||||||
job_name = mkOption {
|
options = {
|
||||||
type = types.str;
|
job_name = mkOption {
|
||||||
default = name;
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
|
|
||||||
|
extraSettings = mkOption {
|
||||||
|
type = types.anything;
|
||||||
|
description = ''
|
||||||
|
Other settings to set for this scrape config.
|
||||||
|
'';
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
targets = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Addresses scrape targets for this config listen on.
|
||||||
|
|
||||||
|
Shortcut for `static_configs = lib.singleton {targets = [<targets>];}`
|
||||||
|
'';
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
static_configs = mkOption {
|
||||||
|
default = [];
|
||||||
|
type = types.listOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
targets = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
The addresses scrape targets for this config listen on.
|
||||||
|
|
||||||
|
Must in `listenAddress:port` format.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
labels = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Labels to apply to all targets defined for this static config.
|
||||||
|
'';
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
});
|
||||||
extraSettings = mkOption {
|
};
|
||||||
inherit (pkgs.formats.yaml { }) type;
|
};
|
||||||
description = ''
|
}));
|
||||||
Other settings to set for this scrape config.
|
|
||||||
'';
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
|
|
||||||
targets = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
Addresses scrape targets for this config listen on.
|
|
||||||
|
|
||||||
Shortcut for `static_configs = lib.singleton {targets = [<targets>];}`
|
|
||||||
'';
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_configs = mkOption {
|
|
||||||
default = [ ];
|
|
||||||
type = types.listOf (
|
|
||||||
types.submodule {
|
|
||||||
options = {
|
|
||||||
targets = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
The addresses scrape targets for this config listen on.
|
|
||||||
|
|
||||||
Must in `listenAddress:port` format.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
labels = mkOption {
|
|
||||||
type = types.attrsOf types.str;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
Labels to apply to all targets defined for this static config.
|
|
||||||
'';
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
systemd.services = lib.mkMerge [
|
systemd.services = lib.mkMerge [
|
||||||
(lib.mapAttrs' (
|
(lib.mapAttrs' (name: exporter:
|
||||||
name: exporter:
|
lib.nameValuePair "prometheus-${name}-exporter" (lib.mkMerge [
|
||||||
lib.nameValuePair "prometheus-${name}-exporter" (
|
{
|
||||||
lib.mkMerge [
|
# Shamelessly copied from upstream because the upstream
|
||||||
{
|
# module is an intractable mess
|
||||||
# Shamelessly copied from upstream because the upstream
|
wantedBy = ["multi-user.target"];
|
||||||
# module is an intractable mess
|
after = ["network.target"];
|
||||||
wantedBy = [ "multi-user.target" ];
|
serviceConfig.Restart = mkDefault "always";
|
||||||
after = [ "network.target" ];
|
serviceConfig.PrivateTmp = mkDefault true;
|
||||||
serviceConfig = {
|
serviceConfig.WorkingDirectory = mkDefault /tmp;
|
||||||
Restart = mkDefault "always";
|
serviceConfig.DynamicUser = mkDefault true;
|
||||||
PrivateTmp = mkDefault true;
|
# Hardening
|
||||||
WorkingDirectory = mkDefault /tmp;
|
serviceConfig.CapabilityBoundingSet = mkDefault [""];
|
||||||
DynamicUser = mkDefault true;
|
serviceConfig.DeviceAllow = [""];
|
||||||
# Hardening
|
serviceConfig.LockPersonality = true;
|
||||||
CapabilityBoundingSet = mkDefault [ "" ];
|
serviceConfig.MemoryDenyWriteExecute = true;
|
||||||
DeviceAllow = [ "" ];
|
serviceConfig.NoNewPrivileges = true;
|
||||||
LockPersonality = true;
|
serviceConfig.PrivateDevices = mkDefault true;
|
||||||
MemoryDenyWriteExecute = true;
|
serviceConfig.ProtectClock = mkDefault true;
|
||||||
NoNewPrivileges = true;
|
serviceConfig.ProtectControlGroups = true;
|
||||||
PrivateDevices = mkDefault true;
|
serviceConfig.ProtectHome = true;
|
||||||
ProtectClock = mkDefault true;
|
serviceConfig.ProtectHostname = true;
|
||||||
ProtectControlGroups = true;
|
serviceConfig.ProtectKernelLogs = true;
|
||||||
ProtectHome = true;
|
serviceConfig.ProtectKernelModules = true;
|
||||||
ProtectHostname = true;
|
serviceConfig.ProtectKernelTunables = true;
|
||||||
ProtectKernelLogs = true;
|
serviceConfig.ProtectSystem = mkDefault "strict";
|
||||||
ProtectKernelModules = true;
|
serviceConfig.RemoveIPC = true;
|
||||||
ProtectKernelTunables = true;
|
serviceConfig.RestrictAddressFamilies = ["AF_INET" "AF_INET6"];
|
||||||
ProtectSystem = mkDefault "strict";
|
serviceConfig.RestrictNamespaces = true;
|
||||||
RemoveIPC = true;
|
serviceConfig.RestrictRealtime = true;
|
||||||
RestrictAddressFamilies = [
|
serviceConfig.RestrictSUIDSGID = true;
|
||||||
"AF_INET"
|
serviceConfig.SystemCallArchitectures = "native";
|
||||||
"AF_INET6"
|
serviceConfig.UMask = "0077";
|
||||||
];
|
}
|
||||||
RestrictNamespaces = true;
|
exporter.serviceOpts
|
||||||
RestrictRealtime = true;
|
]))
|
||||||
RestrictSUIDSGID = true;
|
config.services.prometheus.extraExporters)
|
||||||
SystemCallArchitectures = "native";
|
|
||||||
UMask = "0077";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
exporter.serviceOpts
|
|
||||||
]
|
|
||||||
)
|
|
||||||
) config.services.prometheus.extraExporters)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
vmagent-scrape-exporters =
|
vmagent-scrape-exporters = let
|
||||||
let
|
listenAddress = config.services.victoriametrics.listenAddress;
|
||||||
inherit (config.services.victoriametrics) listenAddress;
|
vmAddr = (lib.optionalString (lib.hasPrefix ":" listenAddress) "127.0.0.1") + listenAddress;
|
||||||
vmAddr = (lib.optionalString (lib.hasPrefix ":" listenAddress) "127.0.0.1") + listenAddress;
|
promscrape = yaml.generate "prometheus.yml" {
|
||||||
promscrape = yaml.generate "prometheus.yml" {
|
scrape_configs = lib.mapAttrsToList (_: scrape:
|
||||||
scrape_configs = lib.mapAttrsToList (
|
lib.recursiveUpdate {
|
||||||
_: scrape:
|
inherit (scrape) job_name;
|
||||||
lib.recursiveUpdate {
|
static_configs =
|
||||||
inherit (scrape) job_name;
|
scrape.static_configs
|
||||||
static_configs =
|
++ lib.optional (scrape.targets != []) {targets = scrape.targets;};
|
||||||
scrape.static_configs
|
}
|
||||||
++ lib.optional (scrape.targets != [ ]) { inherit (scrape) targets; };
|
scrape.extraSettings)
|
||||||
} scrape.extraSettings
|
config.services.victoriametrics.scrapeConfigs;
|
||||||
) config.services.victoriametrics.scrapeConfigs;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
enable = true;
|
|
||||||
path = [ pkgs.victoriametrics ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
after = [
|
|
||||||
"network.target"
|
|
||||||
"victoriametrics.service"
|
|
||||||
];
|
|
||||||
serviceConfig = {
|
|
||||||
ExecStart = [
|
|
||||||
(lib.concatStringsSep " " [
|
|
||||||
"${pkgs.victoriametrics}/bin/vmagent"
|
|
||||||
"-promscrape.config=${promscrape}"
|
|
||||||
"-remoteWrite.url=http://${vmAddr}/api/v1/write"
|
|
||||||
"-remoteWrite.tmpDataPath=%t/vmagent"
|
|
||||||
])
|
|
||||||
];
|
|
||||||
SupplementaryGroups = "metrics";
|
|
||||||
|
|
||||||
DynamicUser = true;
|
|
||||||
RuntimeDirectory = "vmagent";
|
|
||||||
CapabilityBoundingSet = [ "" ];
|
|
||||||
DeviceAllow = [ "" ];
|
|
||||||
LockPersonality = true;
|
|
||||||
MemoryDenyWriteExecute = true;
|
|
||||||
NoNewPrivileges = true;
|
|
||||||
PrivateDevices = true;
|
|
||||||
ProtectClock = true;
|
|
||||||
ProtectControlGroups = true;
|
|
||||||
ProtectHome = true;
|
|
||||||
ProtectHostname = true;
|
|
||||||
ProtectKernelLogs = true;
|
|
||||||
ProtectKernelModules = true;
|
|
||||||
ProtectKernelTunables = true;
|
|
||||||
ProtectSystem = "strict";
|
|
||||||
RemoveIPC = true;
|
|
||||||
RestrictAddressFamilies = [
|
|
||||||
"AF_INET"
|
|
||||||
"AF_INET6"
|
|
||||||
];
|
|
||||||
RestrictNamespaces = true;
|
|
||||||
RestrictRealtime = true;
|
|
||||||
RestrictSUIDSGID = true;
|
|
||||||
SystemCallArchitectures = "native";
|
|
||||||
UMask = "0077";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
in {
|
||||||
|
enable = true;
|
||||||
|
path = [pkgs.victoriametrics];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
after = ["network.target" "victoriametrics.service"];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = [
|
||||||
|
(lib.concatStringsSep " " [
|
||||||
|
"${pkgs.victoriametrics}/bin/vmagent"
|
||||||
|
"-promscrape.config=${promscrape}"
|
||||||
|
"-remoteWrite.url=http://${vmAddr}/api/v1/write"
|
||||||
|
"-remoteWrite.tmpDataPath=%t/vmagent"
|
||||||
|
])
|
||||||
|
];
|
||||||
|
SupplementaryGroups = "metrics";
|
||||||
|
|
||||||
|
DynamicUser = true;
|
||||||
|
RuntimeDirectory = "vmagent";
|
||||||
|
CapabilityBoundingSet = [""];
|
||||||
|
DeviceAllow = [""];
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
RemoveIPC = true;
|
||||||
|
RestrictAddressFamilies = ["AF_INET" "AF_INET6"];
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
users.groups.metrics = { };
|
users.groups.metrics = {};
|
||||||
|
|
||||||
services.victoriametrics.scrapeConfigs =
|
services.victoriametrics.scrapeConfigs = let
|
||||||
let
|
allExporters =
|
||||||
allExporters = lib.mapAttrs (_: exporter: { inherit (exporter) listenAddress port; }) (
|
lib.mapAttrs (name: exporter: {
|
||||||
(lib.filterAttrs (
|
inherit (exporter) listenAddress port;
|
||||||
name: exporter:
|
}) ((lib.filterAttrs (_: exporter: builtins.isAttrs exporter && exporter.enable)
|
||||||
# A bunch of deprecated exporters that need to be ignored
|
config.services.prometheus.exporters)
|
||||||
!(builtins.elem name [
|
// config.services.prometheus.extraExporters);
|
||||||
"blackbox"
|
in
|
||||||
"minio"
|
|
||||||
"tor"
|
|
||||||
"unifi-poller"
|
|
||||||
])
|
|
||||||
&& builtins.isAttrs exporter
|
|
||||||
&& exporter.enable
|
|
||||||
) config.services.prometheus.exporters)
|
|
||||||
// config.services.prometheus.extraExporters
|
|
||||||
);
|
|
||||||
in
|
|
||||||
lib.mapAttrs (_: exporter: {
|
lib.mapAttrs (_: exporter: {
|
||||||
targets = [ "${exporter.listenAddress}:${toString exporter.port}" ];
|
targets = ["${exporter.listenAddress}:${toString exporter.port}"];
|
||||||
}) allExporters;
|
})
|
||||||
|
allExporters;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
{ config, lib, ... }:
|
|
||||||
let
|
|
||||||
cfg = config.services.victorialogs;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.services.victorialogs.bindAddress = lib.mkOption {
|
|
||||||
readOnly = true;
|
|
||||||
type = lib.types.str;
|
|
||||||
description = ''
|
|
||||||
Final address on which victorialogs listens.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
services.victorialogs = {
|
|
||||||
enable = true;
|
|
||||||
bindAddress =
|
|
||||||
(lib.optionalString (lib.hasPrefix ":" cfg.listenAddress) "127.0.0.1") + cfg.listenAddress;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.journald.upload = {
|
|
||||||
enable = true;
|
|
||||||
settings.Upload = {
|
|
||||||
URL = "http://${cfg.bindAddress}/insert/journald";
|
|
||||||
NetworkTimeoutSec = "20s";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
systemd.services."systemd-journal-upload".after = [ "victorialogs.service" ];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,99 +1,16 @@
|
||||||
{ config, lib, ... }:
|
{config, ...}: {
|
||||||
let
|
|
||||||
blackbox_host = config.services.prometheus.exporters.blackbox.listenAddress;
|
|
||||||
blackbox_port = config.services.prometheus.exporters.blackbox.port;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config.services.victoriametrics = {
|
config.services.victoriametrics = {
|
||||||
enable = true;
|
enable = true;
|
||||||
extraOptions = [ "-storage.minFreeDiskSpaceBytes=5GB" ];
|
extraOptions = [
|
||||||
|
"-storage.minFreeDiskSpaceBytes=5GB"
|
||||||
|
];
|
||||||
|
|
||||||
scrapeConfigs = {
|
scrapeConfigs = {
|
||||||
forgejo = {
|
forgejo = {
|
||||||
targets = [ "127.0.0.1:${toString config.services.forgejo.settings.server.HTTP_PORT}" ];
|
targets = ["127.0.0.1:${toString config.services.forgejo.settings.server.HTTP_PORT}"];
|
||||||
extraSettings.authorization.credentials_file = config.sops.secrets."forgejo/metrics-token".path;
|
extraSettings.authorization.credentials_file = config.sops.secrets."forgejo/metrics-token".path;
|
||||||
};
|
};
|
||||||
|
coturn.targets = ["127.0.0.1:9641"];
|
||||||
blackbox = {
|
|
||||||
static_configs = lib.singleton {
|
|
||||||
targets = lib.mapAttrsToList (vHost: _: "https://${vHost}") config.services.nginx.virtualHosts;
|
|
||||||
};
|
|
||||||
|
|
||||||
extraSettings = {
|
|
||||||
metrics_path = "/probe";
|
|
||||||
params.module = [ "http_2xx" ];
|
|
||||||
|
|
||||||
relabel_configs = [
|
|
||||||
{
|
|
||||||
source_labels = [ "__address__" ];
|
|
||||||
target_label = "__param_target";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
source_labels = [ "__param_target" ];
|
|
||||||
target_label = "instance";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
target_label = "__address__";
|
|
||||||
replacement = "${blackbox_host}:${toString blackbox_port}";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
blackbox_turn = {
|
|
||||||
targets = [ "turn.tlater.net:${toString config.services.coturn.tls-listening-port}" ];
|
|
||||||
|
|
||||||
extraSettings = {
|
|
||||||
metrics_path = "/probe";
|
|
||||||
params.module = [ "turn_server" ];
|
|
||||||
|
|
||||||
relabel_configs = [
|
|
||||||
{
|
|
||||||
source_labels = [ "__address__" ];
|
|
||||||
target_label = "__param_target";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
source_labels = [ "__param_target" ];
|
|
||||||
target_label = "instance";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
target_label = "__address__";
|
|
||||||
replacement = "${blackbox_host}:${toString blackbox_port}";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
blackbox_exporter.targets = [ "${blackbox_host}:${toString blackbox_port}" ];
|
|
||||||
|
|
||||||
coturn.targets = [ "127.0.0.1:9641" ];
|
|
||||||
|
|
||||||
crowdsec.targets =
|
|
||||||
let
|
|
||||||
address = config.security.crowdsec.settings.prometheus.listen_addr;
|
|
||||||
port = config.security.crowdsec.settings.prometheus.listen_port;
|
|
||||||
in
|
|
||||||
[ "${address}:${toString port}" ];
|
|
||||||
|
|
||||||
csFirewallBouncer.targets =
|
|
||||||
let
|
|
||||||
address =
|
|
||||||
config.security.crowdsec.remediationComponents.firewallBouncer.settings.prometheus.listen_addr;
|
|
||||||
port =
|
|
||||||
config.security.crowdsec.remediationComponents.firewallBouncer.settings.prometheus.listen_port;
|
|
||||||
in
|
|
||||||
[ "${address}:${toString port}" ];
|
|
||||||
|
|
||||||
immich.targets = [
|
|
||||||
"127.0.0.1:8081"
|
|
||||||
"127.0.0.1:8082"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Configured in the hookshot listeners, but it's hard to filter
|
|
||||||
# the correct values out of that config.
|
|
||||||
matrixHookshot.targets = [ "127.0.0.1:9001" ];
|
|
||||||
|
|
||||||
victorialogs.targets = [ config.services.victorialogs.bindAddress ];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,103 +1,105 @@
|
||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
config,
|
config,
|
||||||
lib,
|
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
let
|
# Update pending on rewrite of nextcloud news, though there is an
|
||||||
nextcloud = pkgs.nextcloud31;
|
# alpha to switch to if it becomes necessary:
|
||||||
|
# https://github.com/nextcloud/news/issues/2610
|
||||||
|
nextcloud = pkgs.nextcloud27;
|
||||||
hostName = "nextcloud.${config.services.nginx.domain}";
|
hostName = "nextcloud.${config.services.nginx.domain}";
|
||||||
in
|
in {
|
||||||
{
|
services.nextcloud = {
|
||||||
services = {
|
inherit hostName;
|
||||||
nextcloud = {
|
|
||||||
inherit hostName;
|
|
||||||
|
|
||||||
package = nextcloud;
|
package = nextcloud;
|
||||||
phpPackage = lib.mkForce (
|
enable = true;
|
||||||
pkgs.php.override {
|
maxUploadSize = "2G";
|
||||||
packageOverrides = _: prev: {
|
https = true;
|
||||||
extensions = prev.extensions // {
|
|
||||||
pgsql = prev.extensions.pgsql.overrideAttrs (_: {
|
|
||||||
configureFlags = [ "--with-pgsql=${lib.getDev config.services.postgresql.package.pg_config}" ];
|
|
||||||
});
|
|
||||||
pdo_pgsql = prev.extensions.pdo_pgsql.overrideAttrs (_: {
|
|
||||||
configureFlags = [ "--with-pdo-pgsql=${lib.getDev config.services.postgresql.package.pg_config}" ];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
enable = true;
|
|
||||||
maxUploadSize = "2G";
|
|
||||||
https = true;
|
|
||||||
|
|
||||||
configureRedis = true;
|
configureRedis = true;
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
dbtype = "pgsql";
|
dbtype = "pgsql";
|
||||||
dbhost = "/run/postgresql";
|
dbhost = "/run/postgresql";
|
||||||
|
|
||||||
adminuser = "tlater";
|
adminuser = "tlater";
|
||||||
adminpassFile = config.sops.secrets."nextcloud/tlater".path;
|
adminpassFile = config.sops.secrets."nextcloud/tlater".path;
|
||||||
};
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
default_phone_region = "AT";
|
|
||||||
overwriteprotocol = "https";
|
|
||||||
};
|
|
||||||
|
|
||||||
phpOptions = {
|
|
||||||
"opcache.interned_strings_buffer" = "16";
|
|
||||||
};
|
|
||||||
|
|
||||||
extraApps = {
|
|
||||||
inherit (config.services.nextcloud.package.packages.apps)
|
|
||||||
calendar
|
|
||||||
contacts
|
|
||||||
cookbook
|
|
||||||
news
|
|
||||||
;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Set up SSL
|
settings = {
|
||||||
nginx.virtualHosts."${hostName}" = {
|
default_phone_region = "AT";
|
||||||
forceSSL = true;
|
overwriteprotocol = "https";
|
||||||
useACMEHost = "tlater.net";
|
|
||||||
# The upstream module already adds HSTS
|
|
||||||
};
|
};
|
||||||
|
|
||||||
backups.nextcloud = {
|
phpOptions = {
|
||||||
user = "nextcloud";
|
"opcache.interned_strings_buffer" = "16";
|
||||||
paths = [
|
};
|
||||||
"/var/lib/nextcloud/nextcloud-db.sql"
|
|
||||||
"/var/lib/nextcloud/data/"
|
extraApps = {
|
||||||
"/var/lib/nextcloud/config/config.php"
|
inherit (pkgs.local) bookmarks calendar contacts cookbook news notes;
|
||||||
];
|
|
||||||
preparation = {
|
|
||||||
packages = [
|
|
||||||
config.services.postgresql.package
|
|
||||||
config.services.nextcloud.occ
|
|
||||||
];
|
|
||||||
text = ''
|
|
||||||
nextcloud-occ maintenance:mode --on
|
|
||||||
pg_dump ${config.services.nextcloud.config.dbname} --file=/var/lib/nextcloud/nextcloud-db.sql
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
cleanup = {
|
|
||||||
packages = [
|
|
||||||
pkgs.coreutils
|
|
||||||
config.services.nextcloud.occ
|
|
||||||
];
|
|
||||||
text = ''
|
|
||||||
rm /var/lib/nextcloud/nextcloud-db.sql
|
|
||||||
nextcloud-occ maintenance:mode --off
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Ensure that this service doesn't start before postgres is ready
|
# Ensure that this service doesn't start before postgres is ready
|
||||||
systemd.services.nextcloud-setup.after = [ "postgresql.service" ];
|
systemd.services.nextcloud-setup.after = ["postgresql.service"];
|
||||||
|
|
||||||
|
# Set up SSL
|
||||||
|
services.nginx.virtualHosts."${hostName}" = {
|
||||||
|
forceSSL = true;
|
||||||
|
useACMEHost = "tlater.net";
|
||||||
|
# The upstream module already adds HSTS
|
||||||
|
};
|
||||||
|
|
||||||
|
# Block repeated failed login attempts
|
||||||
|
environment.etc = {
|
||||||
|
"fail2ban/filter.d/nextcloud.conf".text = ''
|
||||||
|
[Definition]
|
||||||
|
_groupsre = (?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)
|
||||||
|
failregex = \{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Login failed:
|
||||||
|
\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Trusted domain error.
|
||||||
|
datepattern = ,?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?"
|
||||||
|
journalmatch = SYSLOG_IDENTIFIER=Nextcloud
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.fail2ban.jails = {
|
||||||
|
nextcloud = ''
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
# Nextcloud does some throttling already, so we need to set
|
||||||
|
# these to something bigger.
|
||||||
|
findtime = 43200
|
||||||
|
bantime = 86400
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.backups.nextcloud = {
|
||||||
|
user = "nextcloud";
|
||||||
|
paths = [
|
||||||
|
"/var/lib/nextcloud/nextcloud-db.sql"
|
||||||
|
"/var/lib/nextcloud/data/"
|
||||||
|
"/var/lib/nextcloud/config/config.php"
|
||||||
|
];
|
||||||
|
preparation = {
|
||||||
|
packages = [
|
||||||
|
config.services.postgresql.package
|
||||||
|
config.services.nextcloud.occ
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
nextcloud-occ maintenance:mode --on
|
||||||
|
pg_dump ${config.services.nextcloud.config.dbname} --file=/var/lib/nextcloud/nextcloud-db.sql
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
cleanup = {
|
||||||
|
packages = [
|
||||||
|
pkgs.coreutils
|
||||||
|
config.services.nextcloud.occ
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
rm /var/lib/nextcloud/nextcloud-db.sql
|
||||||
|
nextcloud-occ maintenance:mode --off
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
services.postgresql = {
|
services.postgresql = {
|
||||||
package = pkgs.postgresql_14;
|
package = pkgs.postgresql_14;
|
||||||
enable = true;
|
enable = true;
|
||||||
|
@ -25,11 +28,16 @@
|
||||||
name = "nextcloud";
|
name = "nextcloud";
|
||||||
ensureDBOwnership = true;
|
ensureDBOwnership = true;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
name = config.services.authelia.instances.main.user;
|
||||||
|
ensureDBOwnership = true;
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
ensureDatabases = [
|
ensureDatabases = [
|
||||||
"grafana"
|
"grafana"
|
||||||
"nextcloud"
|
"nextcloud"
|
||||||
|
config.services.authelia.instances.main.user
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
{ pkgs, lib, ... }:
|
|
||||||
let
|
|
||||||
inherit (lib) concatStringsSep;
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit (lib) concatStringsSep;
|
||||||
|
in {
|
||||||
# Sadly, steam-run requires some X libs
|
# Sadly, steam-run requires some X libs
|
||||||
environment.noXlibs = false;
|
environment.noXlibs = false;
|
||||||
|
|
||||||
systemd.services.starbound = {
|
systemd.services.starbound = {
|
||||||
description = "Starbound";
|
description = "Starbound";
|
||||||
after = [ "network.target" ];
|
after = ["network.target"];
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${pkgs.local.starbound}/bin/launch-starbound ${./configs/starbound.json}";
|
ExecStart = "${pkgs.local.starbound}/bin/launch-starbound ${./configs/starbound.json}";
|
||||||
|
@ -65,7 +67,7 @@ in
|
||||||
# Game servers shouldn't use cgroups themselves either
|
# Game servers shouldn't use cgroups themselves either
|
||||||
ProtectControlGroups = true;
|
ProtectControlGroups = true;
|
||||||
# Most game servers will never need other socket types
|
# Most game servers will never need other socket types
|
||||||
RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
|
RestrictAddressFamilies = ["AF_UNIX AF_INET AF_INET6"];
|
||||||
# Also a no-brainer, no game server should ever need this
|
# Also a no-brainer, no game server should ever need this
|
||||||
LockPersonality = true;
|
LockPersonality = true;
|
||||||
# Some game servers will probably try to set this, but they
|
# Some game servers will probably try to set this, but they
|
||||||
|
@ -111,7 +113,9 @@ in
|
||||||
|
|
||||||
services.backups.starbound = {
|
services.backups.starbound = {
|
||||||
user = "root";
|
user = "root";
|
||||||
paths = [ "/var/lib/private/starbound/storage/universe/" ];
|
paths = [
|
||||||
pauseServices = [ "starbound.service" ];
|
"/var/lib/private/starbound/storage/universe/"
|
||||||
|
];
|
||||||
|
pauseServices = ["starbound.service"];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
{ config, ... }:
|
{config, ...}: let
|
||||||
let
|
domain = config.services.nginx.domain;
|
||||||
inherit (config.services.nginx) domain;
|
in {
|
||||||
in
|
|
||||||
{
|
|
||||||
services.tlaternet-webserver = {
|
services.tlaternet-webserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
listen = {
|
listen = {
|
||||||
|
@ -12,17 +10,15 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
# Set up SSL
|
# Set up SSL
|
||||||
services.nginx.virtualHosts."${domain}" =
|
services.nginx.virtualHosts."${domain}" = let
|
||||||
let
|
inherit (config.services.tlaternet-webserver.listen) addr port;
|
||||||
inherit (config.services.tlaternet-webserver.listen) addr port;
|
in {
|
||||||
in
|
serverAliases = ["www.${domain}"];
|
||||||
{
|
|
||||||
serverAliases = [ "www.${domain}" ];
|
|
||||||
|
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
useACMEHost = "tlater.net";
|
useACMEHost = "tlater.net";
|
||||||
enableHSTS = true;
|
enableHSTS = true;
|
||||||
|
|
||||||
locations."/".proxyPass = "http://${addr}:${toString port}";
|
locations."/".proxyPass = "http://${addr}:${toString port}";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
{ config, ... }:
|
{config, ...}: {
|
||||||
{
|
|
||||||
# iptables needs to permit forwarding from wg0 to wg0
|
# iptables needs to permit forwarding from wg0 to wg0
|
||||||
networking.firewall.extraCommands = ''
|
networking.firewall.extraCommands = ''
|
||||||
iptables -A FORWARD -i wg0 -o wg0 -j ACCEPT
|
iptables -A FORWARD -i wg0 -o wg0 -j ACCEPT
|
||||||
|
@ -24,10 +23,20 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
wireguardPeers = [
|
wireguardPeers = [
|
||||||
# yui
|
|
||||||
{
|
{
|
||||||
AllowedIPs = [ "10.45.249.2/32" ];
|
# yui
|
||||||
PublicKey = "5mlnqEVJWks5OqgeFA2bLIrvST9TlCE81Btl+j4myz0=";
|
wireguardPeerConfig = {
|
||||||
|
AllowedIPs = ["10.45.249.2/32"];
|
||||||
|
PublicKey = "5mlnqEVJWks5OqgeFA2bLIrvST9TlCE81Btl+j4myz0=";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
# yuanyuan
|
||||||
|
wireguardPeerConfig = {
|
||||||
|
AllowedIPs = ["10.45.249.10/32"];
|
||||||
|
PublicKey = "0UsFE2atz/O5P3OKQ8UHyyyGQNJbp1MeIWUJLuoerwE=";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@ -38,23 +47,23 @@
|
||||||
matchConfig.Name = "wg0";
|
matchConfig.Name = "wg0";
|
||||||
|
|
||||||
networkConfig = {
|
networkConfig = {
|
||||||
Description = "VLAN";
|
|
||||||
|
|
||||||
Address = [
|
Address = [
|
||||||
"10.45.249.1/32"
|
"10.45.249.1/32"
|
||||||
# TODO(tlater): Add IPv6 whenever that becomes relevant
|
# TODO(tlater): Add IPv6 whenever that becomes relevant
|
||||||
];
|
];
|
||||||
|
|
||||||
IPv4Forwarding = "yes";
|
IPForward = "yes";
|
||||||
IPv4ProxyARP = "yes";
|
IPv4ProxyARP = "yes";
|
||||||
};
|
};
|
||||||
|
|
||||||
routes = [
|
routes = [
|
||||||
{
|
{
|
||||||
Source = "10.45.249.0/24";
|
routeConfig = {
|
||||||
Destination = "10.45.249.0/24";
|
Source = "10.45.249.0/24";
|
||||||
Gateway = "10.45.249.1";
|
Destination = "10.45.249.0/24";
|
||||||
GatewayOnLink = "no";
|
Gateway = "10.45.249.1";
|
||||||
|
GatewayOnLink = "no";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,15 @@
|
||||||
defaultSopsFile = ../keys/production.yaml;
|
defaultSopsFile = ../keys/production.yaml;
|
||||||
|
|
||||||
secrets = {
|
secrets = {
|
||||||
"battery-manager/email" = { };
|
"battery-manager/email" = {
|
||||||
|
owner = "battery-manager";
|
||||||
|
group = "battery-manager";
|
||||||
|
};
|
||||||
|
|
||||||
"battery-manager/password" = { };
|
"battery-manager/password" = {
|
||||||
|
owner = "battery-manager";
|
||||||
|
group = "battery-manager";
|
||||||
|
};
|
||||||
|
|
||||||
# Gitea
|
# Gitea
|
||||||
"forgejo/metrics-token" = {
|
"forgejo/metrics-token" = {
|
||||||
|
@ -25,12 +31,12 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Heisenbridge
|
# Heisenbridge
|
||||||
"heisenbridge/as-token" = { };
|
"heisenbridge/as-token" = {};
|
||||||
"heisenbridge/hs-token" = { };
|
"heisenbridge/hs-token" = {};
|
||||||
|
|
||||||
# Matrix-hookshot
|
"hetzner-api" = {
|
||||||
"matrix-hookshot/as-token" = { };
|
owner = "acme";
|
||||||
"matrix-hookshot/hs-token" = { };
|
};
|
||||||
|
|
||||||
# Nextcloud
|
# Nextcloud
|
||||||
"nextcloud/tlater" = {
|
"nextcloud/tlater" = {
|
||||||
|
@ -38,14 +44,6 @@
|
||||||
group = "nextcloud";
|
group = "nextcloud";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Porkbub/ACME
|
|
||||||
"porkbun/api-key" = {
|
|
||||||
owner = "acme";
|
|
||||||
};
|
|
||||||
"porkbun/secret-api-key" = {
|
|
||||||
owner = "acme";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Restic
|
# Restic
|
||||||
"restic/local-backups" = {
|
"restic/local-backups" = {
|
||||||
owner = "root";
|
owner = "root";
|
||||||
|
@ -64,10 +62,10 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Steam
|
# Steam
|
||||||
"steam/tlater" = { };
|
"steam/tlater" = {};
|
||||||
|
|
||||||
# Turn
|
# Turn
|
||||||
"turn/env" = { };
|
"turn/env" = {};
|
||||||
"turn/secret" = {
|
"turn/secret" = {
|
||||||
owner = "turnserver";
|
owner = "turnserver";
|
||||||
};
|
};
|
||||||
|
|
960
flake.lock
generated
960
flake.lock
generated
File diff suppressed because it is too large
Load diff
252
flake.nix
252
flake.nix
|
@ -2,7 +2,8 @@
|
||||||
description = "tlater.net host configuration";
|
description = "tlater.net host configuration";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05-small";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
|
||||||
|
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
disko = {
|
disko = {
|
||||||
url = "github:nix-community/disko";
|
url = "github:nix-community/disko";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
@ -12,6 +13,10 @@
|
||||||
url = "github:Mic92/sops-nix";
|
url = "github:Mic92/sops-nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
nvfetcher = {
|
||||||
|
url = "github:berberman/nvfetcher";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
tlaternet-webserver = {
|
tlaternet-webserver = {
|
||||||
url = "git+https://gitea.tlater.net/tlaternet/tlaternet.git";
|
url = "git+https://gitea.tlater.net/tlaternet/tlaternet.git";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
@ -22,146 +27,131 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
sonnenshift = {
|
sonnenshift = {
|
||||||
url = "git+ssh://git@github.com/sonnenshift/battery-manager";
|
url = "git+ssh://git@github.com/sonnenshift/battery-manager?ref=tlater/implement-nix-module";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs = {
|
||||||
{
|
self,
|
||||||
self,
|
nixpkgs,
|
||||||
nixpkgs,
|
sops-nix,
|
||||||
sops-nix,
|
nvfetcher,
|
||||||
deploy-rs,
|
deploy-rs,
|
||||||
...
|
...
|
||||||
}@inputs:
|
} @ inputs: let
|
||||||
let
|
system = "x86_64-linux";
|
||||||
system = "x86_64-linux";
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
in {
|
||||||
|
##################
|
||||||
vm = nixpkgs.lib.nixosSystem {
|
# Configurations #
|
||||||
|
##################
|
||||||
|
nixosConfigurations = {
|
||||||
|
# The actual system definition
|
||||||
|
hetzner-1 = nixpkgs.lib.nixosSystem {
|
||||||
inherit system;
|
inherit system;
|
||||||
specialArgs.flake-inputs = inputs;
|
specialArgs.flake-inputs = inputs;
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
./configuration
|
./configuration
|
||||||
./configuration/hardware-specific/vm.nix
|
./configuration/hardware-specific/hetzner
|
||||||
];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
##################
|
|
||||||
# Configurations #
|
|
||||||
##################
|
|
||||||
nixosConfigurations = {
|
|
||||||
# The actual system definition
|
|
||||||
hetzner-1 = nixpkgs.lib.nixosSystem {
|
|
||||||
inherit system;
|
|
||||||
specialArgs.flake-inputs = inputs;
|
|
||||||
|
|
||||||
modules = [
|
|
||||||
./configuration
|
|
||||||
./configuration/hardware-specific/hetzner
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
############################
|
|
||||||
# Deployment configuration #
|
|
||||||
############################
|
|
||||||
deploy.nodes = {
|
|
||||||
hetzner-1 = {
|
|
||||||
hostname = "116.202.158.55";
|
|
||||||
|
|
||||||
profiles.system = {
|
|
||||||
user = "root";
|
|
||||||
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.hetzner-1;
|
|
||||||
};
|
|
||||||
|
|
||||||
sshUser = "tlater";
|
|
||||||
sshOpts = [
|
|
||||||
"-p"
|
|
||||||
"2222"
|
|
||||||
"-o"
|
|
||||||
"ForwardAgent=yes"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
#########
|
|
||||||
# Tests #
|
|
||||||
#########
|
|
||||||
checks.${system} = import ./checks (inputs // { inherit system; });
|
|
||||||
|
|
||||||
###########################
|
|
||||||
# Garbage collection root #
|
|
||||||
###########################
|
|
||||||
|
|
||||||
packages.${system} =
|
|
||||||
let
|
|
||||||
localPkgs = import ./pkgs { inherit pkgs; };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
default = vm.config.system.build.vm;
|
|
||||||
crowdsec-hub = localPkgs.crowdsec.hub;
|
|
||||||
crowdsec-firewall-bouncer = localPkgs.crowdsec.firewall-bouncer;
|
|
||||||
};
|
|
||||||
|
|
||||||
###################
|
|
||||||
# Utility scripts #
|
|
||||||
###################
|
|
||||||
apps.${system} = {
|
|
||||||
default = self.apps.${system}.run-vm;
|
|
||||||
|
|
||||||
run-vm = {
|
|
||||||
type = "app";
|
|
||||||
program =
|
|
||||||
(pkgs.writeShellScript "" ''
|
|
||||||
${vm.config.system.build.vm.outPath}/bin/run-testvm-vm
|
|
||||||
'').outPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
update-crowdsec-packages =
|
|
||||||
let
|
|
||||||
git = pkgs.lib.getExe pkgs.git;
|
|
||||||
nvfetcher = pkgs.lib.getExe pkgs.nvfetcher;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
type = "app";
|
|
||||||
program =
|
|
||||||
(pkgs.writeShellScript "update-crowdsec-packages" ''
|
|
||||||
cd "$(${git} rev-parse --show-toplevel)"
|
|
||||||
cd ./pkgs/crowdsec
|
|
||||||
${nvfetcher}
|
|
||||||
echo 'Remember to update the vendorHash of any go packages!'
|
|
||||||
'').outPath;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
###########################
|
|
||||||
# Development environment #
|
|
||||||
###########################
|
|
||||||
devShells.${system}.default = nixpkgs.legacyPackages.${system}.mkShell {
|
|
||||||
sopsPGPKeyDirs = [
|
|
||||||
"./keys/hosts/"
|
|
||||||
"./keys/users/"
|
|
||||||
];
|
|
||||||
nativeBuildInputs = [ sops-nix.packages.${system}.sops-import-keys-hook ];
|
|
||||||
|
|
||||||
packages = with pkgs; [
|
|
||||||
sops-nix.packages.${system}.sops-init-gpg-key
|
|
||||||
deploy-rs.packages.${system}.default
|
|
||||||
|
|
||||||
nixpkgs-fmt
|
|
||||||
|
|
||||||
cargo
|
|
||||||
clippy
|
|
||||||
rustc
|
|
||||||
rustfmt
|
|
||||||
rust-analyzer
|
|
||||||
pkg-config
|
|
||||||
openssl
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Deployment configuration #
|
||||||
|
############################
|
||||||
|
deploy.nodes = {
|
||||||
|
hetzner-1 = {
|
||||||
|
hostname = "116.202.158.55";
|
||||||
|
|
||||||
|
profiles.system = {
|
||||||
|
user = "root";
|
||||||
|
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.hetzner-1;
|
||||||
|
};
|
||||||
|
|
||||||
|
sshUser = "tlater";
|
||||||
|
sshOpts = ["-p" "2222" "-o" "ForwardAgent=yes"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#########
|
||||||
|
# Tests #
|
||||||
|
#########
|
||||||
|
checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib;
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Utility scripts #
|
||||||
|
###################
|
||||||
|
apps.${system} = {
|
||||||
|
default = self.apps.${system}.run-vm;
|
||||||
|
|
||||||
|
run-vm = {
|
||||||
|
type = "app";
|
||||||
|
program = let
|
||||||
|
vm = nixpkgs.lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
specialArgs.flake-inputs = inputs;
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
./configuration
|
||||||
|
./configuration/hardware-specific/vm.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
(pkgs.writeShellScript "" ''
|
||||||
|
${vm.config.system.build.vm.outPath}/bin/run-testvm-vm
|
||||||
|
'')
|
||||||
|
.outPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
update-pkgs = {
|
||||||
|
type = "app";
|
||||||
|
program = let
|
||||||
|
nvfetcher-bin = "${nvfetcher.packages.${system}.default}/bin/nvfetcher";
|
||||||
|
in
|
||||||
|
(pkgs.writeShellScript "update-pkgs" ''
|
||||||
|
cd "$(git rev-parse --show-toplevel)/pkgs"
|
||||||
|
${nvfetcher-bin} -o _sources_pkgs -c nvfetcher.toml
|
||||||
|
'')
|
||||||
|
.outPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
update-nextcloud-apps = {
|
||||||
|
type = "app";
|
||||||
|
program = let
|
||||||
|
nvfetcher-bin = "${nvfetcher.packages.${system}.default}/bin/nvfetcher";
|
||||||
|
in
|
||||||
|
(pkgs.writeShellScript "update-nextcloud-apps" ''
|
||||||
|
cd "$(git rev-parse --show-toplevel)/pkgs"
|
||||||
|
${nvfetcher-bin} -o _sources_nextcloud -c nextcloud-apps.toml
|
||||||
|
'')
|
||||||
|
.outPath;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# Development environment #
|
||||||
|
###########################
|
||||||
|
devShells.${system}.default = nixpkgs.legacyPackages.${system}.mkShell {
|
||||||
|
sopsPGPKeyDirs = ["./keys/hosts/" "./keys/users/"];
|
||||||
|
nativeBuildInputs = [
|
||||||
|
sops-nix.packages.${system}.sops-import-keys-hook
|
||||||
|
];
|
||||||
|
|
||||||
|
packages = with pkgs; [
|
||||||
|
sops-nix.packages.${system}.sops-init-gpg-key
|
||||||
|
deploy-rs.packages.${system}.default
|
||||||
|
|
||||||
|
cargo
|
||||||
|
clippy
|
||||||
|
rustc
|
||||||
|
rustfmt
|
||||||
|
rust-analyzer
|
||||||
|
pkg-config
|
||||||
|
openssl
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
porkbun:
|
authelia:
|
||||||
api-key: ENC[AES256_GCM,data:p3lqvGc8m2U/12rBPjoNR7hxQyD52CyEen/V8q59k5CSJZSqzZS8M5vEXFBsUMjz2lrmKM4pgtz4wa2fWK6Ty4LJCaI=,iv:OQC3FpwTtPmqHvDbA41mWF7LGYwC/jD2ZMBsE8ktNOg=,tag:kq5hUR7TBgczuGcXpsdu2A==,type:str]
|
storageEncryptionKey: ENC[AES256_GCM,data:OUCC+6Gcr6U7Mub1+DaIyswTV6da1wd1u0WGEm4wpJ8L0mi7WSpEmVjH79YyRhp7AmiZhdFFDXFeEYthBb2AZl+xoS9gqs6rWyfU4ezaCbXBiS/dIhsA5foPg13wq5A33qJWtPTy7DJEgqHaIonnaBuVJIBwH3wzPTHc3bDvBo4=,iv:intiZzngz5cMTtjEI9rTKMW0Xv3KB3ZEgtYN3amwKCE=,tag:AKxfbeZlPs54esHCsVnNCg==,type:str]
|
||||||
secret-api-key: ENC[AES256_GCM,data:zV5PTKf45Zab8uW8mbuXmPNzciq6tV9OF0wUND7YnRk/DjZneYWItAsNBVoM+iHA+XsUPDoeKo6hoJiGkH/cCQ8WvuM=,iv:yr1M5DlgI8k6BgzNz3HRnqspHOrQuf2PmoZS1HGp0v8=,tag:JkNNziMMfKFZV2hnx5lXRg==,type:str]
|
sessionSecret: ENC[AES256_GCM,data:GEMWhBltOIOs0g9FsWk3OQGs6dMcbwz3ZuhlyBFYROylsIZb4xTXWLgNwIpHwQukQU3TgvIxbCW/fGRWiALPanE2koSVAHNx0UU0hj1mVNRFQGK4H3EL10tPp7l4PofrcdeCbLPrOwM/xLOuPt+52sKlcbL2Awz5/MmpUVpCKXc=,iv:kWX2ptOpTgW3obBgri0MvVv6gCEPR3o77sldOXFQeks=,tag:je4pqLcEOhuBTQkoZHYNCw==,type:str]
|
||||||
battery-manager:
|
battery-manager:
|
||||||
email: ENC[AES256_GCM,data:rYLUACXR/n+bLBmZ,iv:sUBEkh2+7qGjHZ5R23e/hoCiyTA7GTL4bJvXmxjZ5Sw=,tag:fdPMllaQQfRgX0WZKIre4g==,type:str]
|
email: ENC[AES256_GCM,data:rYLUACXR/n+bLBmZ,iv:sUBEkh2+7qGjHZ5R23e/hoCiyTA7GTL4bJvXmxjZ5Sw=,tag:fdPMllaQQfRgX0WZKIre4g==,type:str]
|
||||||
password: ENC[AES256_GCM,data:7cokZa6Q6ahSeiFPz+cV,iv:vz405P0IcG9FsAQXlY7mi78GuushQUKJm2irG6buGzc=,tag:JLHG2jTkJDGbinAq9dXRsQ==,type:str]
|
password: ENC[AES256_GCM,data:7cokZa6Q6ahSeiFPz+cV,iv:vz405P0IcG9FsAQXlY7mi78GuushQUKJm2irG6buGzc=,tag:JLHG2jTkJDGbinAq9dXRsQ==,type:str]
|
||||||
|
@ -16,9 +16,6 @@ steam:
|
||||||
heisenbridge:
|
heisenbridge:
|
||||||
as-token: ENC[AES256_GCM,data:+2yo6T18j34622H8ZWblAFB2phLw1q0k0vUQEZ5sFj7dQaRnkEiAMi0R3p17Zq0pOtGEC0RRZuPLYkcZ1oKP0w==,iv:lGwrQYp//FufpmJocrLIVyy9RK7lEEVcpAi0wmkjr34=,tag:yV06UbhAYJQz36O2XdhY+A==,type:str]
|
as-token: ENC[AES256_GCM,data:+2yo6T18j34622H8ZWblAFB2phLw1q0k0vUQEZ5sFj7dQaRnkEiAMi0R3p17Zq0pOtGEC0RRZuPLYkcZ1oKP0w==,iv:lGwrQYp//FufpmJocrLIVyy9RK7lEEVcpAi0wmkjr34=,tag:yV06UbhAYJQz36O2XdhY+A==,type:str]
|
||||||
hs-token: ENC[AES256_GCM,data:u52WpkQFd/J7JFoE/rfNluebyZQLOokvkVdL7+AEAvrhJhrkJli1ztkD79lbC+6tGUH4tT3T+nX9wvGKnrRUQg==,iv:as+9fVuvMg2IoE2WIKD9mHi+znhNcWRh5Zq+yr0xcDQ=,tag:mZ7fh7U0MfgI8hyq/28Bcg==,type:str]
|
hs-token: ENC[AES256_GCM,data:u52WpkQFd/J7JFoE/rfNluebyZQLOokvkVdL7+AEAvrhJhrkJli1ztkD79lbC+6tGUH4tT3T+nX9wvGKnrRUQg==,iv:as+9fVuvMg2IoE2WIKD9mHi+znhNcWRh5Zq+yr0xcDQ=,tag:mZ7fh7U0MfgI8hyq/28Bcg==,type:str]
|
||||||
matrix-hookshot:
|
|
||||||
as-token: ENC[AES256_GCM,data:nXTanPhDyDF7R3AllLqpM5dzljBrHwlh1KJnTGIi5PhbDY2lPj4+uXkMEwvm1u+hQjPyM7vKZPfK+0/dms6Y7A==,iv:fSakJN+yai0gfOJKFxxaxgyUtk0pNmIeqVgrdq92/24=,tag:Qc7+SUnm5/Nq5+QIScR9kQ==,type:str]
|
|
||||||
hs-token: ENC[AES256_GCM,data:Bwyj0JTTN0NNnwOs1zA8CqbtZSNcvlINeT7QVc2eJiHda92J6vQk7bSxy6KuqCN9DxlUsK13ggYjNORY2vic5w==,iv:Npnp8arYQ3Yb6CXrnKgE03hD7ZjGINPa/DwFI8D+5tA=,tag:FqNE6yI0nF4puEUw9MGAjQ==,type:str]
|
|
||||||
wireguard:
|
wireguard:
|
||||||
server-key: ENC[AES256_GCM,data:mXb7ZznJHf5CgV8rI4uzPBATMRbmd7LimgtCkQM9kAjbIaGwUBqJZBN3fXs=,iv:3Po1Orinzov9rnEm9cLzgJY1PeD+5Jl9115MriABHh8=,tag:E/2CjDO1JCvJzxCnqKcNyw==,type:str]
|
server-key: ENC[AES256_GCM,data:mXb7ZznJHf5CgV8rI4uzPBATMRbmd7LimgtCkQM9kAjbIaGwUBqJZBN3fXs=,iv:3Po1Orinzov9rnEm9cLzgJY1PeD+5Jl9115MriABHh8=,tag:E/2CjDO1JCvJzxCnqKcNyw==,type:str]
|
||||||
restic:
|
restic:
|
||||||
|
@ -37,63 +34,63 @@ sops:
|
||||||
azure_kv: []
|
azure_kv: []
|
||||||
hc_vault: []
|
hc_vault: []
|
||||||
age: []
|
age: []
|
||||||
lastmodified: "2025-02-07T19:44:49Z"
|
lastmodified: "2024-04-11T23:38:56Z"
|
||||||
mac: ENC[AES256_GCM,data:+0hpd/E7GxK/27f2Itf0hDV+3Ga4gHb8xxLutJ32HLBWLZ5Y+dN03xgkz8jBTiM+BeHwS4gz70Cs9X3zLMHbosWVuIV9DLuRaHRq/IU9KiADwqmCySZALqCf3+T5QKZr3Qs4AZJHwaAXkRX9HbnRFriIAFDJW/BGdIHdoROquxY=,iv:TeXI8LGqHVa5wo61sGdNbZ2nJvSlPdgn9R3Lq5qUggU=,tag:TFort5wxVTdi9LMlMeT/DQ==,type:str]
|
mac: ENC[AES256_GCM,data:GjIB0EbWsh4o+QoFSyIXgGYnNhRlvfSmue1LyTt6oUlIjNgODhdIB8px8LnRo0rmm/f1YHbDq2MFOxlgdm3PTNaqm/MoKyW3r/wuAeWADsYayQszLNxyhTMXcjWtfm6zCRIuc/+YyM44pXRfVrOZRAin9B6pmJZsRJwBAZpogbU=,iv:r/ZQZvrP0E9dOW5fhBH2I21Z0uv2e3njdEGmadxEALg=,tag:iZvbGTvRJFo80n8aoKSSmQ==,type:str]
|
||||||
pgp:
|
pgp:
|
||||||
- created_at: "2025-01-21T17:55:44Z"
|
- created_at: "2024-03-18T04:02:00Z"
|
||||||
enc: |-
|
enc: |-
|
||||||
-----BEGIN PGP MESSAGE-----
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
hQEMA7x7stsXx45CAQf6AjhhYCHhRvJx4xxiXPMTfpIpAvseB1CXVfuhfj2r2yrf
|
hQEMA7x7stsXx45CAQf7BjF+HR3WKdMyAV6R1M0+lqDz6hBHKyGH7YBB/QZBqRbK
|
||||||
3HfJnNOSDBcmHdp9fiLJattqfsykcGisUDVIplCeA7cIJjH3sf8MuIJXDTLmvE0Z
|
3hdABIwWUsqpHjleEOp/Gj0VhZqwagqHxK4Fp5G0r3QBupbAO8u/+DNI8wll0Nva
|
||||||
BmV8LuwP/UPQSZzY3w1eTAoYT/by35gfJm4ofipft2qyIjQuBgOlrg/0swvBl7LK
|
dlOh0Jqp4E17TkERMQL02rrQ1ZmpOYmPkCd2//xkmWAQ1LatHWeRVSRxQBuMtPQi
|
||||||
47mI6mVdds8RutHw/xhJZeNjKF/5tADPJ2CHjOHbCQhLji13Kpm3yObOnM8K2SfV
|
btrefcQNjQCvS9/60dp8oTu8nxlFA4iHCBQKNIKVGqQH7jkdIfMPdUILjCkCiyCc
|
||||||
P9uVudCFN/ZBiTlVkB7PsuitwZx1fW0SR3jcWxbRd17M2k2RAQQDUCqPKaoJ3T3f
|
h+OxlHZZnpU6U9A+hjMBinvCzebSkZh48VX/T33Kr+4b0CBr1gR9MSXKG9f2MPQP
|
||||||
r2ExwmyO4j7G2vkFv1RgQnhAoHqqRZ1nSjNw1+27MNJeAbT1ddia2TC5Q2zjRZY9
|
PMl6rPvqSqG6ddN9QDI+0HEHYaRvxPIV8uDS36tVxNJeAQHB5/6Lt7hJdYWgwf5E
|
||||||
tRJi2pNZH9A424lpIBLnkPl5rpCR/UZ+bqhaQ9C2kFMldSldPn8dMiBy8XG0Trji
|
TLgbZ0IxB17++6K++GlaG8WHO65l1jzmkPlN+ZGcwnhibDxnZjP6kqGqDFcZP4ge
|
||||||
B6X44Q/0RCsJD3FS61GASIjaWdEX0DdSOdhtbtBLLQ==
|
cnV0KnhYcC59IooQYrWKzAJex9rnwPo7MGKV6XwZOQ==
|
||||||
=yLOx
|
=Hy9T
|
||||||
-----END PGP MESSAGE-----
|
-----END PGP MESSAGE-----
|
||||||
fp: B9C7AE554602D82B1AC2CFD0BC7BB2DB17C78E42!
|
fp: 535B61015823443941C744DD12264F6BBDFABA89
|
||||||
- created_at: "2025-01-21T17:55:44Z"
|
- created_at: "2024-03-18T04:02:00Z"
|
||||||
enc: |-
|
enc: |-
|
||||||
-----BEGIN PGP MESSAGE-----
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
hQIMA9ahl2ynTH87AQ/9F/lyLXn60X5Slcd9I7VPdM6x4IjFI0UwMAhMVv0GeimD
|
hQIMA9ahl2ynTH87AQ/+LNXxC3acjs2+c38gHZRW6Am4XFx1t/4tfxIgaaK/Boq8
|
||||||
9Mj1vKW+l1v0gFtqCGjANEEYpXMr109CES+m6z7dNXcIQeR6pyjW6uCTiPUK/ZO/
|
PGU5CFNOMDGv8u/cwyDbfNM7GuL5g7vrLmBXzSV5ErZqc6bJ0+ZCNPTRIxP1Vxph
|
||||||
ZQyhJR0zsT15BTHc6c9Eso9YedsggpRAbjjVIlmrTJx52LfqYYGoRA0XqcLE6LUS
|
tWiDIyTwuqUzxWpOlSzii2Sqhlp8CyiWzBe95eIr96XzDCCtfzyCZ0BYyKgpHHxB
|
||||||
6v6KR/299zzSHsTFVon6wac9wY7i1XvL0Q7JiYSNV2cNADiCJdCIBw1UkaSVH1uz
|
BltH0/+0JZFiR19zvf6M99AHwM8OddRQkXav+mRIJQpA87ovVZcAv5skYGJgNCqN
|
||||||
sTVdoEoQ1m7hu9pgg4UksUzi9IV3xBpte82tlEK9p0M+RXK/02fjMTWcx3q5fnjN
|
55fbskuYmqEnloQCZVJ2+2ZXK5Qn/uq4fLJCiIdZm4YsctJnV5spzZIL+dcOty65
|
||||||
pBu3HkVzLpAjAQHMQ/O2cgbjFmwgjp5APUhn3IBx5M3F2ypGICnAvsrjfzalTChN
|
Plk77BWzLaU5UOKCBAJWrK8oZSTGOrp4VZqb62DuqMRejG0JXmneIVk7p79yn5eA
|
||||||
nkCmTfLynNIL1bX7PViABX2Q/yXGzPqFDe96pPw2hGjOXmxjKxjDoLT2IHixYp4D
|
ANVMGRF4b8RP9YUhzE8HACFzQebKpUU8XKv9+qsmO9Le5jUhU3UQeCSSzT/T5Dr8
|
||||||
cxb5519/WmOMrFd4SJeGiUR2Ph/VBZBVGafRfGAKMzxi666igWjwSU0YSDYeEUW/
|
kLDNtmW+mliQnxFlKcVWq2JIG+HaQD1KLOAl0JBNCOSLif2ofaHahuZ15agbYeis
|
||||||
BalkkWoz/KQZ5HgQwL9dyp26/cjDtpIwe8jLKVNI6aWhZ2ZmBxFwNEB6fE1txOjf
|
hyrBY92EhzqYXHk/Kzv4ff4r+WUs9NN7R4Gg+wfWvMcTtVfbi4Ht+pjjTtCZwK1C
|
||||||
ceIJAfm8y2qIolw3TpBAFk6s53jir17SrEux9VzfiEfFeQ0g5q7cjAs5HfhTOMfh
|
M8JebQn0NZSpVi3e7Xaz1fQ5Tqrg8PHZtkYGoIHLRPJQwLn9PHYtGzC3rFAk+Fqq
|
||||||
iuUnHcCFy27wd+8bPxEaRYR57u7hneenTn3BMuu1CZputDFJWRvweMZk3cH8tiXS
|
5WWHELxfcsZ6DakAGSXPK/80QhEZkpGmKizTwrEde+7fpEPxjdzUqlmH3rv7mFzS
|
||||||
WAGpXw7aKGIOpxe7Ye5X+T3xvYCBN77aFQKkrOmHMWFCbkr2QfnwVanxmmL9BwxF
|
WAGSiBIMjLR6ofb65vpghbwh6gXkpCtgUyINRhx/D+Kj5Z4lGD1u1I05DT1xD6VJ
|
||||||
rf8pG+H0URxBAsy9RZzSC+dXugnwnNBse3wupXf5YkipLx3rX9gtz4Y=
|
FAbnH7oZ3PJecoAXgRT05FndFA1xfPMCkugmec8ML/sEZt+c3kbrXaA=
|
||||||
=a7xO
|
=MqS3
|
||||||
-----END PGP MESSAGE-----
|
-----END PGP MESSAGE-----
|
||||||
fp: 8a3737d48f1035fe6c3a0a8fd6a1976ca74c7f3b
|
fp: 8a3737d48f1035fe6c3a0a8fd6a1976ca74c7f3b
|
||||||
- created_at: "2025-01-21T17:55:44Z"
|
- created_at: "2024-03-18T04:02:00Z"
|
||||||
enc: |-
|
enc: |-
|
||||||
-----BEGIN PGP MESSAGE-----
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
hQIMA0f3HaPlrXn5ARAAph6bpRqnO1TKEE7K6cMCxCA9k3xj7hknBdHN1KlIQkeb
|
hQIMA0f3HaPlrXn5AQ/+N5NK5UJdtw5e7O9T4hfIhtMXci/og1cJiI64daSyNeDH
|
||||||
LUOW9lvxh96q/fQ3NvZXpEQqZoEgdhnYTHgr/Hqx7VSksSicOkJLorrBLELjP1Xo
|
jq+CPJ8e73yiTPwu6wHHqfuEhlEuI6sJY0ZJVFU+h4SIBtG21veGEz7GzlYgBCJm
|
||||||
FKTxRYQmgZmgmBc0u7+lF2t0VLey6DfsC9ehY2LQcRuY9WxxCp2nDjjR4wrfmMKi
|
xvJHXjtM8AprqnFVO7Fj9QA/ik5QBP6ZpkOY7j8/qf1G7alOIne/MYRALXDvvIMH
|
||||||
i/SbpaM9Q29DpdNpwkSlTe2sKr+uMDVA2rCrjpisSDiUBgPe2eJEYPQJ7DHFxB4N
|
HTWE+Y2N57yZK55Pokmdw10hawbrn/N1nt2Y7sa1+5TlRNtuA/+zLkXtEjRr5U3N
|
||||||
26NHS+QtUgZhAA1DOGYZvrXzqcZSeAPGk8WXY44KA7o2iRFw+TczpF/VaMHRR5vN
|
DId+hqCKgXDqKLBMkh4mZUTGOGsk8eeKAWwyPp9+8A5/0rfy+xOJYEjjHICXQMSE
|
||||||
M62IoXGqnMIBau+tsiE1JEVg60DIHpBjsEY/WUgbNX02zfaHcp1OenxU4p7W19Xn
|
zfe6qvj/fRJKGzT5lEzD+ZKHlR0zHEwGRfHqrVUTdPcPdKj3DZILjsoe5ba4VlAp
|
||||||
Y8psPm7JNzmi3+nItB2i3+OLQrumk6VaT021ykterc52tR/3ejCzQCsfbqDLnc/6
|
sS0CAYTg3YuWMT4iHuOQlY5IoQxHHrn7k8ox5iZULFecg58f6r6iJL3AepDYWAey
|
||||||
r2SvNVDiKpxQ/iFyHb0uLHWy0Jx0lYwmMUrThe1f4k/+m9fEPrA5v/1kTGY5o1Iy
|
gtQXYBeaeCm5Ddwmd6TBVz8Q4bCVYIrHbVeAhSDkxfrWLc5UORggvLEWiXilGDJi
|
||||||
aTuHE19VtB6V+g1H6ZRPr8g8wn/3pg1nC2vUoRzT+oUudxrijrUH8SSlwhu59y1z
|
DzAv0MVHE2Wa3eOJLq05K2/LBqRBD1XYM3dcS6JSdFxWWMzvLdUOB4dAuPt9gpl9
|
||||||
7SrdMmtqA1/JsGNMawCLWd63u+/3GC9LmZzK2h/bV/R4DG5f8tsCpy9BrFAHqTMs
|
liaA13Blw/ev+U4ADxptrl+QuYRbWz3z6rniYpluSrTbVCKFRoHXSGFPy5u8/N6O
|
||||||
lZ3SONUUeM3uMOggQ/JT26EVxECmNGYIX1TYLOxcqisRXLbfco5LEc6T8PJvdivS
|
QyjfoovIBxXKnbUq2kMoFa/qFpc1pDUn0sjQNsUBdtorAu3Up4icyoih7qwx2J3S
|
||||||
WAFr6kBMdoeWhUT8MdT5AJBm9mo95A0WM8I14pGrszaezfgo9zc2zs0ebdLEjhI4
|
WAGB1jHWMfcsBJqPwjRYkqBf6MuwHAZWdd+zvj/fKfft9jtxLcCGOIM6QdfiWbl0
|
||||||
jlTf4tXgK3RG455CRWOd9OA3ukR83W7UW6LYjoNaDWHw2RXZlb+hv8E=
|
Wq4gHdH7OhSy+ZgRnaBRt/GAkzkHvfG68HfulviHZ1h2mrQN1y3mxpg=
|
||||||
=+MFT
|
=RCYB
|
||||||
-----END PGP MESSAGE-----
|
-----END PGP MESSAGE-----
|
||||||
fp: 0af7641adb8aa843136cf6d047f71da3e5ad79f9
|
fp: 0af7641adb8aa843136cf6d047f71da3e5ad79f9
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.9.2
|
version: 3.8.1
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
porkbun:
|
authelia:
|
||||||
api-key: ENC[AES256_GCM,data:A5J1sqwq6hs=,iv:77Mar3IX7mq7z7x6s9sSeGNVYc1Wv78HptJElEC7z3Q=,tag:eM/EF9TxKu+zcbJ1SYXiuA==,type:str]
|
storageEncryptionKey: ENC[AES256_GCM,data:8X6Zvq7ct1MkMH+lc5kTTHXOo6rGyhSzc3aWxGRA5tnBet+TGcENo0RYTYmactsPGVpTIUGGplaG7B7dqRPhkdDHhbCCZCm2nLaYjpVJ241DrpUNKHn8lvg/bMxUQ/Dvw76ByYuWN6bREr3XRaBztBSPzld8zTSYx71I0CKY7vk=,iv:cJSwfuVWO39qqKCGt2Mvw7pN8+hD6kRH9v4c/u4hLuk=,tag:YhdlXuX2ETxjb443RI8MsA==,type:str]
|
||||||
secret-api-key: ENC[AES256_GCM,data:8Xv+jWYaWMI=,iv:li4tdY0pch5lksftMmfMVS729caAwfaacoztaQ49az0=,tag:KhfElBGzVH4ByFPfuQsdhw==,type:str]
|
sessionSecret: ENC[AES256_GCM,data:dnoWmc4HND62w3jMXL+akncAEb61c/I70DgRytx55Wxcn4rMiswp6zCkRdsP4CkouTQ1lyAcQrubp5I8M9Kyow/KBMYz9dPkr4+2xJ9w0SEmAVhyPe2DFvYos3x0Uvx5S0B3o1mXoXqbg78e4w5yEIbALiJT8VPGrWK8Cl4nVPo=,iv:FHDXUW2DWUmEZzWUYkYduogdVOtvMlRH4/fVg05cZaI=,tag:u282WQnHpBsZGYJH7mFFKA==,type:str]
|
||||||
|
jwtSecret: ENC[AES256_GCM,data:0M3AyoMp+orrljl5NsxmthzrHMmu0REcz7+9fpFKbwwqV6KqlpgGddjYZIsTpHEWEq9zhZ2YWLJkMxKdDgROVHUFZGKut28JPSAjjY+1V0wxNBnfSCnxEv5BUw2+cCxcpCwYQyNfRK6SotTt8aqpxvda4oRXpzxV6SW7ogDjc6E=,iv:D57SynZkW2JuFyX6bpZYkxpR2KtkOmKaySg1Bxim0r8=,tag:JCPGZaumdHrtgcH16A7b+g==,type:str]
|
||||||
battery-manager:
|
battery-manager:
|
||||||
email: ENC[AES256_GCM,data:LM/EGzWHfVQ=,iv:jFaoUQuUfuGoOyj/GFpdI8TerH/c8D9fjvio+IEt2Tc=,tag:IWLiN011JEnHRLIXWQgfmA==,type:str]
|
email: ENC[AES256_GCM,data:LM/EGzWHfVQ=,iv:jFaoUQuUfuGoOyj/GFpdI8TerH/c8D9fjvio+IEt2Tc=,tag:IWLiN011JEnHRLIXWQgfmA==,type:str]
|
||||||
password: ENC[AES256_GCM,data:SUxjqS7SJHM=,iv:LvdKk88S+nSImh6/ZezbFGLCUBu1Lpdu+neF2xyHdBg=,tag:rcMyZuW4FVNbcbz00wQKBg==,type:str]
|
password: ENC[AES256_GCM,data:SUxjqS7SJHM=,iv:LvdKk88S+nSImh6/ZezbFGLCUBu1Lpdu+neF2xyHdBg=,tag:rcMyZuW4FVNbcbz00wQKBg==,type:str]
|
||||||
|
@ -16,9 +17,6 @@ steam:
|
||||||
heisenbridge:
|
heisenbridge:
|
||||||
as-token: ENC[AES256_GCM,data:tXbOeo7nv8I=,iv:wJAKcOXX9nGIw4n38ThOoj29u7dUWhsxSQG/p79JlEw=,tag:rTVaGS2UuWcea1uBa8YX2g==,type:str]
|
as-token: ENC[AES256_GCM,data:tXbOeo7nv8I=,iv:wJAKcOXX9nGIw4n38ThOoj29u7dUWhsxSQG/p79JlEw=,tag:rTVaGS2UuWcea1uBa8YX2g==,type:str]
|
||||||
hs-token: ENC[AES256_GCM,data:VBwvwomv0Xg=,iv:q6INtJ+rg+QiXj8uBdBzQYQZUBBXp+9odxDHwvu8Jxc=,tag:XKhm8nxygAkKaiVPJ2Fcdg==,type:str]
|
hs-token: ENC[AES256_GCM,data:VBwvwomv0Xg=,iv:q6INtJ+rg+QiXj8uBdBzQYQZUBBXp+9odxDHwvu8Jxc=,tag:XKhm8nxygAkKaiVPJ2Fcdg==,type:str]
|
||||||
matrix-hookshot:
|
|
||||||
as-token: ENC[AES256_GCM,data:uSUOo4f2KqA=,iv:Xb9G8Ecv6m59m51kDw2bOfq3SMJt4g9/6/EdH74R+KM=,tag:K9MSfO2c2Y4rlf0eYrmTnw==,type:str]
|
|
||||||
hs-token: ENC[AES256_GCM,data:0KsyA06InL4=,iv:zAR0Y1fk8SyodcSLBHlQ8I+BAmttz9Hkd8Q3OREFqs4=,tag:t1Et8N/3seq95DeGoUd7Sw==,type:str]
|
|
||||||
wireguard:
|
wireguard:
|
||||||
server-key: ENC[AES256_GCM,data:FvY897XdKoa/mckE8JQLCkklsnYD6Wz1wpsu5t3uhEnW3iarnDQxF9msuYU=,iv:jqGXfekM+Vs+J9b5nlZ5Skd1ZKHajoUo2Dc4tMYPm1w=,tag:EehikjI/FCU8wqtpvJRamQ==,type:str]
|
server-key: ENC[AES256_GCM,data:FvY897XdKoa/mckE8JQLCkklsnYD6Wz1wpsu5t3uhEnW3iarnDQxF9msuYU=,iv:jqGXfekM+Vs+J9b5nlZ5Skd1ZKHajoUo2Dc4tMYPm1w=,tag:EehikjI/FCU8wqtpvJRamQ==,type:str]
|
||||||
restic:
|
restic:
|
||||||
|
@ -37,43 +35,43 @@ sops:
|
||||||
azure_kv: []
|
azure_kv: []
|
||||||
hc_vault: []
|
hc_vault: []
|
||||||
age: []
|
age: []
|
||||||
lastmodified: "2025-02-07T17:43:24Z"
|
lastmodified: "2024-04-12T01:00:31Z"
|
||||||
mac: ENC[AES256_GCM,data:akmD/bfgeTyFzW1quvM16cdj0fC6+CbJ8WyX9173H11yKGxvE1USQYcErpl1SHOx9Jk8LVb7f+MsUm2fjQF1MEq6xaWI74jem12lZ9CGXFaTL7e87JvfbK7pV+aKpxSBBNFyJgbYm30ibdUwxwKmNVfPb1e0HT9qwenvoV7RobM=,iv:mKqOW0ULXL711uczUbRf9NPo6uPTQoS/IbR46S+JID4=,tag:vE6NYzYLbQHDImov1XGTcg==,type:str]
|
mac: ENC[AES256_GCM,data:fVnMwfvGi7vtP1Fg4NLrhGvLF2PcIgZPOcwk4Ssm4iw5iSj0K1npOX3pd5BWzyszqchfYYRHY99GllAump0bZmprVAld9rf70B2HZIVvowBPuUXfc9Cz/5q0z+s8bQ5vCdElW1Bh7h8W/POePdc8cFGAyBS4i1ZVNheIDOHdDjI=,iv:Bi6rekXOx3/dwwPRryF3CoAoQi3D06ABysRF1oBeG5A=,tag:0TCra+AkhBDczj4uvAzKMw==,type:str]
|
||||||
pgp:
|
pgp:
|
||||||
- created_at: "2025-01-21T17:55:30Z"
|
- created_at: "2023-12-29T15:25:27Z"
|
||||||
enc: |-
|
enc: |
|
||||||
-----BEGIN PGP MESSAGE-----
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
hQEMA7x7stsXx45CAQf9ELnm9TdXCIO6fTPiSCkKthx0tSHqBWX/s63158k4IUu7
|
hQEMA7x7stsXx45CAQf/RWxP6z7xjV5TqiA6lFhtygjrH9x3y1DUWG9aUb/dO+xH
|
||||||
v0WWgQy0SKFU3AwIFuVaAYEXB32SaOWKq2WbVAbFZU+xhyUmNe9asg9Fl24+zjGI
|
zDbGMYqGe9RPlgi5sWPstdKXvCgs+AKNj93qJYMwEtaasJOinYXCGeAQmzg90+pt
|
||||||
oYnPzv3lz/5vcI6Q9rZi8F2uIi2GQZnbscS1XfjA5u17uOalQpb0hjUXr0LMaUvU
|
bS6SoBHhGIxAvvLKKPtYx0V50I2reYR+32ux9bcrnzwIsV0P7/SSp1Cl8H+sotB8
|
||||||
Sggm1ZMKE1o1mHAWK6ZT9SrTMIroWFArJRZLS1eY/vI9ja7I5YR3z0MkLqIvdIp+
|
yf+0ULXcpC+SYECmZqzR9qQ3S+3I6/+QS+QgWj4NsyF+apxnE9oQDcBLdYP4aKgR
|
||||||
B4DsOXLlqAqtVoPGcK1CixQiXzzwyQAYHyJc3JFDpaF9Y4S5/bkMLGyQVMA259a+
|
JHERA9HYfDTKoS137pFHxgINqHkFRY6lhoZdz1yDzOjiPxd8YVfPdKyf022Rg+cX
|
||||||
W7ge+EngdJdXV8Unj4s7ndB1e1iM87Jc+4YOcA7jC9JeAR98n8GL+MN9vE8q2AsB
|
J/Q2P+OhNZEG3gapNATp6wH3niovA89KwZKSmbTZOdJeAZ6NV6TiUP+TgGg5+CmV
|
||||||
qSOXiGSwmAkhq7+ZwJHWlivlB1In0coyJ4eMd/yhyBuc2NrstO0t595HlA93GcLN
|
pSLaGel2NZRnFVNdDFi0dsOwhHv3FpKhIpALJh08/jsmAAslfE7vVlcEnaoUJPTS
|
||||||
5JsXIFMqklqGSzE2KgEXhxa2aUoJxcpApVz2BLFPvg==
|
3v86AACUC5D/gUxmFrrED1qoxbELCmZ17xTwjQzxwg==
|
||||||
=X72N
|
=KzdF
|
||||||
-----END PGP MESSAGE-----
|
-----END PGP MESSAGE-----
|
||||||
fp: B9C7AE554602D82B1AC2CFD0BC7BB2DB17C78E42!
|
fp: 535B61015823443941C744DD12264F6BBDFABA89
|
||||||
- created_at: "2025-01-21T17:55:30Z"
|
- created_at: "2023-12-29T15:25:27Z"
|
||||||
enc: |-
|
enc: |
|
||||||
-----BEGIN PGP MESSAGE-----
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
hQIMA/3lh+ZzfS28AQ//TPVk5x8wZtKnNcbZjqmXUvi7FzaDMGYFNtiX+bQ17Lkm
|
hQIMA/3lh+ZzfS28ARAAm729dMouF7juUeHAb+aHMoyZVKsXapxnxebkjE/LSIbz
|
||||||
qdo9TcH8gZI+xni6wlROQBVmu5MAVkT+NWnZIyxN7pokQPmb2v9zo7oyUPV70owU
|
IEZwegTNrtxQJLclV4Km2gUaBTcE4vLJCpB7YxZvk7JV9OdVKi97o9PcXUXbz9ej
|
||||||
dD/LDNVqgyFTVzWKco4wr8CUwQWhOJI9wM0sjTCOTTbCT5hYVnJOLe630sNG9G5b
|
/WomnEvFyyxTZGTiHU+L4kNudl8UAKhTt3P4fR3PLpTily75Kn53tzLFJuCO8fAY
|
||||||
cJYvaQFxKJeHKJq/DL1I9wT02gOE7vu2xh2OyEozEz3SlB7Bp2mGTLNSiOZBhbzh
|
I/YwQAzayxhPcxk3FuPsD/ONiG7mW8n2ZwfwgOkKXwnrlJv7DreKJRYzu/EeuvX/
|
||||||
DNwFRTeDc1Z/ACQJyoGEXkmj1VLyBFuCfXu4UvUQSmlfyCXKTD1/CUo2uiw1N44W
|
d4oz+k+xofniOeZmQjZllzR7/++MBg/e1U9VocN1EAWpWHP5taLiThfnVSGDhlQM
|
||||||
sYh5UpIrCU2eVAAXAiD1nB6dHxiau3QlapNQfbY2sOZVSnsB21yIja9C3aQN/0Q5
|
+4WT5ezH6EuUQlAyQNpDaCincBvCHInhrNlUPOpW51nHMb0y3n4x2hMtZA0JbYEu
|
||||||
gRrKYcwULzLZ0Z3oQqQxQG9acU8L9CwjKJ1vOKgPVF7hcWkba2bLVsMMaK6seVNc
|
mkWTYDe65cHjImHXQk9oO2/v4oIyq7ywHX7g2hqVbbiLHZqqTaGfV8lP30+r6/UQ
|
||||||
jazp9gDAj440S2aE86CdQvgcOEsfgPhBZrulYglbhW8ZaIN2SdjDN/xP1Tn5PadP
|
29iAdWac1hY5HDzwbqpY6b38i60j4bkiS83xqrGYBy037bCFk1oHJqwxp5P7vrzr
|
||||||
gS2DVgqILKPRMF4VavzV3uhEA77QF39Hr18SeToNWcDmfqNPNNf7HpnogstGPf3y
|
rTv5NBr95BlwF+s8xPEPZneaEu7N3UnhhSzDWp1jgsCxN9b/XHarchNt70xEt2VS
|
||||||
xrFAysLbD8IClU4LqI5M19akODON8qeQa5QD+jHOJmAYnMYNRmY/IwHb/SC5WyIm
|
xpgs9GEXhsJcbrFNPYqTkFb8vjLFI+poGPTfadW17j4Pp5ftIBRNdKvDG0ni/AIp
|
||||||
EPNZg5E+q9cNrTKtEIuWec0SObqpaUz2E/Vt9+dge0uVgTA/QqPvMP7x19XBrRDS
|
K98R/nvaHEFuX31SkL8ZUIRqhJm3JVqilFxLAJrqGuSN3jA6wKrimUYpK+t+64jS
|
||||||
WAFiKkv7MxImNgcqqe7D3StZoeNm+RXJiULaxxuR8qmMnmvaC+L7ggI4QR3TPQw2
|
WAEN9jHYFQDTVHix3g15S5YTGh5ROyqxouDhvSDFTmGtbm5W/HYgnkZmh53TgVeJ
|
||||||
mSoi7SkdjQUCa8ut15UwNHTGO+smbRs9aonGP4G7c1cOH90YYvR+BTs=
|
Rph/O9QptculzTN+nEqshBhbjhl/uDsLsjLYo/O1AyCwTUSd3OKn6uU=
|
||||||
=q2IG
|
=zThh
|
||||||
-----END PGP MESSAGE-----
|
-----END PGP MESSAGE-----
|
||||||
fp: 2f5caa73e7ceea4fcc8d2881fde587e6737d2dbc
|
fp: 2f5caa73e7ceea4fcc8d2881fde587e6737d2dbc
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.9.2
|
version: 3.8.1
|
||||||
|
|
|
@ -11,230 +11,62 @@ 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
|
PhCB2Dz76iQ5nDY4MQ==
|
||||||
6/gEa+M6kuTyRYMokA8VO3w8eeZk0bqkPDP1/HmHiuz15/MOVNg0IIdQBIH0Ky2z
|
=R7Pm
|
||||||
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
|
|
||||||
GmeDneCrlseO
|
|
||||||
=ncr2
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
|
@ -1,383 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.security.crowdsec;
|
|
||||||
settingsFormat = pkgs.formats.yaml { };
|
|
||||||
|
|
||||||
hub = pkgs.fetchFromGitHub {
|
|
||||||
owner = "crowdsecurity";
|
|
||||||
repo = "hub";
|
|
||||||
rev = "7a3b4753f4577257c0cbeb8f8f90c7f17d2ae008";
|
|
||||||
hash = "sha256-HB4jHyhiO8gjBkLmpo6bDbwhfm5m5nAtNlKhDkZjt2I=";
|
|
||||||
};
|
|
||||||
|
|
||||||
cscli = pkgs.writeShellScriptBin "cscli" ''
|
|
||||||
export PATH="$PATH:${cfg.package}/bin/"
|
|
||||||
|
|
||||||
sudo=exec
|
|
||||||
if [ "$USER" != "crowdsec" ]; then
|
|
||||||
sudo='exec /run/wrappers/bin/sudo -u crowdsec'
|
|
||||||
fi
|
|
||||||
|
|
||||||
$sudo ${cfg.package}/bin/cscli "$@"
|
|
||||||
'';
|
|
||||||
|
|
||||||
acquisitions = ''
|
|
||||||
---
|
|
||||||
${lib.concatMapStringsSep "\n---\n" builtins.toJSON cfg.acquisitions}
|
|
||||||
---
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ ./remediations ];
|
|
||||||
|
|
||||||
options.security.crowdsec =
|
|
||||||
let
|
|
||||||
inherit (lib.types)
|
|
||||||
nullOr
|
|
||||||
listOf
|
|
||||||
package
|
|
||||||
path
|
|
||||||
str
|
|
||||||
;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
enable = lib.mkEnableOption "crowdsec";
|
|
||||||
|
|
||||||
package = lib.mkOption {
|
|
||||||
type = package;
|
|
||||||
default = pkgs.crowdsec;
|
|
||||||
};
|
|
||||||
|
|
||||||
stateDirectory = lib.mkOption {
|
|
||||||
type = path;
|
|
||||||
readOnly = true;
|
|
||||||
|
|
||||||
description = ''
|
|
||||||
The state directory of the crowdsec instance. Cannot be
|
|
||||||
changed, but is exposed for downstream use.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
settings = lib.mkOption {
|
|
||||||
inherit (settingsFormat) type;
|
|
||||||
default = { };
|
|
||||||
|
|
||||||
description = ''
|
|
||||||
The crowdsec configuration. Refer to
|
|
||||||
<https://docs.crowdsec.net/docs/next/configuration/crowdsec_configuration/>
|
|
||||||
for details on supported values.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
parserWhitelist = lib.mkOption {
|
|
||||||
type = listOf str;
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
Set of IP addresses to add to a parser-based whitelist.
|
|
||||||
|
|
||||||
Addresses can be specified either as plain IP addresses or
|
|
||||||
in CIDR notation.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
acquisitions = lib.mkOption {
|
|
||||||
type = listOf settingsFormat.type;
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
Log acquisitions.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
extraGroups = lib.mkOption {
|
|
||||||
type = listOf str;
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
Additional groups to make the service part of.
|
|
||||||
|
|
||||||
Required to permit reading from various log sources.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
hubConfigurations = {
|
|
||||||
collections = lib.mkOption {
|
|
||||||
type = listOf str;
|
|
||||||
description = ''
|
|
||||||
List of pre-made crowdsec collections to install.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
scenarios = lib.mkOption {
|
|
||||||
type = listOf str;
|
|
||||||
description = ''
|
|
||||||
List of pre-made crowdsec scenarios to install.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
parsers = lib.mkOption {
|
|
||||||
type = listOf str;
|
|
||||||
description = ''
|
|
||||||
List of pre-made crowdsec parsers to install.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
postoverflows = lib.mkOption {
|
|
||||||
type = listOf str;
|
|
||||||
description = ''
|
|
||||||
List of pre-made crowdsec postoverflows to install.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
appsecConfigs = lib.mkOption {
|
|
||||||
type = listOf str;
|
|
||||||
description = ''
|
|
||||||
List of pre-made crowdsec appsec configurations to install.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
appsecRules = lib.mkOption {
|
|
||||||
type = listOf str;
|
|
||||||
description = ''
|
|
||||||
List of pre-made crowdsec appsec rules to install.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
centralApiCredentials = lib.mkOption {
|
|
||||||
type = nullOr path;
|
|
||||||
default = null;
|
|
||||||
|
|
||||||
description = ''
|
|
||||||
The API key to access crowdsec's central API - this is
|
|
||||||
required to access any of the shared blocklists.
|
|
||||||
|
|
||||||
Use of this feature is optional, entering no API key (the
|
|
||||||
default) turns all sharing or receiving of blocked IPs off.
|
|
||||||
|
|
||||||
Note that adding the API key by itself does not enable
|
|
||||||
sharing of blocked IPs with the central API. This limits the
|
|
||||||
types of blocklists this instance can access.
|
|
||||||
|
|
||||||
To also turn sharing blocked IPs on, set
|
|
||||||
`api.server.online_client.sharing = true;`.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
ctiApiKey = lib.mkOption {
|
|
||||||
type = nullOr path;
|
|
||||||
default = null;
|
|
||||||
|
|
||||||
description = ''
|
|
||||||
The API key for crowdsec's CTI offering.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
# Set up default settings; anything that *shouldn't* be changed is
|
|
||||||
# set to the default priority so that users need to use
|
|
||||||
# `lib.mkForce`.
|
|
||||||
security.crowdsec = {
|
|
||||||
stateDirectory = "/var/lib/crowdsec";
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
common = {
|
|
||||||
daemonize = true;
|
|
||||||
# The default logs to files, which isn't the preferred way
|
|
||||||
# on NixOS
|
|
||||||
log_media = "stdout";
|
|
||||||
};
|
|
||||||
|
|
||||||
config_paths = {
|
|
||||||
config_dir = "${cfg.stateDirectory}/config/";
|
|
||||||
data_dir = "${cfg.stateDirectory}/data/";
|
|
||||||
# This "config" file is intended to be written to using the
|
|
||||||
# cscli tool, so you can temporarily make it so rules don't
|
|
||||||
# do anything but log what they *would* do for
|
|
||||||
# experimentation.
|
|
||||||
simulation_path = "${cfg.stateDirectory}/config/simulation.yaml";
|
|
||||||
|
|
||||||
pattern_dir = lib.mkDefault "${cfg.package}/share/crowdsec/config/patterns";
|
|
||||||
|
|
||||||
hub_dir = hub;
|
|
||||||
index_path = "${hub}/.index.json";
|
|
||||||
|
|
||||||
# Integrations aren't supported for now
|
|
||||||
notification_dir = lib.mkDefault "/var/empty/";
|
|
||||||
plugin_dir = lib.mkDefault "/var/empty/";
|
|
||||||
};
|
|
||||||
|
|
||||||
crowdsec_service.acquisition_path =
|
|
||||||
# Using an if/else here because `mkMerge` does not work in
|
|
||||||
# YAML-type options
|
|
||||||
if cfg.acquisitions == [ ] then
|
|
||||||
"${cfg.package}/share/crowdsec/config/acquis.yaml"
|
|
||||||
else
|
|
||||||
pkgs.writeText "acquis.yaml" acquisitions;
|
|
||||||
|
|
||||||
cscli = {
|
|
||||||
prometheus_uri = lib.mkDefault "127.0.0.1:6060";
|
|
||||||
};
|
|
||||||
|
|
||||||
db_config = {
|
|
||||||
type = lib.mkDefault "sqlite";
|
|
||||||
db_path = lib.mkDefault "${cfg.stateDirectory}/data/crowdsec.db";
|
|
||||||
use_wal = lib.mkDefault true;
|
|
||||||
flush = {
|
|
||||||
max_items = lib.mkDefault 5000;
|
|
||||||
max_age = lib.mkDefault "7d";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
api = {
|
|
||||||
cti = {
|
|
||||||
enabled = cfg.ctiApiKey != null;
|
|
||||||
key = cfg.ctiApiKey;
|
|
||||||
};
|
|
||||||
client.credentials_path = "${cfg.stateDirectory}/local_credentials.yaml";
|
|
||||||
server = {
|
|
||||||
listen_uri = lib.mkDefault "127.0.0.1:8080";
|
|
||||||
profiles_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/profiles.yaml";
|
|
||||||
console_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/console.yaml";
|
|
||||||
|
|
||||||
online_client = {
|
|
||||||
# By default, we don't let crowdsec phone home, since
|
|
||||||
# this is usually within NixOS users' concerns.
|
|
||||||
sharing = lib.mkDefault false;
|
|
||||||
credentials_path = cfg.centralApiCredentials;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# We enable prometheus by default, since cscli relies on it
|
|
||||||
# for metrics
|
|
||||||
prometheus = {
|
|
||||||
enabled = lib.mkDefault true;
|
|
||||||
level = lib.mkDefault "full";
|
|
||||||
listen_addr = lib.mkDefault "127.0.0.1";
|
|
||||||
listen_port = lib.mkDefault 6060;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.packages = [ cfg.package ];
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
systemPackages = [
|
|
||||||
# To add completions; sadly need to hand-roll this since
|
|
||||||
# neither `symlinkJoin` nor `buildEnv` have collision
|
|
||||||
# handling.
|
|
||||||
(pkgs.runCommandNoCCLocal "cscli" { } ''
|
|
||||||
mkdir -p $out
|
|
||||||
ln -s ${cscli}/bin $out/bin
|
|
||||||
ln -s ${cfg.package}/share $out/share
|
|
||||||
'')
|
|
||||||
];
|
|
||||||
|
|
||||||
etc."crowdsec/config.yaml".source = settingsFormat.generate "crowdsec-settings.yaml" cfg.settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd = {
|
|
||||||
tmpfiles.settings."10-crowdsec" = {
|
|
||||||
"${cfg.stateDirectory}".d = {
|
|
||||||
user = "crowdsec";
|
|
||||||
group = "crowdsec";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
|
|
||||||
# This must be created for the setup service to work
|
|
||||||
"${cfg.stateDirectory}/config".d = {
|
|
||||||
user = "crowdsec";
|
|
||||||
group = "crowdsec";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
|
|
||||||
"${cfg.stateDirectory}/config/parsers".d = lib.mkIf (cfg.parserWhitelist != [ ]) {
|
|
||||||
user = "crowdsec";
|
|
||||||
group = "crowdsec";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
|
|
||||||
"${cfg.stateDirectory}/config/parsers/s02-enrich".d = lib.mkIf (cfg.parserWhitelist != [ ]) {
|
|
||||||
user = "crowdsec";
|
|
||||||
group = "crowdsec";
|
|
||||||
mode = "0700";
|
|
||||||
};
|
|
||||||
|
|
||||||
"${cfg.stateDirectory}/config/parsers/s02-enrich/nixos-whitelist.yaml" =
|
|
||||||
lib.mkIf (cfg.parserWhitelist != [ ])
|
|
||||||
{
|
|
||||||
"L+".argument =
|
|
||||||
(settingsFormat.generate "crowdsec-nixos-whitelist.yaml" {
|
|
||||||
name = "nixos/parser-whitelist";
|
|
||||||
description = "Parser whitelist generated by the crowdsec NixOS module";
|
|
||||||
whitelist = {
|
|
||||||
reason = "Filtered by NixOS whitelist";
|
|
||||||
ip = lib.lists.filter (ip: !(lib.hasInfix "/" ip)) cfg.parserWhitelist;
|
|
||||||
cidr = lib.lists.filter (ip: lib.hasInfix "/" ip) cfg.parserWhitelist;
|
|
||||||
};
|
|
||||||
}).outPath;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services = {
|
|
||||||
crowdsec-setup = {
|
|
||||||
# TODO(tlater): Depend on tmpfiles path for
|
|
||||||
# /var/lib/crowdsec/config
|
|
||||||
description = "Crowdsec database and config preparation";
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
if [ ! -e '${cfg.settings.config_paths.simulation_path}' ]; then
|
|
||||||
cp '${cfg.package}/share/crowdsec/config/simulation.yaml' '${cfg.settings.config_paths.simulation_path}'
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -e '${cfg.settings.api.client.credentials_path}' ]; then
|
|
||||||
${cfg.package}/bin/cscli machines add --auto --file '${cfg.settings.api.client.credentials_path}'
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
User = "crowdsec";
|
|
||||||
Group = "crowdsec";
|
|
||||||
StateDirectory = "crowdsec";
|
|
||||||
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Note that the service basics are already defined upstream
|
|
||||||
crowdsec = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
after = [ "crowdsec-setup.service" ];
|
|
||||||
bindsTo = [ "crowdsec-setup.service" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
User = "crowdsec";
|
|
||||||
Group = "crowdsec";
|
|
||||||
SupplementaryGroups = cfg.extraGroups;
|
|
||||||
|
|
||||||
StateDirectory = "crowdsec";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
users = {
|
|
||||||
users.crowdsec = {
|
|
||||||
isSystemUser = true;
|
|
||||||
home = cfg.stateDirectory;
|
|
||||||
group = "crowdsec";
|
|
||||||
};
|
|
||||||
groups = {
|
|
||||||
crowdsec = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
{
|
|
||||||
flake-inputs,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
inherit (flake-inputs.self.packages.${pkgs.system}) crowdsec-firewall-bouncer;
|
|
||||||
|
|
||||||
crowdsecCfg = config.security.crowdsec;
|
|
||||||
cfg = crowdsecCfg.remediationComponents.firewallBouncer;
|
|
||||||
settingsFormat = pkgs.formats.yaml { };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.security.crowdsec.remediationComponents.firewallBouncer = {
|
|
||||||
enable = lib.mkEnableOption "cs-firewall-bouncer";
|
|
||||||
|
|
||||||
settings = lib.mkOption {
|
|
||||||
inherit (settingsFormat) type;
|
|
||||||
default = { };
|
|
||||||
|
|
||||||
description = ''
|
|
||||||
The bouncer configuration. Refer to
|
|
||||||
<https://docs.crowdsec.net/u/bouncers/firewall/> for details
|
|
||||||
on supported values.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
security.crowdsec.remediationComponents.firewallBouncer.settings = {
|
|
||||||
mode = lib.mkDefault "${if config.networking.nftables.enable then "nftables" else "iptables"}";
|
|
||||||
log_mode = "stdout";
|
|
||||||
iptables_chains = [ "nixos-fw" ];
|
|
||||||
|
|
||||||
# Don't let users easily override this; unfortunately we need to
|
|
||||||
# set up this key through substitution at runtime.
|
|
||||||
api_key = lib.mkForce "\${API_KEY}";
|
|
||||||
api_url = lib.mkDefault "http://${crowdsecCfg.settings.api.server.listen_uri}";
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd = {
|
|
||||||
packages = [ crowdsec-firewall-bouncer ];
|
|
||||||
|
|
||||||
services = {
|
|
||||||
crowdsec-firewall-bouncer-setup = {
|
|
||||||
description = "Crowdsec firewall bouncer config preparation";
|
|
||||||
script = ''
|
|
||||||
if [ ! -e '${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml' ]; then
|
|
||||||
${crowdsecCfg.package}/bin/cscli -oraw bouncers add "cs-firewall-bouncer-$(${pkgs.coreutils}/bin/date +%s)" > \
|
|
||||||
${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Stdout redirection is deliberately used to forcibly
|
|
||||||
# overwrite the file if it exists
|
|
||||||
API_KEY="$(<${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml)" \
|
|
||||||
${lib.getExe pkgs.envsubst} \
|
|
||||||
-i ${settingsFormat.generate "crowdsec-firewall-bouncer.yaml" cfg.settings} \
|
|
||||||
> /var/lib/crowdsec/config/crowdsec-firewall-bouncer.yaml
|
|
||||||
'';
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
User = "crowdsec";
|
|
||||||
Group = "crowdsec";
|
|
||||||
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
crowdsec-firewall-bouncer = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
after = [ "crowdsec-firewall-bouncer-setup.service" ];
|
|
||||||
bindsTo = [ "crowdsec-firewall-bouncer-setup.service" ];
|
|
||||||
requiredBy = [ "crowdsec.service" ];
|
|
||||||
|
|
||||||
path =
|
|
||||||
lib.optionals (cfg.settings.mode == "ipset" || cfg.settings.mode == "iptables") [ pkgs.ipset ]
|
|
||||||
++ lib.optional (cfg.settings.mode == "iptables") pkgs.iptables
|
|
||||||
++ lib.optional (cfg.settings.mode == "nftables") pkgs.nftables;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
{ imports = [ ./cs-firewall-bouncer.nix ]; }
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./crowdsec
|
|
||||||
./nginxExtensions.nix
|
./nginxExtensions.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,57 +3,156 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}: {
|
||||||
{
|
|
||||||
options = {
|
options = {
|
||||||
services.nginx.domain = lib.mkOption {
|
services.nginx.domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
description = "The base domain name to append to virtual domain names";
|
description = "The base domain name to append to virtual domain names";
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.virtualHosts =
|
services.nginx.virtualHosts = let
|
||||||
let
|
autheliaDomain = "auth.${config.services.nginx.domain}";
|
||||||
extraVirtualHostOptions =
|
extraLocationOptions = {config, ...}: {
|
||||||
{ name, config, ... }:
|
options = {
|
||||||
{
|
enableAutheliaProxy = lib.mkEnableOption "Enable recommended authelia proxy settings";
|
||||||
options = {
|
enableAuthorization = lib.mkEnableOption "Enable authorization via authelia";
|
||||||
enableHSTS = lib.mkEnableOption "Enable HSTS";
|
};
|
||||||
|
|
||||||
addAccessLog = lib.mkOption {
|
config = {
|
||||||
type = lib.types.bool;
|
recommendedProxySettings = lib.mkIf config.enableAutheliaProxy false;
|
||||||
default = true;
|
|
||||||
description = ''
|
|
||||||
Add special logging to `/var/log/nginx/''${serverName}`
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
extraConfig = lib.concatStringsSep "\n" [
|
||||||
extraConfig = lib.concatStringsSep "\n" [
|
(lib.optionalString config.enableAutheliaProxy ''
|
||||||
(lib.optionalString config.enableHSTS ''
|
proxy_set_header Host $host;
|
||||||
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
|
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||||
'')
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
(lib.optionalString config.addAccessLog ''
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
access_log /var/log/nginx/${name}/access.log upstream_time;
|
proxy_set_header X-Forwarded-URI $request_uri;
|
||||||
'')
|
proxy_set_header X-Forwarded-Ssl on;
|
||||||
];
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
client_body_buffer_size 128k;
|
||||||
|
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
|
||||||
|
proxy_redirect http:// $scheme://;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_cache_bypass $cookie_session;
|
||||||
|
proxy_no_cache $cookie_session;
|
||||||
|
proxy_buffers 64 256k;
|
||||||
|
|
||||||
|
real_ip_header X-Forwarded-For;
|
||||||
|
real_ip_recursive on;
|
||||||
|
|
||||||
|
send_timeout 5m;
|
||||||
|
proxy_read_timeout 360;
|
||||||
|
proxy_send_timeout 360;
|
||||||
|
proxy_connect_timeout 360;
|
||||||
|
'')
|
||||||
|
(lib.optionalString config.enableAuthorization ''
|
||||||
|
auth_request /authelia;
|
||||||
|
|
||||||
|
set_escape_uri $target_url $scheme://$http_host$request_uri;
|
||||||
|
|
||||||
|
auth_request_set $user $upstream_http_remote_user;
|
||||||
|
auth_request_set $groups $upstream_http_remote_groups;
|
||||||
|
auth_request_set $name $upstream_http_remote_name;
|
||||||
|
auth_request_set $email $upstream_http_remote_email;
|
||||||
|
|
||||||
|
proxy_set_header Remote-User $user;
|
||||||
|
proxy_set_header Remote-Groups $groups;
|
||||||
|
proxy_set_header Remote-Email $email;
|
||||||
|
proxy_set_header Remote-Name $name;
|
||||||
|
|
||||||
|
error_page 401 =302 https://${autheliaDomain}/?rd=$target_url;
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
extraVirtualHostOptions = {
|
||||||
|
name,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
options = {
|
||||||
|
enableAuthorization = lib.mkEnableOption "Enable authorization via authelia";
|
||||||
|
enableHSTS = lib.mkEnableOption "Enable HSTS";
|
||||||
|
|
||||||
|
addAccessLog = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Add special logging to `/var/log/nginx/''${serverName}`
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
locations = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule extraLocationOptions);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
extraConfig = lib.concatStringsSep "\n" [
|
||||||
|
(lib.optionalString config.enableHSTS ''
|
||||||
|
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
|
||||||
|
'')
|
||||||
|
(lib.optionalString config.addAccessLog ''
|
||||||
|
access_log /var/log/nginx/${name}/access.log upstream_time;
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
|
||||||
|
locations = lib.mkIf config.enableAuthorization {
|
||||||
|
"/".enableAuthorization = true;
|
||||||
|
"/authelia" = {
|
||||||
|
proxyPass = "http://127.0.0.1:9091/api/verify";
|
||||||
|
recommendedProxySettings = false;
|
||||||
|
extraConfig = ''
|
||||||
|
internal;
|
||||||
|
|
||||||
|
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||||
|
proxy_set_header X-Original-Method $request_method;
|
||||||
|
proxy_set_header X-Forwarded-Method $request_method;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
|
proxy_set_header X-Forwarded-Uri $request_uri;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header Content-Length "";
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_pass_request_body off;
|
||||||
|
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
|
||||||
|
proxy_redirect http:// $scheme://;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_cache_bypass $cookie_session;
|
||||||
|
proxy_no_cache $cookie_session;
|
||||||
|
proxy_buffers 4 32k;
|
||||||
|
client_body_buffer_size 128k;
|
||||||
|
|
||||||
|
send_timeout 5m;
|
||||||
|
proxy_read_timeout 240;
|
||||||
|
proxy_send_timeout 240;
|
||||||
|
proxy_connect_timeout 240;
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
};
|
||||||
lib.mkOption { type = lib.types.attrsOf (lib.types.submodule extraVirtualHostOptions); };
|
};
|
||||||
|
in
|
||||||
|
lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule extraVirtualHostOptions);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
# Don't attempt to run acme if the domain name is not tlater.net
|
# Don't attempt to run acme if the domain name is not tlater.net
|
||||||
systemd.services =
|
systemd.services = let
|
||||||
let
|
confirm = ''[[ "tlater.net" = ${config.services.nginx.domain} ]]'';
|
||||||
confirm = ''[[ "tlater.net" = ${config.services.nginx.domain} ]]'';
|
in
|
||||||
in
|
lib.mapAttrs' (cert: _:
|
||||||
lib.mapAttrs' (
|
|
||||||
cert: _:
|
|
||||||
lib.nameValuePair "acme-${cert}" {
|
lib.nameValuePair "acme-${cert}" {
|
||||||
serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' '';
|
serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' '';
|
||||||
}
|
})
|
||||||
) config.security.acme.certs;
|
config.security.acme.certs;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
86
pkgs/_sources_nextcloud/generated.json
Normal file
86
pkgs/_sources_nextcloud/generated.json
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
{
|
||||||
|
"bookmarks": {
|
||||||
|
"cargoLocks": null,
|
||||||
|
"date": null,
|
||||||
|
"extract": null,
|
||||||
|
"name": "bookmarks",
|
||||||
|
"passthru": null,
|
||||||
|
"pinned": false,
|
||||||
|
"src": {
|
||||||
|
"sha256": "sha256-JXNQNnWXoii71QhtKktuEBEIqzmONVetULBhpSjM9xo=",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/nextcloud/bookmarks/releases/download/v13.1.3/bookmarks-13.1.3.tar.gz"
|
||||||
|
},
|
||||||
|
"version": "13.1.3"
|
||||||
|
},
|
||||||
|
"calendar": {
|
||||||
|
"cargoLocks": null,
|
||||||
|
"date": null,
|
||||||
|
"extract": null,
|
||||||
|
"name": "calendar",
|
||||||
|
"passthru": null,
|
||||||
|
"pinned": false,
|
||||||
|
"src": {
|
||||||
|
"sha256": "sha256-hZfjWAMi/0qs5xMMgOlcoSXG6kcZ2aeDaez+NqSZFKI=",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/nextcloud-releases/calendar/releases/download/v4.6.7/calendar-v4.6.7.tar.gz"
|
||||||
|
},
|
||||||
|
"version": "v4.6.7"
|
||||||
|
},
|
||||||
|
"contacts": {
|
||||||
|
"cargoLocks": null,
|
||||||
|
"date": null,
|
||||||
|
"extract": null,
|
||||||
|
"name": "contacts",
|
||||||
|
"passthru": null,
|
||||||
|
"pinned": false,
|
||||||
|
"src": {
|
||||||
|
"sha256": "sha256-HCEjiAqn6sTNXKW6O5X6Ta9Ll4ehvzmGZUj1c0ue2Xc=",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/nextcloud-releases/contacts/releases/download/v5.5.3/contacts-v5.5.3.tar.gz"
|
||||||
|
},
|
||||||
|
"version": "v5.5.3"
|
||||||
|
},
|
||||||
|
"cookbook": {
|
||||||
|
"cargoLocks": null,
|
||||||
|
"date": null,
|
||||||
|
"extract": null,
|
||||||
|
"name": "cookbook",
|
||||||
|
"passthru": null,
|
||||||
|
"pinned": false,
|
||||||
|
"src": {
|
||||||
|
"sha256": "sha256-TE/w8SgyIPaGl5wZUAsG234nxoPj25QoRPF3zjbMoRk=",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/christianlupus-nextcloud/cookbook-releases/releases/download/v0.10.5/Cookbook-0.10.5.tar.gz"
|
||||||
|
},
|
||||||
|
"version": "0.10.5"
|
||||||
|
},
|
||||||
|
"news": {
|
||||||
|
"cargoLocks": null,
|
||||||
|
"date": null,
|
||||||
|
"extract": null,
|
||||||
|
"name": "news",
|
||||||
|
"passthru": null,
|
||||||
|
"pinned": false,
|
||||||
|
"src": {
|
||||||
|
"sha256": "sha256-cfJkKRNSz15L4E3w1tnEb+t4MrVwVzb8lb6vCOA4cK4=",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/nextcloud/news/releases/download/24.0.0/news.tar.gz"
|
||||||
|
},
|
||||||
|
"version": "24.0.0"
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"cargoLocks": null,
|
||||||
|
"date": null,
|
||||||
|
"extract": null,
|
||||||
|
"name": "notes",
|
||||||
|
"passthru": null,
|
||||||
|
"pinned": false,
|
||||||
|
"src": {
|
||||||
|
"sha256": "sha256-ydpxatwuZUz7XIgK8FMklZlxNQklpsP8Uqpxvt3iK0k=",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/nextcloud/notes/releases/download/v4.10.0/notes.tar.gz"
|
||||||
|
},
|
||||||
|
"version": "v4.10.0"
|
||||||
|
}
|
||||||
|
}
|
52
pkgs/_sources_nextcloud/generated.nix
Normal file
52
pkgs/_sources_nextcloud/generated.nix
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# This file was generated by nvfetcher, please do not modify it manually.
|
||||||
|
{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
|
||||||
|
{
|
||||||
|
bookmarks = {
|
||||||
|
pname = "bookmarks";
|
||||||
|
version = "13.1.3";
|
||||||
|
src = fetchTarball {
|
||||||
|
url = "https://github.com/nextcloud/bookmarks/releases/download/v13.1.3/bookmarks-13.1.3.tar.gz";
|
||||||
|
sha256 = "sha256-JXNQNnWXoii71QhtKktuEBEIqzmONVetULBhpSjM9xo=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
calendar = {
|
||||||
|
pname = "calendar";
|
||||||
|
version = "v4.6.7";
|
||||||
|
src = fetchTarball {
|
||||||
|
url = "https://github.com/nextcloud-releases/calendar/releases/download/v4.6.7/calendar-v4.6.7.tar.gz";
|
||||||
|
sha256 = "sha256-hZfjWAMi/0qs5xMMgOlcoSXG6kcZ2aeDaez+NqSZFKI=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
contacts = {
|
||||||
|
pname = "contacts";
|
||||||
|
version = "v5.5.3";
|
||||||
|
src = fetchTarball {
|
||||||
|
url = "https://github.com/nextcloud-releases/contacts/releases/download/v5.5.3/contacts-v5.5.3.tar.gz";
|
||||||
|
sha256 = "sha256-HCEjiAqn6sTNXKW6O5X6Ta9Ll4ehvzmGZUj1c0ue2Xc=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
cookbook = {
|
||||||
|
pname = "cookbook";
|
||||||
|
version = "0.10.5";
|
||||||
|
src = fetchTarball {
|
||||||
|
url = "https://github.com/christianlupus-nextcloud/cookbook-releases/releases/download/v0.10.5/Cookbook-0.10.5.tar.gz";
|
||||||
|
sha256 = "sha256-TE/w8SgyIPaGl5wZUAsG234nxoPj25QoRPF3zjbMoRk=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
news = {
|
||||||
|
pname = "news";
|
||||||
|
version = "24.0.0";
|
||||||
|
src = fetchTarball {
|
||||||
|
url = "https://github.com/nextcloud/news/releases/download/24.0.0/news.tar.gz";
|
||||||
|
sha256 = "sha256-cfJkKRNSz15L4E3w1tnEb+t4MrVwVzb8lb6vCOA4cK4=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
notes = {
|
||||||
|
pname = "notes";
|
||||||
|
version = "v4.10.0";
|
||||||
|
src = fetchTarball {
|
||||||
|
url = "https://github.com/nextcloud/notes/releases/download/v4.10.0/notes.tar.gz";
|
||||||
|
sha256 = "sha256-ydpxatwuZUz7XIgK8FMklZlxNQklpsP8Uqpxvt3iK0k=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
21
pkgs/_sources_pkgs/generated.json
Normal file
21
pkgs/_sources_pkgs/generated.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"prometheus-fail2ban-exporter": {
|
||||||
|
"cargoLocks": null,
|
||||||
|
"date": null,
|
||||||
|
"extract": null,
|
||||||
|
"name": "prometheus-fail2ban-exporter",
|
||||||
|
"passthru": null,
|
||||||
|
"pinned": false,
|
||||||
|
"src": {
|
||||||
|
"deepClone": false,
|
||||||
|
"fetchSubmodules": false,
|
||||||
|
"leaveDotGit": false,
|
||||||
|
"name": null,
|
||||||
|
"rev": "v0.10.1",
|
||||||
|
"sha256": "sha256-zGEhDy3uXIbvx4agSA8Mx7bRtiZZtoDZGbNbHc9L+yI=",
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitlab.com/hectorjsmith/fail2ban-prometheus-exporter"
|
||||||
|
},
|
||||||
|
"version": "v0.10.1"
|
||||||
|
}
|
||||||
|
}
|
16
pkgs/_sources_pkgs/generated.nix
Normal file
16
pkgs/_sources_pkgs/generated.nix
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# This file was generated by nvfetcher, please do not modify it manually.
|
||||||
|
{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
|
||||||
|
{
|
||||||
|
prometheus-fail2ban-exporter = {
|
||||||
|
pname = "prometheus-fail2ban-exporter";
|
||||||
|
version = "v0.10.1";
|
||||||
|
src = fetchgit {
|
||||||
|
url = "https://gitlab.com/hectorjsmith/fail2ban-prometheus-exporter";
|
||||||
|
rev = "v0.10.1";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
sha256 = "sha256-zGEhDy3uXIbvx4agSA8Mx7bRtiZZtoDZGbNbHc9L+yI=";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
1430
pkgs/afvalcalendar/Cargo.lock
generated
Normal file
1430
pkgs/afvalcalendar/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
pkgs/afvalcalendar/Cargo.toml
Normal file
16
pkgs/afvalcalendar/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "afvalcalendar"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
icalendar = "0.16.0"
|
||||||
|
serde = { version = "1.0.195", features = ["derive"] }
|
||||||
|
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
reqwest = { version = "0.11", features = ["cookies", "json"] }
|
||||||
|
chrono = { version = "0.4.34", features = ["serde"] }
|
||||||
|
serde_json = "1.0.114"
|
||||||
|
serde_repr = "0.1.18"
|
||||||
|
hostname = "0.3.1"
|
20
pkgs/afvalcalendar/default.nix
Normal file
20
pkgs/afvalcalendar/default.nix
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
rustPlatform,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
rustPlatform.buildRustPackage {
|
||||||
|
pname = "afvalcalendar";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
openssl
|
||||||
|
];
|
||||||
|
|
||||||
|
cargoHash = "sha256-JXx6aUKdKbUTBCwlBw5i1hZy8ofCfSrhLCwFzqdA8cI=";
|
||||||
|
}
|
43
pkgs/afvalcalendar/src/calendar.rs
Normal file
43
pkgs/afvalcalendar/src/calendar.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use chrono::{Duration, NaiveDate};
|
||||||
|
use icalendar::{Alarm, Calendar, Component, Event, EventLike, Property};
|
||||||
|
|
||||||
|
use crate::trash::TrashType;
|
||||||
|
|
||||||
|
pub(crate) fn calendar_from_pickup_dates(dates: Vec<(TrashType, NaiveDate)>) -> Calendar {
|
||||||
|
let mut ical = Calendar::new();
|
||||||
|
ical.name("Twente Milieu Afvalkalender");
|
||||||
|
|
||||||
|
let events = dates.iter().map(|date| {
|
||||||
|
let description = match date.0 {
|
||||||
|
TrashType::Grey => "Restafval wordt opgehaald",
|
||||||
|
TrashType::Green => "GFT wordt opgehaald",
|
||||||
|
TrashType::Paper => "Papier wordt opgehaald",
|
||||||
|
TrashType::Packages => "Verpakkingen worden opgehaald",
|
||||||
|
};
|
||||||
|
|
||||||
|
let color = Property::new(
|
||||||
|
"COLOR",
|
||||||
|
match date.0 {
|
||||||
|
TrashType::Grey => "darkgray",
|
||||||
|
TrashType::Green => "darkgreen",
|
||||||
|
TrashType::Paper => "royalblue",
|
||||||
|
TrashType::Packages => "darkorange",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let reminder = Alarm::display(description, -Duration::hours(5));
|
||||||
|
|
||||||
|
Event::new()
|
||||||
|
.all_day(date.1)
|
||||||
|
.summary(description)
|
||||||
|
.append_property(color)
|
||||||
|
.alarm(reminder)
|
||||||
|
.done()
|
||||||
|
});
|
||||||
|
|
||||||
|
for event in events {
|
||||||
|
ical.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
ical.done()
|
||||||
|
}
|
15
pkgs/afvalcalendar/src/main.rs
Normal file
15
pkgs/afvalcalendar/src/main.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
mod calendar;
|
||||||
|
mod trash;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
match trash::get_pickup_dates().await {
|
||||||
|
Ok(dates) => {
|
||||||
|
let calendar = calendar::calendar_from_pickup_dates(dates);
|
||||||
|
calendar.print().unwrap();
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("{}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
pkgs/afvalcalendar/src/trash.rs
Normal file
59
pkgs/afvalcalendar/src/trash.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use chrono::{Months, NaiveDate, NaiveDateTime, Utc};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_repr::Deserialize_repr;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct CalendarAPIDatum {
|
||||||
|
pickup_dates: Vec<NaiveDateTime>,
|
||||||
|
pickup_type: TrashType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct CalendarAPIResponse {
|
||||||
|
data_list: Vec<CalendarAPIDatum>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Deserialize_repr, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub(crate) enum TrashType {
|
||||||
|
Grey = 0,
|
||||||
|
Green = 1,
|
||||||
|
Paper = 2,
|
||||||
|
Packages = 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn get_pickup_dates() -> Result<Vec<(TrashType, NaiveDate)>, reqwest::Error> {
|
||||||
|
let today = Utc::now().date_naive();
|
||||||
|
let next_month = (today + Months::new(1)).to_string();
|
||||||
|
let today = today.to_string();
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let params = [
|
||||||
|
("companyCode", "8d97bb56-5afd-4cbc-a651-b4f7314264b4"),
|
||||||
|
("uniqueAddressID", "1300002485"),
|
||||||
|
("startDate", &today),
|
||||||
|
("endDate", &next_month),
|
||||||
|
];
|
||||||
|
|
||||||
|
let calendar = client
|
||||||
|
.post("https://twentemilieuapi.ximmio.com/api/GetCalendar")
|
||||||
|
.form(¶ms)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json::<CalendarAPIResponse>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(calendar
|
||||||
|
.data_list
|
||||||
|
.iter()
|
||||||
|
.flat_map(|datum| {
|
||||||
|
datum
|
||||||
|
.pickup_dates
|
||||||
|
.iter()
|
||||||
|
.map(|date| (datum.pickup_type, NaiveDate::from(*date)))
|
||||||
|
})
|
||||||
|
.collect::<Vec<(TrashType, NaiveDate)>>())
|
||||||
|
}
|
4
pkgs/afvalcalendar/test.rest
Normal file
4
pkgs/afvalcalendar/test.rest
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
POST https://twentemilieuapi.ximmio.com/api/GetCalendar
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
|
||||||
|
companyCode=8d97bb56-5afd-4cbc-a651-b4f7314264b4&uniqueAddressID=1300002485&startDate=2024-02-01&endDate=2024-02-29
|
|
@ -1,42 +0,0 @@
|
||||||
{
|
|
||||||
"crowdsec-firewall-bouncer": {
|
|
||||||
"cargoLocks": null,
|
|
||||||
"date": null,
|
|
||||||
"extract": null,
|
|
||||||
"name": "crowdsec-firewall-bouncer",
|
|
||||||
"passthru": null,
|
|
||||||
"pinned": false,
|
|
||||||
"src": {
|
|
||||||
"deepClone": false,
|
|
||||||
"fetchSubmodules": false,
|
|
||||||
"leaveDotGit": false,
|
|
||||||
"name": null,
|
|
||||||
"owner": "crowdsecurity",
|
|
||||||
"repo": "cs-firewall-bouncer",
|
|
||||||
"rev": "v0.0.31",
|
|
||||||
"sha256": "sha256-59MWll8v00CF4WA53gjHZSTFc8hpYaHENg9O7LgTCrA=",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"version": "v0.0.31"
|
|
||||||
},
|
|
||||||
"crowdsec-hub": {
|
|
||||||
"cargoLocks": null,
|
|
||||||
"date": "2025-05-17",
|
|
||||||
"extract": null,
|
|
||||||
"name": "crowdsec-hub",
|
|
||||||
"passthru": null,
|
|
||||||
"pinned": false,
|
|
||||||
"src": {
|
|
||||||
"deepClone": false,
|
|
||||||
"fetchSubmodules": false,
|
|
||||||
"leaveDotGit": false,
|
|
||||||
"name": null,
|
|
||||||
"owner": "crowdsecurity",
|
|
||||||
"repo": "hub",
|
|
||||||
"rev": "850614b9fcd4298f559b422c5ac685a69aa2e5ff",
|
|
||||||
"sha256": "sha256-96MMwFN5KongQA3YJVSuk7Kanbr1gR94CCyiflmez2k=",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"version": "850614b9fcd4298f559b422c5ac685a69aa2e5ff"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
# This file was generated by nvfetcher, please do not modify it manually.
|
|
||||||
{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
|
|
||||||
{
|
|
||||||
crowdsec-firewall-bouncer = {
|
|
||||||
pname = "crowdsec-firewall-bouncer";
|
|
||||||
version = "v0.0.31";
|
|
||||||
src = fetchFromGitHub {
|
|
||||||
owner = "crowdsecurity";
|
|
||||||
repo = "cs-firewall-bouncer";
|
|
||||||
rev = "v0.0.31";
|
|
||||||
fetchSubmodules = false;
|
|
||||||
sha256 = "sha256-59MWll8v00CF4WA53gjHZSTFc8hpYaHENg9O7LgTCrA=";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
crowdsec-hub = {
|
|
||||||
pname = "crowdsec-hub";
|
|
||||||
version = "850614b9fcd4298f559b422c5ac685a69aa2e5ff";
|
|
||||||
src = fetchFromGitHub {
|
|
||||||
owner = "crowdsecurity";
|
|
||||||
repo = "hub";
|
|
||||||
rev = "850614b9fcd4298f559b422c5ac685a69aa2e5ff";
|
|
||||||
fetchSubmodules = false;
|
|
||||||
sha256 = "sha256-96MMwFN5KongQA3YJVSuk7Kanbr1gR94CCyiflmez2k=";
|
|
||||||
};
|
|
||||||
date = "2025-05-17";
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{ pkgs }:
|
|
||||||
let
|
|
||||||
sources = pkgs.callPackage ./_sources/generated.nix { };
|
|
||||||
callPackage = pkgs.lib.callPackageWith (pkgs // { inherit sources; });
|
|
||||||
in
|
|
||||||
{
|
|
||||||
hub = callPackage ./hub.nix { };
|
|
||||||
firewall-bouncer = callPackage ./firewall-bouncer.nix { };
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
lib,
|
|
||||||
sources,
|
|
||||||
buildGoModule,
|
|
||||||
envsubst,
|
|
||||||
coreutils,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
envsubstBin = lib.getExe envsubst;
|
|
||||||
in
|
|
||||||
buildGoModule {
|
|
||||||
inherit (sources.crowdsec-firewall-bouncer) pname version src;
|
|
||||||
|
|
||||||
vendorHash = "sha256-7Jxvg8UEjUxnIz1llvXyI2AefJ31OVdNzhWD/C8wU/Y=";
|
|
||||||
|
|
||||||
postInstall = ''
|
|
||||||
mkdir -p $out/lib/systemd/system
|
|
||||||
|
|
||||||
CFG=/var/lib/crowdsec/config BIN=$out/bin/cs-firewall-bouncer ${envsubstBin} \
|
|
||||||
-i ./config/crowdsec-firewall-bouncer.service \
|
|
||||||
-o $out/lib/systemd/system/crowdsec-firewall-bouncer.service
|
|
||||||
|
|
||||||
substituteInPlace $out/lib/systemd/system/crowdsec-firewall-bouncer.service \
|
|
||||||
--replace-fail /bin/sleep ${coreutils}/bin/sleep
|
|
||||||
'';
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
{ sources }: sources.crowdsec-hub.src
|
|
|
@ -1,7 +0,0 @@
|
||||||
[crowdsec-hub]
|
|
||||||
src.git = "https://github.com/crowdsecurity/hub.git"
|
|
||||||
fetch.github = "crowdsecurity/hub"
|
|
||||||
|
|
||||||
[crowdsec-firewall-bouncer]
|
|
||||||
src.github = "crowdsecurity/cs-firewall-bouncer"
|
|
||||||
fetch.github = "crowdsecurity/cs-firewall-bouncer"
|
|
|
@ -1,5 +1,22 @@
|
||||||
{ pkgs }:
|
|
||||||
{
|
{
|
||||||
crowdsec = import ./crowdsec { inherit pkgs; };
|
pkgs,
|
||||||
starbound = pkgs.callPackage ./starbound { };
|
lib,
|
||||||
}
|
}: let
|
||||||
|
inherit (builtins) fromJSON mapAttrs readFile;
|
||||||
|
inherit (pkgs) callPackage;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
starbound = callPackage ./starbound {};
|
||||||
|
prometheus-fail2ban-exporter = callPackage ./prometheus/fail2ban-exporter.nix {
|
||||||
|
sources = pkgs.callPackage ./_sources_pkgs/generated.nix {};
|
||||||
|
};
|
||||||
|
afvalcalendar = callPackage ./afvalcalendar {};
|
||||||
|
}
|
||||||
|
// (
|
||||||
|
# Add nextcloud apps
|
||||||
|
let
|
||||||
|
mkNextcloudApp = pkgs.callPackage ./mkNextcloudApp.nix {};
|
||||||
|
sources = fromJSON (readFile ./_sources_nextcloud/generated.json);
|
||||||
|
in
|
||||||
|
mapAttrs (_: source: mkNextcloudApp source) sources
|
||||||
|
)
|
||||||
|
|
9
pkgs/mkNextcloudApp.nix
Normal file
9
pkgs/mkNextcloudApp.nix
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
fetchNextcloudApp,
|
||||||
|
lib,
|
||||||
|
}: source:
|
||||||
|
fetchNextcloudApp {
|
||||||
|
url = source.src.url;
|
||||||
|
sha256 = source.src.sha256;
|
||||||
|
license = "unlicense"; # Blatant lie
|
||||||
|
}
|
31
pkgs/nextcloud-apps.toml
Normal file
31
pkgs/nextcloud-apps.toml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[bookmarks]
|
||||||
|
# src.github = "nextcloud/bookmarks"
|
||||||
|
src.prefix = "v"
|
||||||
|
src.manual = "v13.1.3"
|
||||||
|
fetch.tarball = "https://github.com/nextcloud/bookmarks/releases/download/v$ver/bookmarks-$ver.tar.gz"
|
||||||
|
|
||||||
|
[calendar]
|
||||||
|
# src.github = "nextcloud-releases/calendar"
|
||||||
|
src.manual = "v4.6.7"
|
||||||
|
fetch.tarball = "https://github.com/nextcloud-releases/calendar/releases/download/$ver/calendar-$ver.tar.gz"
|
||||||
|
|
||||||
|
[contacts]
|
||||||
|
# src.github = "nextcloud-releases/contacts"
|
||||||
|
src.manual = "v5.5.3"
|
||||||
|
fetch.tarball = "https://github.com/nextcloud-releases/contacts/releases/download/$ver/contacts-$ver.tar.gz"
|
||||||
|
|
||||||
|
[cookbook]
|
||||||
|
# src.github = "christianlupus-nextcloud/cookbook-releases"
|
||||||
|
src.prefix = "v"
|
||||||
|
src.manual = "0.10.5"
|
||||||
|
fetch.tarball = "https://github.com/christianlupus-nextcloud/cookbook-releases/releases/download/v$ver/Cookbook-$ver.tar.gz"
|
||||||
|
|
||||||
|
[news]
|
||||||
|
# src.github = "nextcloud/news"
|
||||||
|
# Update to 25 when angular rewrite is done/the alpha when I need to switch to nextcloud 28+
|
||||||
|
src.manual = "24.0.0"
|
||||||
|
fetch.tarball = "https://github.com/nextcloud/news/releases/download/$ver/news.tar.gz"
|
||||||
|
|
||||||
|
[notes]
|
||||||
|
src.github = "nextcloud/notes"
|
||||||
|
fetch.tarball = "https://github.com/nextcloud/notes/releases/download/$ver/notes.tar.gz"
|
3
pkgs/nvfetcher.toml
Normal file
3
pkgs/nvfetcher.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[prometheus-fail2ban-exporter]
|
||||||
|
src.manual = "v0.10.1" # No gitlab support in nvfetcher
|
||||||
|
fetch.git = "https://gitlab.com/hectorjsmith/fail2ban-prometheus-exporter"
|
8
pkgs/prometheus/fail2ban-exporter.nix
Normal file
8
pkgs/prometheus/fail2ban-exporter.nix
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
buildGoModule,
|
||||||
|
sources,
|
||||||
|
}:
|
||||||
|
buildGoModule {
|
||||||
|
inherit (sources.prometheus-fail2ban-exporter) pname src version;
|
||||||
|
vendorHash = "sha256-5o8p5p0U/c0WAIV5dACnWA3ThzSh2tt5LIFMb59i9GY=";
|
||||||
|
}
|
|
@ -5,33 +5,30 @@
|
||||||
patchelf,
|
patchelf,
|
||||||
steamPackages,
|
steamPackages,
|
||||||
replace-secret,
|
replace-secret,
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
# Use the directory in which starbound is installed so steamcmd
|
# Use the directory in which starbound is installed so steamcmd
|
||||||
# doesn't have to be reinstalled constantly (we're using DynamicUser
|
# doesn't have to be reinstalled constantly (we're using DynamicUser
|
||||||
# with StateDirectory to persist this).
|
# with StateDirectory to persist this).
|
||||||
steamcmd = steamPackages.steamcmd.override { steamRoot = "/var/lib/starbound/.steamcmd"; };
|
steamcmd = steamPackages.steamcmd.override {
|
||||||
wrapperPath = lib.makeBinPath [
|
steamRoot = "/var/lib/starbound/.steamcmd";
|
||||||
patchelf
|
};
|
||||||
steamcmd
|
wrapperPath = lib.makeBinPath [patchelf steamcmd replace-secret];
|
||||||
replace-secret
|
|
||||||
];
|
|
||||||
in
|
in
|
||||||
stdenv.mkDerivation {
|
stdenv.mkDerivation {
|
||||||
name = "starbound-update-script";
|
name = "starbound-update-script";
|
||||||
nativeBuildInputs = [ makeWrapper ];
|
nativeBuildInputs = [makeWrapper];
|
||||||
dontUnpack = true;
|
dontUnpack = true;
|
||||||
patchPhase = ''
|
patchPhase = ''
|
||||||
interpreter="$(cat $NIX_CC/nix-support/dynamic-linker)"
|
interpreter="$(cat $NIX_CC/nix-support/dynamic-linker)"
|
||||||
substitute ${./launch-starbound.sh} launch-starbound --subst-var interpreter
|
substitute ${./launch-starbound.sh} launch-starbound --subst-var interpreter
|
||||||
'';
|
'';
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cp launch-starbound $out/bin/launch-starbound
|
cp launch-starbound $out/bin/launch-starbound
|
||||||
chmod +x $out/bin/launch-starbound
|
chmod +x $out/bin/launch-starbound
|
||||||
'';
|
'';
|
||||||
postFixup = ''
|
postFixup = ''
|
||||||
wrapProgram $out/bin/launch-starbound \
|
wrapProgram $out/bin/launch-starbound \
|
||||||
--prefix PATH : "${wrapperPath}"
|
--prefix PATH : "${wrapperPath}"
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue