Compare commits

..

No commits in common. "64b13ad9f260e88b7f6bbf401a570fda37f98c56" and "501c3466bc0d44fa49c07284765463f28ff5b4f3" have entirely different histories.

30 changed files with 766 additions and 816 deletions

View file

@ -1,5 +0,0 @@
# Run this command to always ignore formatting commits in `git blame`
# git config blame.ignoreRevsFile .git-blame-ignore-revs
# Switch to nixpkgs-fmt formatting
fd138d45e6a2cad89fead6e9f246ba282070d6b7

View file

@ -1,9 +1,10 @@
{ config {
, pkgs config,
, lib pkgs,
, modulesPath lib,
, flake-inputs modulesPath,
, ... flake-inputs,
...
}: { }: {
imports = [ imports = [
flake-inputs.disko.nixosModules.disko flake-inputs.disko.nixosModules.disko
@ -46,15 +47,15 @@
''; '';
# Enable remote builds from tlater # Enable remote builds from tlater
settings.trusted-users = [ "@wheel" ]; settings.trusted-users = ["@wheel"];
}; };
nixpkgs.config.allowUnfreePredicate = pkg: nixpkgs.config.allowUnfreePredicate = pkg:
builtins.elem (lib.getName pkg) [ "steam-original" "steam-runtime" "steam-run" "steamcmd" ]; 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 = [ "highres=off" "nohz=off" ]; boot.kernelParams = ["highres=off" "nohz=off"];
networking = { networking = {
usePredictableInterfaceNames = false; usePredictableInterfaceNames = false;
@ -105,15 +106,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 = {
@ -132,14 +133,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";
} }

View file

@ -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";

View file

@ -1,6 +1,5 @@
{ {
disko.devices.disk = disko.devices.disk = let
let
bootPartition = { bootPartition = {
size = "1M"; size = "1M";
type = "EF02"; type = "EF02";
@ -19,9 +18,8 @@
}; };
}; };
mountOptions = [ "compress=zstd" "noatime" ]; mountOptions = ["compress=zstd" "noatime"];
in in {
{
sda = { sda = {
type = "disk"; type = "disk";
device = "/dev/sda"; device = "/dev/sda";
@ -54,9 +52,9 @@
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 = [ "-d" "raid1" "-m" "raid1" "--runtime-features" "quota" "/dev/sda3" ]; extraArgs = ["-d" "raid1" "-m" "raid1" "--runtime-features" "quota" "/dev/sda3"];
subvolumes = { subvolumes = {
"/volume" = { }; "/volume" = {};
"/volume/root" = { "/volume/root" = {
inherit mountOptions; inherit mountOptions;
mountpoint = "/"; mountpoint = "/";
@ -73,7 +71,7 @@
inherit mountOptions; inherit mountOptions;
mountpoint = "/nix"; mountpoint = "/nix";
}; };
"/snapshots" = { }; "/snapshots" = {};
}; };
}; };
}; };

View file

@ -1,8 +1,8 @@
{ 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 # Sets the base domain for nginx to a local domain so that we can

View file

@ -1,6 +1,7 @@
{ config {
, lib config,
, ... lib,
...
}: { }: {
services.nginx = { services.nginx = {
enable = true; enable = true;
@ -26,8 +27,7 @@
# Override the default, just keep fewer logs # Override the default, just keep fewer logs
nginx.rotate = 6; nginx.rotate = 6;
} }
// lib.mapAttrs' // lib.mapAttrs' (virtualHost: _:
(virtualHost: _:
lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" { lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" {
frequency = "daily"; frequency = "daily";
rotate = 2; rotate = 2;
@ -39,8 +39,7 @@
config.services.nginx.virtualHosts; config.services.nginx.virtualHosts;
systemd.tmpfiles.rules = systemd.tmpfiles.rules =
lib.mapAttrsToList lib.mapAttrsToList (
(
virtualHost: _: virtualHost: _:
# #
"d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}" "d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}"
@ -52,7 +51,7 @@
acceptTerms = true; acceptTerms = true;
certs."tlater.net" = { certs."tlater.net" = {
extraDomainNames = [ "*.tlater.net" ]; extraDomainNames = ["*.tlater.net"];
dnsProvider = "hetzner"; dnsProvider = "hetzner";
group = "nginx"; group = "nginx";
credentialFiles."HETZNER_API_KEY_FILE" = config.sops.secrets."hetzner-api".path; credentialFiles."HETZNER_API_KEY_FILE" = config.sops.secrets."hetzner-api".path;

View file

@ -1,11 +1,12 @@
{ pkgs {
, config pkgs,
, ... config,
...
}: { }: {
systemd.services.afvalcalendar = { systemd.services.afvalcalendar = {
description = "Enschede afvalcalendar -> ical converter"; description = "Enschede afvalcalendar -> ical converter";
wantedBy = [ "multi-user.target" ]; wantedBy = ["multi-user.target"];
after = [ "network.target" ]; after = ["network.target"];
script = '' script = ''
${pkgs.local.afvalcalendar}/bin/afvalcalendar > /srv/afvalcalendar/afvalcalendar.ical ${pkgs.local.afvalcalendar}/bin/afvalcalendar > /srv/afvalcalendar/afvalcalendar.ical
@ -25,14 +26,14 @@
ProtectKernelModules = true; ProtectKernelModules = true;
ProtectKernelLogs = true; ProtectKernelLogs = true;
ProtectControlGroups = true; ProtectControlGroups = true;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
RestrictNamespaces = true; RestrictNamespaces = true;
LockPersonality = true; LockPersonality = true;
MemoryDenyWriteExecute = true; MemoryDenyWriteExecute = true;
RestrictRealtime = true; RestrictRealtime = true;
RestrictSUIDSGID = true; RestrictSUIDSGID = true;
SystemCallArchitectures = "native"; SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ]; SystemCallFilter = ["@system-service" "~@privileged @resources @setuid @keyring"];
Umask = 0002; Umask = 0002;
SupplementaryGroups = "afvalcalendar-hosting"; SupplementaryGroups = "afvalcalendar-hosting";
@ -49,7 +50,7 @@
root = "/srv/afvalcalendar"; root = "/srv/afvalcalendar";
}; };
users.groups.afvalcalendar-hosting = { }; users.groups.afvalcalendar-hosting = {};
systemd.tmpfiles.settings."10-afvalcalendar" = { systemd.tmpfiles.settings."10-afvalcalendar" = {
"/srv/afvalcalendar".d = { "/srv/afvalcalendar".d = {
user = "nginx"; user = "nginx";

View file

@ -1,9 +1,9 @@
{ config {
, pkgs config,
, lib pkgs,
, ... lib,
}: ...
let }: let
inherit (lib) types optional singleton; inherit (lib) types optional singleton;
mkShutdownScript = service: mkShutdownScript = service:
pkgs.writeShellScript "backup-${service}-shutdown" '' pkgs.writeShellScript "backup-${service}-shutdown" ''
@ -42,16 +42,16 @@ 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 (types.submodule ({ config type = types.attrsOf (types.submodule ({
, name config,
, ... name,
...
}: { }: {
options = { options = {
user = lib.mkOption { user = lib.mkOption {
@ -76,7 +76,7 @@ in
preparation = { preparation = {
packages = lib.mkOption { packages = lib.mkOption {
type = types.listOf types.package; type = types.listOf types.package;
default = [ ]; default = [];
description = '' description = ''
The list of packages to make available in the The list of packages to make available in the
preparation script. preparation script.
@ -97,7 +97,7 @@ in
cleanup = { cleanup = {
packages = lib.mkOption { packages = lib.mkOption {
type = types.listOf types.package; type = types.listOf types.package;
default = [ ]; default = [];
description = '' description = ''
The list of packages to make available in the The list of packages to make available in the
cleanup script. cleanup script.
@ -116,7 +116,7 @@ in
}; };
pauseServices = lib.mkOption { pauseServices = lib.mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [];
description = '' description = ''
The systemd services that need to be shut down before The systemd services that need to be shut down before
the backup can run. Services will be restarted after the the backup can run. Services will be restarted after the
@ -131,7 +131,7 @@ in
}; };
}; };
config = lib.mkIf (config.services.backups != { }) { config = lib.mkIf (config.services.backups != {}) {
systemd.services = systemd.services =
{ {
restic-prune = { restic-prune = {
@ -164,8 +164,7 @@ 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;
@ -197,12 +196,12 @@ in
PrivateTmp = true; PrivateTmp = true;
ExecStart = [ ExecStart = [
(lib.concatStringsSep " " ([ "${pkgs.restic}/bin/restic" "backup" "--tag" name ] ++ backup.paths)) (lib.concatStringsSep " " (["${pkgs.restic}/bin/restic" "backup" "--tag" name] ++ backup.paths))
]; ];
ExecStartPre = ExecStartPre =
map (service: "+${mkShutdownScript service}") backup.pauseServices map (service: "+${mkShutdownScript service}") backup.pauseServices
++ singleton (writeScript "backup-${name}-repo-init" [ ] '' ++ singleton (writeScript "backup-${name}-repo-init" [] ''
restic snapshots || restic init restic snapshots || restic init
'') '')
++ optional (backup.preparation.text != null) ++ optional (backup.preparation.text != null)
@ -220,17 +219,16 @@ in
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: backup:
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";
@ -247,7 +245,7 @@ in
group = "backup"; group = "backup";
isSystemUser = true; isSystemUser = true;
}; };
groups.backup = { }; groups.backup = {};
}; };
}; };
} }

View file

@ -1,6 +1,7 @@
{ config {
, flake-inputs config,
, ... flake-inputs,
...
}: { }: {
imports = [ imports = [
flake-inputs.sonnenshift.nixosModules.default flake-inputs.sonnenshift.nixosModules.default

View file

@ -1,16 +1,15 @@
{ pkgs {
, config pkgs,
, lib config,
, ... lib,
}: ...
let }: let
inherit (lib.strings) concatMapStringsSep; inherit (lib.strings) concatMapStringsSep;
cfg = config.services.matrix-conduit; cfg = config.services.matrix-conduit;
domain = "matrix.${config.services.nginx.domain}"; domain = "matrix.${config.services.nginx.domain}";
turn-realm = "turn.${config.services.nginx.domain}"; turn-realm = "turn.${config.services.nginx.domain}";
in in {
{
services.matrix-conduit = { services.matrix-conduit = {
enable = true; enable = true;
settings.global = { settings.global = {
@ -18,12 +17,10 @@ in
server_name = domain; server_name = domain;
database_backend = "rocksdb"; database_backend = "rocksdb";
turn_uris = turn_uris = let
let
address = "${config.services.coturn.realm}:${toString config.services.coturn.listening-port}"; address = "${config.services.coturn.realm}:${toString config.services.coturn.listening-port}";
tls-address = "${config.services.coturn.realm}:${toString config.services.coturn.tls-listening-port}"; tls-address = "${config.services.coturn.realm}:${toString config.services.coturn.tls-listening-port}";
in in [
[
"turn:${address}?transport=udp" "turn:${address}?transport=udp"
"turn:${address}?transport=tcp" "turn:${address}?transport=tcp"
"turns:${tls-address}?transport=udp" "turns:${tls-address}?transport=udp"
@ -32,8 +29,7 @@ in
}; };
}; };
systemd.services.heisenbridge = systemd.services.heisenbridge = let
let
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret"; replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
registrationFile = builtins.toFile "heisenbridge-registration.yaml" (builtins.toJSON { registrationFile = builtins.toFile "heisenbridge-registration.yaml" (builtins.toJSON {
id = "heisenbridge"; id = "heisenbridge";
@ -53,8 +49,8 @@ in
exclusive = true; exclusive = true;
} }
]; ];
aliases = [ ]; aliases = [];
rooms = [ ]; rooms = [];
}; };
}); });
@ -77,11 +73,10 @@ in
--owner @tlater:matrix.tlater.net \ --owner @tlater:matrix.tlater.net \
'http://localhost:${toString cfg.settings.global.port}' 'http://localhost:${toString cfg.settings.global.port}'
''; '';
in in {
{
description = "Matrix<->IRC bridge"; description = "Matrix<->IRC bridge";
wantedBy = [ "multi-user.target" ]; wantedBy = ["multi-user.target"];
after = [ "conduit.service" ]; after = ["conduit.service"];
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
@ -102,7 +97,7 @@ in
ProtectKernelModules = true; ProtectKernelModules = true;
ProtectKernelLogs = true; ProtectKernelLogs = true;
ProtectControlGroups = true; ProtectControlGroups = true;
RestrictAddressFamilies = [ "AF_INET AF_INET6" ]; RestrictAddressFamilies = ["AF_INET AF_INET6"];
LockPersonality = true; LockPersonality = true;
RestrictRealtime = true; RestrictRealtime = true;
ProtectProc = "invisible"; ProtectProc = "invisible";
@ -254,6 +249,6 @@ in
]; ];
# Other services store their data in conduit, so no other services # Other services store their data in conduit, so no other services
# need to be shut down currently. # need to be shut down currently.
pauseServices = [ "conduit.service" ]; pauseServices = ["conduit.service"];
}; };
} }

View file

@ -1,7 +1,7 @@
{ pkgs, ... }: { {pkgs, ...}: {
services.fail2ban = { services.fail2ban = {
enable = true; enable = true;
extraPackages = [ pkgs.ipset ]; extraPackages = [pkgs.ipset];
banaction = "iptables-ipset-proto6-allports"; banaction = "iptables-ipset-proto6-allports";
bantime-increment.enable = true; bantime-increment.enable = true;
@ -21,7 +21,7 @@
}; };
# Allow metrics services to connect to the socket as well # Allow metrics services to connect to the socket as well
users.groups.fail2ban = { }; users.groups.fail2ban = {};
systemd.services.fail2ban.serviceConfig = { systemd.services.fail2ban.serviceConfig = {
ExecStartPost = ExecStartPost =
"+" "+"

View file

@ -1,13 +1,12 @@
{ lib
, config
, flake-inputs
, ...
}:
let
domain = "foundryvtt.${config.services.nginx.domain}";
in
{ {
imports = [ flake-inputs.foundryvtt.nixosModules.foundryvtt ]; lib,
config,
flake-inputs,
...
}: let
domain = "foundryvtt.${config.services.nginx.domain}";
in {
imports = [flake-inputs.foundryvtt.nixosModules.foundryvtt];
services.foundryvtt = { services.foundryvtt = {
enable = true; enable = true;
@ -19,13 +18,11 @@ in
# 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}" = services.nginx.virtualHosts."${domain}" = let
let
inherit (config.services.foundryvtt) port; inherit (config.services.foundryvtt) port;
in in {
{
forceSSL = true; forceSSL = true;
useACMEHost = "tlater.net"; useACMEHost = "tlater.net";
enableHSTS = true; enableHSTS = true;
@ -41,6 +38,6 @@ in
paths = [ paths = [
config.services.foundryvtt.dataDir config.services.foundryvtt.dataDir
]; ];
pauseServices = [ "foundryvtt.service" ]; pauseServices = ["foundryvtt.service"];
}; };
} }

View file

@ -1,12 +1,11 @@
{ pkgs
, config
, lib
, ...
}:
let
domain = "gitea.${config.services.nginx.domain}";
in
{ {
pkgs,
config,
lib,
...
}: let
domain = "gitea.${config.services.nginx.domain}";
in {
services.forgejo = { services.forgejo = {
enable = true; enable = true;
database.type = "postgres"; database.type = "postgres";
@ -28,23 +27,19 @@ in
}; };
}; };
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 # Set up SSL
services.nginx.virtualHosts."${domain}" = services.nginx.virtualHosts."${domain}" = let
let
httpAddress = config.services.forgejo.settings.server.HTTP_ADDR; httpAddress = config.services.forgejo.settings.server.HTTP_ADDR;
httpPort = config.services.forgejo.settings.server.HTTP_PORT; httpPort = config.services.forgejo.settings.server.HTTP_PORT;
in in {
{
forceSSL = true; forceSSL = true;
useACMEHost = "tlater.net"; useACMEHost = "tlater.net";
enableHSTS = true; enableHSTS = true;
@ -88,13 +83,13 @@ in
# Conf is backed up via nix # Conf is backed up via nix
]; ];
preparation = { preparation = {
packages = [ config.services.postgresql.package ]; packages = [config.services.postgresql.package];
text = "pg_dump ${config.services.forgejo.database.name} --file=/var/lib/forgejo/forgejo-db.sql"; text = "pg_dump ${config.services.forgejo.database.name} --file=/var/lib/forgejo/forgejo-db.sql";
}; };
cleanup = { cleanup = {
packages = [ pkgs.coreutils ]; packages = [pkgs.coreutils];
text = "rm /var/lib/forgejo/forgejo-db.sql"; text = "rm /var/lib/forgejo/forgejo-db.sql";
}; };
pauseServices = [ "forgejo.service" ]; pauseServices = ["forgejo.service"];
}; };
} }

View file

@ -1,26 +1,23 @@
{ config
, pkgs
, lib
, ...
}:
let
yaml = pkgs.formats.yaml { };
in
{ {
config,
pkgs,
lib,
...
}: let
yaml = pkgs.formats.yaml {};
in {
services.prometheus = { services.prometheus = {
exporters = { exporters = {
# 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}"
]; ];
}; };
@ -52,8 +49,7 @@ in
group = "nginx"; group = "nginx";
settings.namespaces = settings.namespaces =
lib.mapAttrsToList lib.mapAttrsToList (name: virtualHost: {
(name: virtualHost: {
inherit name; inherit name;
metrics_override.prefix = "nginxlog"; metrics_override.prefix = "nginxlog";
namespace_label = "vhost"; namespace_label = "vhost";
@ -75,18 +71,16 @@ in
}; };
extraExporters = { extraExporters = {
fail2ban = fail2ban = let
let
cfg = config.services.prometheus.extraExporters.fail2ban; cfg = config.services.prometheus.extraExporters.fail2ban;
in in {
{
port = 9191; port = 9191;
serviceOpts = { serviceOpts = {
after = [ "fail2ban.service" ]; after = ["fail2ban.service"];
requires = [ "fail2ban.service" ]; requires = ["fail2ban.service"];
serviceConfig = { serviceConfig = {
Group = "fail2ban"; Group = "fail2ban";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
ExecStart = lib.concatStringsSep " " [ ExecStart = lib.concatStringsSep " " [
"${pkgs.local.prometheus-fail2ban-exporter}/bin/fail2ban-prometheus-exporter" "${pkgs.local.prometheus-fail2ban-exporter}/bin/fail2ban-prometheus-exporter"
"--collector.f2b.socket=/var/run/fail2ban/fail2ban.sock" "--collector.f2b.socket=/var/run/fail2ban/fail2ban.sock"

View file

@ -1,8 +1,6 @@
{ 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 = {

View file

@ -1,13 +1,12 @@
{ pkgs
, config
, lib
, ...
}:
let
inherit (lib) types mkOption mkDefault;
yaml = pkgs.formats.yaml { };
in
{ {
pkgs,
config,
lib,
...
}: let
inherit (lib) types mkOption mkDefault;
yaml = pkgs.formats.yaml {};
in {
options = { options = {
services.prometheus = { services.prometheus = {
extraExporters = mkOption { extraExporters = mkOption {
@ -32,9 +31,10 @@ in
}; };
services.victoriametrics.scrapeConfigs = mkOption { services.victoriametrics.scrapeConfigs = mkOption {
type = types.attrsOf (types.submodule ({ name type = types.attrsOf (types.submodule ({
, self name,
, ... self,
...
}: { }: {
options = { options = {
job_name = mkOption { job_name = mkOption {
@ -47,7 +47,7 @@ in
description = '' description = ''
Other settings to set for this scrape config. Other settings to set for this scrape config.
''; '';
default = { }; default = {};
}; };
targets = mkOption { targets = mkOption {
@ -57,11 +57,11 @@ in
Shortcut for `static_configs = lib.singleton {targets = [<targets>];}` Shortcut for `static_configs = lib.singleton {targets = [<targets>];}`
''; '';
default = [ ]; default = [];
}; };
static_configs = mkOption { static_configs = mkOption {
default = [ ]; default = [];
type = types.listOf (types.submodule { type = types.listOf (types.submodule {
options = { options = {
targets = mkOption { targets = mkOption {
@ -77,7 +77,7 @@ in
description = lib.mdDoc '' description = lib.mdDoc ''
Labels to apply to all targets defined for this static config. Labels to apply to all targets defined for this static config.
''; '';
default = { }; default = {};
}; };
}; };
}); });
@ -89,21 +89,20 @@ in
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 # Shamelessly copied from upstream because the upstream
# module is an intractable mess # module is an intractable mess
wantedBy = [ "multi-user.target" ]; wantedBy = ["multi-user.target"];
after = [ "network.target" ]; after = ["network.target"];
serviceConfig.Restart = mkDefault "always"; serviceConfig.Restart = mkDefault "always";
serviceConfig.PrivateTmp = mkDefault true; serviceConfig.PrivateTmp = mkDefault true;
serviceConfig.WorkingDirectory = mkDefault /tmp; serviceConfig.WorkingDirectory = mkDefault /tmp;
serviceConfig.DynamicUser = mkDefault true; serviceConfig.DynamicUser = mkDefault true;
# Hardening # Hardening
serviceConfig.CapabilityBoundingSet = mkDefault [ "" ]; serviceConfig.CapabilityBoundingSet = mkDefault [""];
serviceConfig.DeviceAllow = [ "" ]; serviceConfig.DeviceAllow = [""];
serviceConfig.LockPersonality = true; serviceConfig.LockPersonality = true;
serviceConfig.MemoryDenyWriteExecute = true; serviceConfig.MemoryDenyWriteExecute = true;
serviceConfig.NoNewPrivileges = true; serviceConfig.NoNewPrivileges = true;
@ -117,7 +116,7 @@ in
serviceConfig.ProtectKernelTunables = true; serviceConfig.ProtectKernelTunables = true;
serviceConfig.ProtectSystem = mkDefault "strict"; serviceConfig.ProtectSystem = mkDefault "strict";
serviceConfig.RemoveIPC = true; serviceConfig.RemoveIPC = true;
serviceConfig.RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; serviceConfig.RestrictAddressFamilies = ["AF_INET" "AF_INET6"];
serviceConfig.RestrictNamespaces = true; serviceConfig.RestrictNamespaces = true;
serviceConfig.RestrictRealtime = true; serviceConfig.RestrictRealtime = true;
serviceConfig.RestrictSUIDSGID = true; serviceConfig.RestrictSUIDSGID = true;
@ -129,29 +128,25 @@ in
config.services.prometheus.extraExporters) config.services.prometheus.extraExporters)
{ {
vmagent-scrape-exporters = vmagent-scrape-exporters = let
let
listenAddress = config.services.victoriametrics.listenAddress; listenAddress = 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_configs = lib.mapAttrsToList (_: scrape:
(_: scrape: lib.recursiveUpdate {
lib.recursiveUpdate
{
inherit (scrape) job_name; inherit (scrape) job_name;
static_configs = static_configs =
scrape.static_configs scrape.static_configs
++ lib.optional (scrape.targets != [ ]) { targets = scrape.targets; }; ++ lib.optional (scrape.targets != []) {targets = scrape.targets;};
} }
scrape.extraSettings) scrape.extraSettings)
config.services.victoriametrics.scrapeConfigs; config.services.victoriametrics.scrapeConfigs;
}; };
in in {
{
enable = true; enable = true;
path = [ pkgs.victoriametrics ]; path = [pkgs.victoriametrics];
wantedBy = [ "multi-user.target" ]; wantedBy = ["multi-user.target"];
after = [ "network.target" "victoriametrics.service" ]; after = ["network.target" "victoriametrics.service"];
serviceConfig = { serviceConfig = {
ExecStart = [ ExecStart = [
(lib.concatStringsSep " " [ (lib.concatStringsSep " " [
@ -165,8 +160,8 @@ in
DynamicUser = true; DynamicUser = true;
RuntimeDirectory = "vmagent"; RuntimeDirectory = "vmagent";
CapabilityBoundingSet = [ "" ]; CapabilityBoundingSet = [""];
DeviceAllow = [ "" ]; DeviceAllow = [""];
LockPersonality = true; LockPersonality = true;
MemoryDenyWriteExecute = true; MemoryDenyWriteExecute = true;
NoNewPrivileges = true; NoNewPrivileges = true;
@ -180,7 +175,7 @@ in
ProtectKernelTunables = true; ProtectKernelTunables = true;
ProtectSystem = "strict"; ProtectSystem = "strict";
RemoveIPC = true; RemoveIPC = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; RestrictAddressFamilies = ["AF_INET" "AF_INET6"];
RestrictNamespaces = true; RestrictNamespaces = true;
RestrictRealtime = true; RestrictRealtime = true;
RestrictSUIDSGID = true; RestrictSUIDSGID = true;
@ -191,22 +186,18 @@ in
} }
]; ];
users.groups.metrics = { }; users.groups.metrics = {};
services.victoriametrics.scrapeConfigs = services.victoriametrics.scrapeConfigs = let
let
allExporters = allExporters =
lib.mapAttrs lib.mapAttrs (name: exporter: {
(name: exporter: {
inherit (exporter) listenAddress port; inherit (exporter) listenAddress port;
}) }) ((lib.filterAttrs (_: exporter: builtins.isAttrs exporter && exporter.enable)
((lib.filterAttrs (_: exporter: builtins.isAttrs exporter && exporter.enable)
config.services.prometheus.exporters) config.services.prometheus.exporters)
// config.services.prometheus.extraExporters); // config.services.prometheus.extraExporters);
in in
lib.mapAttrs lib.mapAttrs (_: exporter: {
(_: exporter: { targets = ["${exporter.listenAddress}:${toString exporter.port}"];
targets = [ "${exporter.listenAddress}:${toString exporter.port}" ];
}) })
allExporters; allExporters;
}; };

View file

@ -1,4 +1,4 @@
{ config, ... }: { {config, ...}: {
config.services.victoriametrics = { config.services.victoriametrics = {
enable = true; enable = true;
extraOptions = [ extraOptions = [
@ -7,10 +7,10 @@
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" ]; coturn.targets = ["127.0.0.1:9641"];
}; };
}; };
} }

View file

@ -1,15 +1,14 @@
{ pkgs {
, config pkgs,
, ... config,
}: ...
let }: let
# Update pending on rewrite of nextcloud news, though there is an # Update pending on rewrite of nextcloud news, though there is an
# alpha to switch to if it becomes necessary: # alpha to switch to if it becomes necessary:
# https://github.com/nextcloud/news/issues/2610 # https://github.com/nextcloud/news/issues/2610
nextcloud = pkgs.nextcloud27; nextcloud = pkgs.nextcloud27;
hostName = "nextcloud.${config.services.nginx.domain}"; hostName = "nextcloud.${config.services.nginx.domain}";
in in {
{
services.nextcloud = { services.nextcloud = {
inherit hostName; inherit hostName;
@ -43,7 +42,7 @@ in
}; };
# 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 # Set up SSL
services.nginx.virtualHosts."${hostName}" = { services.nginx.virtualHosts."${hostName}" = {

View file

@ -1,4 +1,4 @@
{ pkgs, ... }: { {pkgs, ...}: {
services.postgresql = { services.postgresql = {
package = pkgs.postgresql_14; package = pkgs.postgresql_14;
enable = true; enable = true;

View file

@ -1,17 +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}";
@ -68,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
@ -117,6 +116,6 @@ in
paths = [ paths = [
"/var/lib/private/starbound/storage/universe/" "/var/lib/private/starbound/storage/universe/"
]; ];
pauseServices = [ "starbound.service" ]; pauseServices = ["starbound.service"];
}; };
} }

View file

@ -1,8 +1,6 @@
{ config, ... }: {config, ...}: let
let
domain = config.services.nginx.domain; domain = config.services.nginx.domain;
in in {
{
services.tlaternet-webserver = { services.tlaternet-webserver = {
enable = true; enable = true;
listen = { listen = {
@ -12,12 +10,10 @@ 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";

View file

@ -1,4 +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
@ -26,7 +26,7 @@
{ {
# yui # yui
wireguardPeerConfig = { wireguardPeerConfig = {
AllowedIPs = [ "10.45.249.2/32" ]; AllowedIPs = ["10.45.249.2/32"];
PublicKey = "5mlnqEVJWks5OqgeFA2bLIrvST9TlCE81Btl+j4myz0="; PublicKey = "5mlnqEVJWks5OqgeFA2bLIrvST9TlCE81Btl+j4myz0=";
}; };
} }
@ -34,7 +34,7 @@
{ {
# yuanyuan # yuanyuan
wireguardPeerConfig = { wireguardPeerConfig = {
AllowedIPs = [ "10.45.249.10/32" ]; AllowedIPs = ["10.45.249.10/32"];
PublicKey = "0UsFE2atz/O5P3OKQ8UHyyyGQNJbp1MeIWUJLuoerwE="; PublicKey = "0UsFE2atz/O5P3OKQ8UHyyyGQNJbp1MeIWUJLuoerwE=";
}; };
} }

View file

@ -31,8 +31,8 @@
}; };
# Heisenbridge # Heisenbridge
"heisenbridge/as-token" = { }; "heisenbridge/as-token" = {};
"heisenbridge/hs-token" = { }; "heisenbridge/hs-token" = {};
"hetzner-api" = { "hetzner-api" = {
owner = "acme"; owner = "acme";
@ -62,10 +62,10 @@
}; };
# Steam # Steam
"steam/tlater" = { }; "steam/tlater" = {};
# Turn # Turn
"turn/env" = { }; "turn/env" = {};
"turn/secret" = { "turn/secret" = {
owner = "turnserver"; owner = "turnserver";
}; };

View file

@ -32,19 +32,17 @@
}; };
}; };
outputs = outputs = {
{ self self,
, nixpkgs nixpkgs,
, sops-nix sops-nix,
, nvfetcher 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 in {
{
################## ##################
# Configurations # # Configurations #
################## ##################
@ -74,7 +72,7 @@
}; };
sshUser = "tlater"; sshUser = "tlater";
sshOpts = [ "-p" "2222" "-o" "ForwardAgent=yes" ]; sshOpts = ["-p" "2222" "-o" "ForwardAgent=yes"];
}; };
}; };
@ -91,8 +89,7 @@
run-vm = { run-vm = {
type = "app"; type = "app";
program = program = let
let
vm = nixpkgs.lib.nixosSystem { vm = nixpkgs.lib.nixosSystem {
inherit system; inherit system;
specialArgs.flake-inputs = inputs; specialArgs.flake-inputs = inputs;
@ -105,31 +102,32 @@
in in
(pkgs.writeShellScript "" '' (pkgs.writeShellScript "" ''
${vm.config.system.build.vm.outPath}/bin/run-testvm-vm ${vm.config.system.build.vm.outPath}/bin/run-testvm-vm
'').outPath; '')
.outPath;
}; };
update-pkgs = { update-pkgs = {
type = "app"; type = "app";
program = program = let
let
nvfetcher-bin = "${nvfetcher.packages.${system}.default}/bin/nvfetcher"; nvfetcher-bin = "${nvfetcher.packages.${system}.default}/bin/nvfetcher";
in in
(pkgs.writeShellScript "update-pkgs" '' (pkgs.writeShellScript "update-pkgs" ''
cd "$(git rev-parse --show-toplevel)/pkgs" cd "$(git rev-parse --show-toplevel)/pkgs"
${nvfetcher-bin} -o _sources_pkgs -c nvfetcher.toml ${nvfetcher-bin} -o _sources_pkgs -c nvfetcher.toml
'').outPath; '')
.outPath;
}; };
update-nextcloud-apps = { update-nextcloud-apps = {
type = "app"; type = "app";
program = program = let
let
nvfetcher-bin = "${nvfetcher.packages.${system}.default}/bin/nvfetcher"; nvfetcher-bin = "${nvfetcher.packages.${system}.default}/bin/nvfetcher";
in in
(pkgs.writeShellScript "update-nextcloud-apps" '' (pkgs.writeShellScript "update-nextcloud-apps" ''
cd "$(git rev-parse --show-toplevel)/pkgs" cd "$(git rev-parse --show-toplevel)/pkgs"
${nvfetcher-bin} -o _sources_nextcloud -c nextcloud-apps.toml ${nvfetcher-bin} -o _sources_nextcloud -c nextcloud-apps.toml
'').outPath; '')
.outPath;
}; };
}; };
@ -137,7 +135,7 @@
# Development environment # # Development environment #
########################### ###########################
devShells.${system}.default = nixpkgs.legacyPackages.${system}.mkShell { devShells.${system}.default = nixpkgs.legacyPackages.${system}.mkShell {
sopsPGPKeyDirs = [ "./keys/hosts/" "./keys/users/" ]; sopsPGPKeyDirs = ["./keys/hosts/" "./keys/users/"];
nativeBuildInputs = [ nativeBuildInputs = [
sops-nix.packages.${system}.sops-import-keys-hook sops-nix.packages.${system}.sops-import-keys-hook
]; ];
@ -146,8 +144,6 @@
sops-nix.packages.${system}.sops-init-gpg-key sops-nix.packages.${system}.sops-init-gpg-key
deploy-rs.packages.${system}.default deploy-rs.packages.${system}.default
nixpkgs-fmt
cargo cargo
clippy clippy
rustc rustc

View file

@ -1,7 +1,8 @@
{ config {
, pkgs config,
, lib pkgs,
, ... lib,
...
}: { }: {
options = { options = {
services.nginx.domain = lib.mkOption { services.nginx.domain = lib.mkOption {
@ -9,12 +10,11 @@
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 extraVirtualHostOptions = {
extraVirtualHostOptions = name,
{ name config,
, config ...
, ...
}: { }: {
options = { options = {
enableHSTS = lib.mkEnableOption "Enable HSTS"; enableHSTS = lib.mkEnableOption "Enable HSTS";
@ -47,12 +47,10 @@
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' lib.mapAttrs' (cert: _:
(cert: _:
lib.nameValuePair "acme-${cert}" { lib.nameValuePair "acme-${cert}" {
serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' ''; serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' '';
}) })

View file

@ -1,6 +1,7 @@
{ pkgs {
, rustPlatform pkgs,
, ... rustPlatform,
...
}: }:
rustPlatform.buildRustPackage { rustPlatform.buildRustPackage {
pname = "afvalcalendar"; pname = "afvalcalendar";

View file

@ -1,23 +1,22 @@
{ pkgs {
, lib pkgs,
, lib,
}: }: let
let
inherit (builtins) fromJSON mapAttrs readFile; inherit (builtins) fromJSON mapAttrs readFile;
inherit (pkgs) callPackage; inherit (pkgs) callPackage;
in in
{ {
starbound = callPackage ./starbound { }; starbound = callPackage ./starbound {};
prometheus-fail2ban-exporter = callPackage ./prometheus/fail2ban-exporter.nix { prometheus-fail2ban-exporter = callPackage ./prometheus/fail2ban-exporter.nix {
sources = pkgs.callPackage ./_sources_pkgs/generated.nix { }; sources = pkgs.callPackage ./_sources_pkgs/generated.nix {};
}; };
afvalcalendar = callPackage ./afvalcalendar { }; afvalcalendar = callPackage ./afvalcalendar {};
} }
// ( // (
# Add nextcloud apps # Add nextcloud apps
let let
mkNextcloudApp = pkgs.callPackage ./mkNextcloudApp.nix { }; mkNextcloudApp = pkgs.callPackage ./mkNextcloudApp.nix {};
sources = fromJSON (readFile ./_sources_nextcloud/generated.json); sources = fromJSON (readFile ./_sources_nextcloud/generated.json);
in in
mapAttrs (_: source: mkNextcloudApp source) sources mapAttrs (_: source: mkNextcloudApp source) sources
) )

View file

@ -1,6 +1,6 @@
{ fetchNextcloudApp {
, lib fetchNextcloudApp,
, lib,
}: source: }: source:
fetchNextcloudApp { fetchNextcloudApp {
url = source.src.url; url = source.src.url;

View file

@ -1,6 +1,6 @@
{ buildGoModule {
, sources buildGoModule,
, sources,
}: }:
buildGoModule { buildGoModule {
inherit (sources.prometheus-fail2ban-exporter) pname src version; inherit (sources.prometheus-fail2ban-exporter) pname src version;

View file

@ -1,23 +1,22 @@
{ stdenv {
, lib stdenv,
, makeWrapper lib,
, patchelf makeWrapper,
, steamPackages patchelf,
, replace-secret steamPackages,
, 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 { steamcmd = steamPackages.steamcmd.override {
steamRoot = "/var/lib/starbound/.steamcmd"; steamRoot = "/var/lib/starbound/.steamcmd";
}; };
wrapperPath = lib.makeBinPath [ patchelf steamcmd replace-secret ]; wrapperPath = lib.makeBinPath [patchelf steamcmd 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)"
@ -32,4 +31,4 @@ stdenv.mkDerivation {
wrapProgram $out/bin/launch-starbound \ wrapProgram $out/bin/launch-starbound \
--prefix PATH : "${wrapperPath}" --prefix PATH : "${wrapperPath}"
''; '';
} }