Refactoring

This commit is contained in:
Jakob Lechner 2025-04-09 21:45:49 +02:00
parent 209526d7c0
commit 8e8041e423
51 changed files with 1414 additions and 1368 deletions

View file

@ -7,11 +7,11 @@
./services
];
networking.hostName = "aluminium";
services.openssh.enable = true;
security.sudo.wheelNeedsPassword = false;
networking = {
hostName = "aluminium";
useDHCP = false;
vlans = {
lechner = {
@ -78,6 +78,15 @@
iifname "voice" udp dport 5059 accept
ip saddr 217.10.68.150 udp dport 5060 accept
'';
nftables.tables.pppoe = {
family = "ip";
content = ''
chain clamp {
type filter hook forward priority mangle;
oifname "ppp0" tcp flags syn tcp option maxseg size set rt mtu comment "clamp MSS to Path MTU"
}
'';
};
};
@ -116,16 +125,6 @@
};
};
networking.nftables.tables.pppoe = {
family = "ip";
content = ''
chain clamp {
type filter hook forward priority mangle;
oifname "ppp0" tcp flags syn tcp option maxseg size set rt mtu comment "clamp MSS to Path MTU"
}
'';
};
zramSwap = {
enable = true;
algorithm = "zstd";

View file

@ -14,21 +14,6 @@ let
voicemail-sounds = pkgs.callPackage ./voicemail-sounds { };
in
{
systemd.services.asterisk-voicemail-sounds = {
wantedBy = [ "asterisk.service" ];
after = [ "asterisk.service" ];
script = ''
ln -sfn \
${voicemail-sounds}/unavail.wav \
/var/spool/asterisk/voicemail/lechner/876/unavail.wav
'';
restartTriggers = [ voicemail-sounds ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
};
services.asterisk = {
enable = true;
confFiles = {
@ -169,12 +154,12 @@ in
useTheseDefaultConfFiles = [ ];
};
sops.secrets = (lib.listToAttrs (map
sops.secrets = lib.listToAttrs (map
(name: lib.nameValuePair "asterisk-${name}" {
sopsFile = ../../secrets.yaml;
owner = config.users.users.asterisk.name;
})
secretConfigFiles));
secretConfigFiles);
environment.etc = lib.mapAttrs'
(name: _: lib.nameValuePair
"asterisk/${name}.conf"
@ -194,84 +179,99 @@ in
};
};
systemd.services."asterisk-reload-endpoint@" = {
description = "Check if asterisk endpoint is identified and reload it when it is not.";
serviceConfig = {
Type = "oneshot";
systemd.services = {
"asterisk-reload-endpoint@" = {
description = "Check if asterisk endpoint is identified and reload it when it is not.";
serviceConfig = {
Type = "oneshot";
};
environment = {
ENDPOINT = "%I";
};
script = ''
export PATH=${pkgs.lib.makeBinPath [pkgs.asterisk pkgs.gnused pkgs.gnugrep]}
if ! asterisk -r -x "pjsip show endpoint $ENDPOINT" | sed -n '/^===/,/^\s*ParameterName/{//!p}' | grep -q 'Identify:'; then
asterisk -r -x "module reload res_pjsip_endpoint_identifier_ip.so"
fi
'';
};
environment = {
ENDPOINT = "%I";
asterisk-voicemail-sounds = {
wantedBy = [ "asterisk.service" ];
after = [ "asterisk.service" ];
script = ''
ln -sfn \
${voicemail-sounds}/unavail.wav \
/var/spool/asterisk/voicemail/lechner/876/unavail.wav
'';
restartTriggers = [ voicemail-sounds ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
};
script = ''
export PATH=${pkgs.lib.makeBinPath [pkgs.asterisk pkgs.gnused pkgs.gnugrep]}
if ! asterisk -r -x "pjsip show endpoint $ENDPOINT" | sed -n '/^===/,/^\s*ParameterName/{//!p}' | grep -q 'Identify:'; then
asterisk -r -x "module reload res_pjsip_endpoint_identifier_ip.so"
fi
'';
};
"asterisk-voicemail-call@" = {
description = "Check if voicemail exists and place a call to the voicemail application.";
serviceConfig = {
Type = "oneshot";
};
scriptArgs = "%I";
script = ''
export PATH=${pkgs.lib.makeBinPath [pkgs.asterisk pkgs.coreutils pkgs.findutils]}
number="$(echo "$1" | cut -d ':' -f 1)"
user="$(echo "$1" | cut -d ':' -f 2)"
channel="PJSIP/$(echo "$1" | cut -d ':' -f 3)"
systemd.timers.asterisk-reload-endpoint = {
description = "Check if asterisk endpoint is identified and reload it when it is not.";
after = [ "asterisk.service" ];
wantedBy = [ "timers.target" ];
timerConfig = {
Persistent = true;
OnCalendar = "*-*-* *:*:00";
Unit = "asterisk-reload-endpoint@sipgate.service";
if ! find "/var/spool/asterisk/voicemail/$user/$number/INBOX/" -mindepth 1 -maxdepth 1 | read; then
exit
fi
callfile="$(mktemp -p /tmp XXXXXXXXXX.call)"
chmod 644 "$callfile"
cat > "$callfile" << EOF
Channel: $channel
WaitTime: 15
Application: VoiceMailMain
Data: $number@$user
CallerID: Voicemail
EOF
mv "$callfile" /var/spool/asterisk/outgoing/
'';
};
};
systemd.services."asterisk-voicemail-call@" = {
description = "Check if voicemail exists and place a call to the voicemail application.";
serviceConfig = {
Type = "oneshot";
systemd.timers = {
asterisk-reload-endpoint = {
description = "Check if asterisk endpoint is identified and reload it when it is not.";
after = [ "asterisk.service" ];
wantedBy = [ "timers.target" ];
timerConfig = {
Persistent = true;
OnCalendar = "*-*-* *:*:00";
Unit = "asterisk-reload-endpoint@sipgate.service";
};
};
scriptArgs = "%I";
script = ''
export PATH=${pkgs.lib.makeBinPath [pkgs.asterisk pkgs.coreutils pkgs.findutils]}
number="$(echo "$1" | cut -d ':' -f 1)"
user="$(echo "$1" | cut -d ':' -f 2)"
channel="PJSIP/$(echo "$1" | cut -d ':' -f 3)"
if ! find "/var/spool/asterisk/voicemail/$user/$number/INBOX/" -mindepth 1 -maxdepth 1 | read; then
exit
fi
callfile="$(mktemp -p /tmp XXXXXXXXXX.call)"
chmod 644 "$callfile"
cat > "$callfile" << EOF
Channel: $channel
WaitTime: 15
Application: VoiceMailMain
Data: $number@$user
CallerID: Voicemail
EOF
mv "$callfile" /var/spool/asterisk/outgoing/
'';
};
systemd.timers.asterisk-voicemail-call-10 = {
description = "Check if voicemail exists and place a call to the voicemail application.";
after = [ "asterisk.service" ];
wantedBy = [ "timers.target" ];
timerConfig = {
Persistent = true;
OnCalendar = "*-*-* 07..22:00,20,40:00";
Unit = "asterisk-voicemail-call@876:lechner:10.service";
asterisk-voicemail-call-10 = {
description = "Check if voicemail exists and place a call to the voicemail application.";
after = [ "asterisk.service" ];
wantedBy = [ "timers.target" ];
timerConfig = {
Persistent = true;
OnCalendar = "*-*-* 07..22:00,20,40:00";
Unit = "asterisk-voicemail-call@876:lechner:10.service";
};
};
asterisk-voicemail-call-11 = {
description = "Check if voicemail exists and place a call to the voicemail application.";
after = [ "asterisk.service" ];
wantedBy = [ "timers.target" ];
timerConfig = {
Persistent = true;
OnCalendar = "*-*-* 07..22:00,10,30:50";
Unit = "asterisk-voicemail-call@876:lechner:11.service";
};
};
};
systemd.timers.asterisk-voicemail-call-11 = {
description = "Check if voicemail exists and place a call to the voicemail application.";
after = [ "asterisk.service" ];
wantedBy = [ "timers.target" ];
timerConfig = {
Persistent = true;
OnCalendar = "*-*-* 07..22:00,10,30:50";
Unit = "asterisk-voicemail-call@876:lechner:11.service";
};
};
#voicemailCallScript
}

View file

@ -78,8 +78,8 @@ in
unit_system = "metric";
time_zone = "Europe/Berlin";
temperature_unit = "C";
longitude = config.location.longitude;
latitude = config.location.latitude;
inherit (config.location) longitude;
inherit (config.location) latitude;
};
default_config = { };
"automation nix" = [

View file

@ -8,8 +8,6 @@
./services
];
services.fstrim.enable = true;
networking = {
hostName = "copper";
extraHosts = lib.concatStringsSep "\n" (
@ -32,24 +30,28 @@
priority = 1;
};
services.snapper.configs = {
home = {
SUBVOLUME = "/home";
ALLOW_USERS = [ "jalr" ];
TIMELINE_CREATE = true;
TIMELINE_CLEANUP = true;
TIMELINE_LIMIT_HOURLY = 12;
TIMELINE_LIMIT_DAILY = 7;
TIMELINE_LIMIT_WEEKLY = 4;
TIMELINE_LIMIT_MONTHLY = 3;
TIMELINE_LIMIT_YEARLY = 0;
BACKGROUND_COMPARISON = "yes";
NUMBER_CLEANUP = "no";
NUMBER_MIN_AGE = "1800";
NUMBER_LIMIT = "100";
NUMBER_LIMIT_IMPORTANT = "10";
EMPTY_PRE_POST_CLEANUP = "yes";
EMPTY_PRE_POST_MIN_AGE = "1800";
services = {
fstrim.enable = true;
flatpak.enable = true;
snapper.configs = {
home = {
SUBVOLUME = "/home";
ALLOW_USERS = [ "jalr" ];
TIMELINE_CREATE = true;
TIMELINE_CLEANUP = true;
TIMELINE_LIMIT_HOURLY = 12;
TIMELINE_LIMIT_DAILY = 7;
TIMELINE_LIMIT_WEEKLY = 4;
TIMELINE_LIMIT_MONTHLY = 3;
TIMELINE_LIMIT_YEARLY = 0;
BACKGROUND_COMPARISON = "yes";
NUMBER_CLEANUP = "no";
NUMBER_MIN_AGE = "1800";
NUMBER_LIMIT = "100";
NUMBER_LIMIT_IMPORTANT = "10";
EMPTY_PRE_POST_CLEANUP = "yes";
EMPTY_PRE_POST_MIN_AGE = "1800";
};
};
};
@ -68,7 +70,5 @@
};
system.stateVersion = "24.05";
services.flatpak.enable = true;
}

View file

@ -1,4 +1,4 @@
{ ... }@inputs:
inputs:
let
hardware = inputs.nixos-hardware.nixosModules;
in

View file

@ -28,7 +28,7 @@ let
luksDev = "-part3";
biosBoot = "-part4";
};
efiSystemPartitions = (map (diskName: diskName + partitionScheme.efiBoot) (lib.attrValues disks));
efiSystemPartitions = map (diskName: diskName + partitionScheme.efiBoot) (lib.attrValues disks);
in
with lib; {
imports = [
@ -154,19 +154,19 @@ with lib; {
};
supportedFilesystems = [ "zfs" ];
zfs = {
devNodes = devNodes;
inherit devNodes;
forceImportRoot = false;
};
loader = {
efi = {
canTouchEfiVariables = (if removableEfi then false else true);
efiSysMountPoint = ("/boot/efis/" + (head (lib.attrValues disks))
+ partitionScheme.efiBoot);
canTouchEfiVariables = if removableEfi then false else true;
efiSysMountPoint = "/boot/efis/" + (head (lib.attrValues disks))
+ partitionScheme.efiBoot;
};
generationsDir.copyKernels = true;
grub = {
enable = true;
devices = (map (diskName: devNodes + diskName) (attrValues disks));
devices = map (diskName: devNodes + diskName) (attrValues disks);
efiInstallAsRemovable = removableEfi;
copyKernels = true;
efiSupport = true;
@ -176,11 +176,11 @@ with lib; {
terminal_input --append serial
terminal_output --append serial
'';
extraInstallCommands = (toString (map
extraInstallCommands = toString (map
(diskName: ''
${pkgs.coreutils-full}/bin/cp -r ${config.boot.loader.efi.efiSysMountPoint}/EFI /boot/efis/${diskName}${partitionScheme.efiBoot}
'')
(tail (attrValues disks))));
(tail (attrValues disks)));
};
};
kernelParams = [

View file

@ -6,11 +6,13 @@ in
services.avahi = {
enable = true;
allowInterfaces = [ interfaces.lan ];
publish.domain = true;
publish.enable = true;
publish.userServices = true;
publish.workstation = true;
openFirewall = false;
publish = {
domain = true;
enable = true;
userServices = true;
workstation = true;
};
};
networking.firewall.interfaces."${interfaces.lan}".allowedUDPPorts = [

View file

@ -8,23 +8,29 @@ in
sopsFile = ../secrets.yaml;
};
services.calibre-server = {
enable = true;
port = ports.calibre-server.tcp;
host = "127.0.0.1";
};
services.calibre-web = {
enable = true;
user = config.services.calibre-server.user;
group = config.services.calibre-server.group;
listen = {
ip = "127.0.0.1";
port = ports.calibre-web.tcp;
services = {
calibre-server = {
enable = true;
port = ports.calibre-server.tcp;
host = "127.0.0.1";
};
calibre-web = {
enable = true;
inherit (config.services.calibre-server) user;
inherit (config.services.calibre-server) group;
listen = {
ip = "127.0.0.1";
port = ports.calibre-web.tcp;
};
options = {
enableBookUploading = true;
reverseProxyAuth = {
enable = true;
header = "X-Remote-User";
};
};
};
options.enableBookUploading = true;
options.reverseProxyAuth.enable = true;
options.reverseProxyAuth.header = "X-Remote-User";
};
systemd.services.calibre-web = {

View file

@ -5,256 +5,264 @@ let
domain = "hass.jalr.de";
in
{
sops.secrets.home-assistant = {
sopsFile = ../secrets.yaml;
owner = "root";
group = "hass";
mode = "0640";
};
sops.secrets."mqtt-users/home-assistant" = {
sopsFile = ../secrets.yaml;
};
sops.secrets."mqtt-users/valetudo" = {
sopsFile = ../secrets.yaml;
};
services.home-assistant = {
enable = true;
lovelaceConfig = {
title = "Home";
views = [
{
path = "default_view";
title = "Home";
cards = [
{
title = "Eingang";
type = "entities";
entities = [
{
entity = "light.eingang_deckenleuchte_deckenleuchte";
name = "Deckenleuchte";
}
];
}
{
name = "Esstisch";
type = "entities";
entities = [
{
entity = "light.yeelight_meteorite_ambient_light";
name = "Ambient light";
}
{
entity = "light.yeelight_meteorite_ceiling_light";
name = "Ceiling light";
}
{
entity = "light.yeelight_meteorite_night_light";
name = "Night light";
}
];
}
];
}
];
sops.secrets = {
home-assistant = {
sopsFile = ../secrets.yaml;
owner = "root";
group = "hass";
mode = "0640";
};
extraComponents = [
# See https://www.home-assistant.io/integrations
"bthome"
"caldav"
"esphome"
"local_todo"
"openweathermap"
"wyoming"
"xiaomi_ble"
"vlc_telnet"
];
customComponents = with pkgs.home-assistant-custom-components; [
adaptive_lighting
];
customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
valetudo-map-card
];
lovelaceConfigWritable = false;
configWritable = false;
config = {
http = {
server_host = [ "127.0.0.1" ];
server_port = ports.home-assistant.tcp;
use_x_forwarded_for = true;
trusted_proxies = [ "127.0.0.1" ];
};
homeassistant = {
unit_system = "metric";
time_zone = "Europe/Berlin";
temperature_unit = "C";
longitude = config.location.longitude;
latitude = config.location.latitude;
external_url = "https://${domain}/";
internal_url = "https://${domain}/";
};
default_config = { };
adaptive_lighting = {
lights = [
"light.yeelight_meteorite_ceiling_light"
"light.eingang_deckenleuchte_deckenleuchte"
"light.led_panel_schreibtisch_panel"
"light.kueche_leiste_led_light"
"light.badspiegel_background_light"
"light.badspiegel_front_light"
"mqtt-users/home-assistant" = {
sopsFile = ../secrets.yaml;
};
"mqtt-users/valetudo" = {
sopsFile = ../secrets.yaml;
};
};
networking.firewall.interfaces = {
"${interfaces.lan}".allowedTCPPorts = [ ports.mqtt.tcp ];
iot.allowedTCPPorts = [ ports.mqtt.tcp ];
};
services = {
home-assistant = {
enable = true;
lovelaceConfig = {
title = "Home";
views = [
{
path = "default_view";
title = "Home";
cards = [
{
title = "Eingang";
type = "entities";
entities = [
{
entity = "light.eingang_deckenleuchte_deckenleuchte";
name = "Deckenleuchte";
}
];
}
{
name = "Esstisch";
type = "entities";
entities = [
{
entity = "light.yeelight_meteorite_ambient_light";
name = "Ambient light";
}
{
entity = "light.yeelight_meteorite_ceiling_light";
name = "Ceiling light";
}
{
entity = "light.yeelight_meteorite_night_light";
name = "Night light";
}
];
}
];
}
];
};
"automation nix" = [
{
alias = "Waschmaschine fertig Benachrichtigung";
trigger = {
platform = "state";
entity_id = "sensor.waschmaschine_aktueller_vorgang";
to = "Knitterschutz/Ende";
};
action = [
{
service = "notify.mobile_app_shift6mq";
data = {
message = "Die Waschmaschine hat das Programm beendet.";
title = "Wäsche fertig";
};
}
extraComponents = [
# See https://www.home-assistant.io/integrations
"bthome"
"caldav"
"esphome"
"local_todo"
"openweathermap"
"wyoming"
"xiaomi_ble"
"vlc_telnet"
];
customComponents = with pkgs.home-assistant-custom-components; [
adaptive_lighting
];
customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
valetudo-map-card
];
lovelaceConfigWritable = false;
configWritable = false;
config = {
http = {
server_host = [ "127.0.0.1" ];
server_port = ports.home-assistant.tcp;
use_x_forwarded_for = true;
trusted_proxies = [ "127.0.0.1" ];
};
homeassistant = {
unit_system = "metric";
time_zone = "Europe/Berlin";
temperature_unit = "C";
inherit (config.location) longitude;
inherit (config.location) latitude;
external_url = "https://${domain}/";
internal_url = "https://${domain}/";
};
default_config = { };
adaptive_lighting = {
lights = [
"light.yeelight_meteorite_ceiling_light"
"light.eingang_deckenleuchte_deckenleuchte"
"light.led_panel_schreibtisch_panel"
"light.kueche_leiste_led_light"
"light.badspiegel_background_light"
"light.badspiegel_front_light"
];
}
];
"automation ui" = "!include automations.yaml";
"scene nix" = [
];
"scene ui" = "!include scenes.yaml";
bluetooth = { };
device_tracker = [
{
platform = "bluetooth_le_tracker";
}
];
"script nix" = [
{
lights_off_except = {
icon = "mdi:home-lightbulb";
fields.exclude_lights.description = "Excluded lights as list";
sequence = [
};
"automation nix" = [
{
alias = "Waschmaschine fertig Benachrichtigung";
trigger = {
platform = "state";
entity_id = "sensor.waschmaschine_aktueller_vorgang";
to = "Knitterschutz/Ende";
};
action = [
{
service = "logbook.log";
data_template = {
entity_id = "script.turn_off_lights";
name = "Exclude log";
message = "Turning of all lights except: {{ exclude_lights }}";
service = "notify.mobile_app_shift6mq";
data = {
message = "Die Waschmaschine hat das Programm beendet.";
title = "Wäsche fertig";
};
}
{
service = "light.turn_off";
data_template.entity_id = ''
{{ states.light | rejectattr('entity_id', 'in', exclude_lights) | rejectattr('state', 'in', 'off') | join(',', attribute='entity_id') }}
'';
}
];
}
];
"automation ui" = "!include automations.yaml";
"scene nix" = [
];
"scene ui" = "!include scenes.yaml";
bluetooth = { };
device_tracker = [
{
platform = "bluetooth_le_tracker";
}
];
"script nix" = [
{
lights_off_except = {
icon = "mdi:home-lightbulb";
fields.exclude_lights.description = "Excluded lights as list";
sequence = [
{
service = "logbook.log";
data_template = {
entity_id = "script.turn_off_lights";
name = "Exclude log";
message = "Turning of all lights except: {{ exclude_lights }}";
};
}
{
service = "light.turn_off";
data_template.entity_id = ''
{{ states.light | rejectattr('entity_id', 'in', exclude_lights) | rejectattr('state', 'in', 'off') | join(',', attribute='entity_id') }}
'';
}
];
};
}
];
"script ui" = "!include scripts.yaml";
calendar = [
{
platform = "caldav";
username = "jalr@jalr.de";
password = "!secret radicale";
url = "https://cal.jalr.de/radicale";
}
];
mqtt = { };
media_player = [
{
platform = "mpd";
name = "mpd@iron";
host = "127.0.0.1";
}
];
};
};
mosquitto = {
enable = true;
persistence = true;
listeners = [
{
port = ports.mqtt.tcp;
users = {
valetudo = {
passwordFile = config.sops.secrets."mqtt-users/valetudo".path;
acl = [
"readwrite homeassistant/+/donald/#"
"readwrite valetudo/donald/#"
];
};
home-assistant = {
passwordFile = config.sops.secrets."mqtt-users/home-assistant".path;
acl = [ "readwrite #" ];
};
};
}
];
"script ui" = "!include scripts.yaml";
calendar = [
{
platform = "caldav";
username = "jalr@jalr.de";
password = "!secret radicale";
url = "https://cal.jalr.de/radicale";
}
];
mqtt = { };
media_player = [
{
platform = "mpd";
name = "mpd@iron";
host = "127.0.0.1";
}
];
};
};
services.mosquitto = {
enable = true;
persistence = true;
listeners = [
{
port = ports.mqtt.tcp;
users = {
valetudo = {
passwordFile = config.sops.secrets."mqtt-users/valetudo".path;
acl = [
"readwrite homeassistant/+/donald/#"
"readwrite valetudo/donald/#"
];
};
home-assistant = {
passwordFile = config.sops.secrets."mqtt-users/home-assistant".path;
acl = [ "readwrite #" ];
};
};
}
systemd.services = {
home-assistant.serviceConfig.ExecStartPre = [
(
pkgs.writeShellScript "home-assistant-secrets" ''
ln -sf "${config.sops.secrets.home-assistant.path}" "${config.services.home-assistant.configDir}/secrets.yaml"
''
)
];
};
systemd.services.hass-vlc = {
script = ''
exec ${pkgs.vlc}/bin/cvlc \
--no-video \
-I telnet \
--telnet-password=vlc \
--sout='#transcode{acodec=s16le,channels=2,samplerate=48000}:std{access=file,mux=raw,dst=/run/snapserver/hass.fifo}' \
--aout=none
'';
wants = [ "snapserver.service" ];
after = [ "snapserver.service" ];
serviceConfig = {
BindPaths = [ "/run/snapserver/hass.fifo" ];
BindReadOnlyPaths = [ "/nix/store" "/etc/ssl/certs" "/etc/static/ssl/certs" ];
CapabilityBoundingSet = "";
DynamicUser = "true";
Group = "snapserver";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = lib.mkForce true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "noaccess";
ProtectSystem = "strict";
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RootDirectory = "/run/hass-vlc";
RuntimeDirectory = "hass-vlc";
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" ];
hass-vlc = {
script = ''
exec ${pkgs.vlc}/bin/cvlc \
--no-video \
-I telnet \
--telnet-password=vlc \
--sout='#transcode{acodec=s16le,channels=2,samplerate=48000}:std{access=file,mux=raw,dst=/run/snapserver/hass.fifo}' \
--aout=none
'';
wants = [ "snapserver.service" ];
after = [ "snapserver.service" ];
serviceConfig = {
BindPaths = [ "/run/snapserver/hass.fifo" ];
BindReadOnlyPaths = [ "/nix/store" "/etc/ssl/certs" "/etc/static/ssl/certs" ];
CapabilityBoundingSet = "";
DynamicUser = "true";
Group = "snapserver";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = lib.mkForce true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "noaccess";
ProtectSystem = "strict";
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RootDirectory = "/run/hass-vlc";
RuntimeDirectory = "hass-vlc";
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" ];
};
wantedBy = [ "multi-user.target" ];
};
wantedBy = [ "multi-user.target" ];
};
networking.firewall.interfaces."${interfaces.lan}".allowedTCPPorts = [ ports.mqtt.tcp ];
networking.firewall.interfaces.iot.allowedTCPPorts = [ ports.mqtt.tcp ];
systemd.services.home-assistant.serviceConfig.ExecStartPre = [
(
pkgs.writeShellScript "home-assistant-secrets" ''
ln -sf "${config.sops.secrets.home-assistant.path}" "${config.services.home-assistant.configDir}/secrets.yaml"
''
)
];
systemd.tmpfiles.rules = [
"f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass"
"f ${config.services.home-assistant.configDir}/scenes.yaml 0755 hass hass"

View file

@ -13,27 +13,27 @@ let
};
in
{
sops.secrets = (
lib.listToAttrs (map
(name: lib.nameValuePair "wireguard_key_${name}" {
sopsFile = ../secrets.yaml;
})
[
"hetzner-ha"
]
)
sops.secrets = lib.listToAttrs (map
(name: lib.nameValuePair "wireguard_key_${name}" {
sopsFile = ../secrets.yaml;
})
[
"hetzner-ha"
]
);
networking.iproute2.enable = true;
networking.iproute2.rttablesExtraConfig = ''
${toString rtTable.id} ${rtTable.name}
'';
networking.wireguard.interfaces = {
hetzner-ha = {
networking = {
iproute2 = {
enable = true;
rttablesExtraConfig = ''
${toString rtTable.id} ${rtTable.name}
'';
};
firewall.allowedUDPPorts = [ listenPort ];
wireguard.interfaces.hetzner-ha = {
ips = [ "${externalIp}/32" ];
privateKeyFile = config.sops.secrets.wireguard_key_hetzner-ha.path;
listenPort = listenPort;
inherit listenPort;
table = rtTable.name;
postSetup = ''
${pkgs.iproute2}/bin/ip rule add from ${externalIp} to 192.168.0.0/16 table main priority 10
@ -44,7 +44,7 @@ in
${pkgs.iproute2}/bin/ip rule del from ${externalIp} table ${rtTable.name} priority 20
'';
peers = [{
publicKey = publicKey;
inherit publicKey;
endpoint = "${remoteHost}:${toString remotePort}";
persistentKeepalive = 25;
allowedIPs = [
@ -53,6 +53,4 @@ in
}];
};
};
networking.firewall.allowedUDPPorts = [ listenPort ];
}

View file

@ -14,19 +14,21 @@ in
enableACME = true;
forceSSL = true;
basicAuthFile = config.sops.secrets.radicale-htpasswd.path;
locations."/radicale/" = {
proxyPass = "http://127.0.0.1:${toString ports.radicale.tcp}/";
recommendedProxySettings = true;
#basicAuthFile = "";
extraConfig = ''
proxy_set_header X-Script-Name /radicale;
proxy_set_header X-Remote-User $remote_user;
'';
# proxy_pass_request_headers = on;
# underscores_in_headers = on;
locations = {
"/radicale/" = {
proxyPass = "http://127.0.0.1:${toString ports.radicale.tcp}/";
recommendedProxySettings = true;
#basicAuthFile = "";
extraConfig = ''
proxy_set_header X-Script-Name /radicale;
proxy_set_header X-Remote-User $remote_user;
'';
# proxy_pass_request_headers = on;
# underscores_in_headers = on;
};
"/.well-known/caldav".return = "301 $scheme://$host:$server_port/radicale";
"/.well-known/carddav".return = "301 $scheme://$host:$server_port/radicale";
};
locations."/.well-known/caldav".return = "301 $scheme://$host:$server_port/radicale";
locations."/.well-known/carddav".return = "301 $scheme://$host:$server_port/radicale";
};
};

View file

@ -3,9 +3,9 @@ let
ports = import ../ports.nix args;
domain = "rmfakecloud.jalr.de";
cfg = config.services.rmfakecloud;
mkEnvironment = (settings: lib.strings.concatLines (
mkEnvironment = settings: lib.strings.concatLines (
lib.attrsets.mapAttrsToList (name: value: "export ${name}='${value}'") settings
));
);
managementScript = pkgs.writeShellScriptBin "rmfakecloud" ''
[[ $(id -u) == "rmfakecloud" ]] || exec sudo -u rmfakecloud -- "$0" "$@"
set -a

View file

@ -24,54 +24,44 @@
services.blueman.enable = true;
systemd.services.bluetooth-auto-pair = {
wantedBy = [
"bluetooth.service"
];
after = [
"bluetooth.service"
];
bindsTo = [
"bluetooth.service"
];
serviceConfig = {
Type = "simple";
ExecStart = pkgs.writeShellScript "exec-start" ''
${pkgs.bluez}/bin/bluetoothctl <<EOF
discoverable on
pairable on
EOF
systemd.services = {
bluetooth-auto-pair = {
wantedBy = [ "bluetooth.service" ];
after = [ "bluetooth.service" ];
bindsTo = [ "bluetooth.service" ];
serviceConfig = {
Type = "simple";
ExecStart = pkgs.writeShellScript "exec-start" ''
${pkgs.bluez}/bin/bluetoothctl <<EOF
discoverable on
pairable on
EOF
${pkgs.coreutils}/bin/yes | ${pkgs.bluez-tools}/bin/bt-agent -c NoInputNoOutput
'';
ExecStop = pkgs.writeShellScript "exec-stop" ''
kill -s SIGINT $MAINPID
'';
Restart = "on-failure";
${pkgs.coreutils}/bin/yes | ${pkgs.bluez-tools}/bin/bt-agent -c NoInputNoOutput
'';
ExecStop = pkgs.writeShellScript "exec-stop" ''
kill -s SIGINT $MAINPID
'';
Restart = "on-failure";
};
};
};
systemd.services.bluealsa-aplay = {
wantedBy = [
"multi-user.target"
];
serviceConfig = {
DynamicUser = true;
Type = "simple";
ExecStart = "${pkgs.bluez-alsa}/bin/bluealsa-aplay --profile-a2dp --pcm=default:CARD=bluetooth 00:00:00:00:00:00";
Restart = "on-failure";
SupplementaryGroups = [ "audio" ];
bluealsa-aplay = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
DynamicUser = true;
Type = "simple";
ExecStart = "${pkgs.bluez-alsa}/bin/bluealsa-aplay --profile-a2dp --pcm=default:CARD=bluetooth 00:00:00:00:00:00";
Restart = "on-failure";
SupplementaryGroups = [ "audio" ];
};
};
};
systemd.services.bluealsa-a2dp = {
wantedBy = [
"multi-user.target"
];
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.bluez-alsa}/bin/bluealsa -p a2dp-sink";
Restart = "on-failure";
bluealsa-a2dp = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.bluez-alsa}/bin/bluealsa -p a2dp-sink";
Restart = "on-failure";
};
};
};
}

View file

@ -30,50 +30,52 @@ in
jalr.libvirt.enable = true;
systemd.services.libvirt-guests.serviceConfig.ExecStop = [
""
"${shutdownAndroidVm} ${vmName}"
"${pkgs.libvirt}/libexec/libvirt-guests.sh stop"
];
systemd.services = {
libvirt-guests.serviceConfig.ExecStop = [
""
"${shutdownAndroidVm} ${vmName}"
"${pkgs.libvirt}/libexec/libvirt-guests.sh stop"
];
systemd.services."whatsapp@" = {
description = "Start Android VM, wait for WhatsApp and shut down VM.";
serviceConfig = {
Type = "oneshot";
};
environment.VM = "%i";
script = ''
export PATH=${pkgs.lib.makeBinPath [pkgs.libvirt pkgs.gnused pkgs.android-tools pkgs.coreutils]}
"whatsapp@" = {
description = "Start Android VM, wait for WhatsApp and shut down VM.";
serviceConfig = {
Type = "oneshot";
};
environment.VM = "%i";
script = ''
export PATH=${pkgs.lib.makeBinPath [pkgs.libvirt pkgs.gnused pkgs.android-tools pkgs.coreutils]}
domstate="$(virsh domstate "$VM")"
domstate="$(virsh domstate "$VM")"
if [ "$domstate" != "running" ]; then
virsh start "$VM"
fi
echo "Wait until IP of Android VM is known"
while :; do
host="$(virsh -q domifaddr --domain "$VM" | sed -n -r 's#.*ipv4\s*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/[0-9]+$#\1#p')"
if [ "$host" ]; then
break
if [ "$domstate" != "running" ]; then
virsh start "$VM"
fi
sleep 1
done
port=5555
adb connect "$host:$port"
echo "Wait until IP of Android VM is known"
while :; do
host="$(virsh -q domifaddr --domain "$VM" | sed -n -r 's#.*ipv4\s*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/[0-9]+$#\1#p')"
if [ "$host" ]; then
break
fi
sleep 1
done
echo "Waiting for WhatsApp"
while ! adb -s "$host:$port" shell -- pgrep com.whatsapp > /dev/null; do
sleep 1
done
port=5555
adb connect "$host:$port"
echo "Sleeping..."
sleep 5m
echo "Waiting for WhatsApp"
while ! adb -s "$host:$port" shell -- pgrep com.whatsapp > /dev/null; do
sleep 1
done
echo "Shutting down Android"
adb -s "$host:$port" shell -- reboot -p
'';
echo "Sleeping..."
sleep 5m
echo "Shutting down Android"
adb -s "$host:$port" shell -- reboot -p
'';
};
};
systemd.timers."whatsapp-${vmName}" = {
description = "Start Android VM to run WhatsApp";

View file

@ -2,7 +2,7 @@
{
boot.initrd.postDeviceCommands =
let
device = config.disko.devices.disk.virt.content.partitions.linux.device;
inherit (config.disko.devices.disk.virt.content.partitions.linux) device;
in
lib.mkAfter ''
mkdir /mnt
@ -14,16 +14,17 @@
btrfs subvolume snapshot /mnt/root-blank /mnt/root
'';
services.openssh = {
hostKeys = lib.mkForce [{
path = "/persist/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}];
services = {
openssh = {
hostKeys = lib.mkForce [{
path = "/persist/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}];
};
forgejo.stateDir = "/persist/var/lib/forgejo";
postgresql.dataDir = "/persist/var/lib/postgresql/${config.services.postgresql.package.psqlSchema}";
};
services.forgejo.stateDir = "/persist/var/lib/forgejo";
services.postgresql.dataDir = "/persist/var/lib/postgresql/${config.services.postgresql.package.psqlSchema}";
fileSystems."/persist".neededForBoot = true;
environment.persistence."/persist" = {
hideMounts = true;

View file

@ -10,44 +10,44 @@ in
owner = config.systemd.services.hedgedoc.serviceConfig.User;
sopsFile = ../secrets.yaml;
};
services.hedgedoc = {
enable = true;
settings =
let
day = 24 * 60 * 60 * 1000;
in
{
domain = domain;
protocolUseSSL = true;
csp.enable = true;
port = ports.hedgedoc.tcp;
db = {
dialect = "postgres";
host = "/run/postgresql";
user = "hedgedoc";
database = "hedgedoc";
services = {
hedgedoc = {
enable = true;
settings =
let
day = 24 * 60 * 60 * 1000;
in
{
inherit domain;
protocolUseSSL = true;
csp.enable = true;
port = ports.hedgedoc.tcp;
db = {
dialect = "postgres";
host = "/run/postgresql";
user = "hedgedoc";
database = "hedgedoc";
};
allowEmailRegister = false;
sessionSecret = config.sops.secrets.hedgedoc-session-secret.path;
sessionLife = 90 * day;
};
allowEmailRegister = false;
sessionSecret = config.sops.secrets.hedgedoc-session-secret.path;
sessionLife = 90 * day;
};
postgresql = {
enable = true;
ensureDatabases = [ "hedgedoc" ];
ensureUsers = [{
name = "hedgedoc";
ensureDBOwnership = true;
}];
};
nginx.virtualHosts."${domain}" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://${cfg.settings.host}:${toString cfg.settings.port}";
};
};
services.postgresql = {
enable = true;
ensureDatabases = [ "hedgedoc" ];
ensureUsers = [{
name = "hedgedoc";
ensureDBOwnership = true;
}];
};
services.nginx.virtualHosts."${domain}" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://${cfg.settings.host}:${toString cfg.settings.port}";
};
};
}

View file

@ -6,15 +6,13 @@ let
publicKey = "GCmQs7upvDYFueEfqD2yJkkOZg3K7YaGluWWzdjsyTo=";
in
{
sops.secrets = (
lib.listToAttrs (map
(name: lib.nameValuePair "wireguard_key_${name}" {
sopsFile = ../secrets.yaml;
})
[
"hetzner-ha"
]
)
sops.secrets = lib.listToAttrs (map
(name: lib.nameValuePair "wireguard_key_${name}" {
sopsFile = ../secrets.yaml;
})
[
"hetzner-ha"
]
);
#boot.kernel.sysctl = {
@ -22,24 +20,27 @@ in
# "net.ipv4.conf.hetzner-ha.proxy_arp" = 1;
# "net.ipv4.conf.enp1s0.proxy_arp" = 1;
#};
networking.interfaces.hetzner-ha.proxyARP = true;
networking.interfaces.enp1s0.proxyARP = true;
networking.wireguard.interfaces = {
hetzner-ha = {
ips = [ ];
privateKeyFile = config.sops.secrets.wireguard_key_hetzner-ha.path;
listenPort = listenPort;
networking = {
interfaces = {
hetzner-ha.proxyARP = true;
enp1s0.proxyARP = true;
};
firewall.allowedUDPPorts = [ listenPort ];
wireguard.interfaces = {
hetzner-ha = {
ips = [ ];
privateKeyFile = config.sops.secrets.wireguard_key_hetzner-ha.path;
inherit listenPort;
peers = [{
publicKey = publicKey;
persistentKeepalive = 25;
allowedIPs = [
"159.69.103.126/32"
];
}];
peers = [{
inherit publicKey;
persistentKeepalive = 25;
allowedIPs = [
"159.69.103.126/32"
];
}];
};
};
};
networking.firewall.allowedUDPPorts = [ listenPort ];
}

View file

@ -2,10 +2,14 @@
{
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
boot.initrd.availableKernelModules = [ "xhci_pci" "virtio_pci" "virtio_scsi" "usbhid" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
boot = {
initrd = {
availableKernelModules = [ "xhci_pci" "virtio_pci" "virtio_scsi" "usbhid" "sr_mod" ];
kernelModules = [ ];
};
kernelModules = [ ];
extraModulePackages = [ ];
};
fileSystems = {
"/" = {

View file

@ -23,67 +23,15 @@ in
};
};
services.pretix = {
enable = true;
settings = {
pretix = {
instance_name = "Digitaler Dienst GmbH";
url = "https://${domain}";
registration = false;
password_reset = true;
};
locale = {
default = "de";
timezone = "Europe/Berlin";
};
mail = {
from = "no-reply@tickets.weinturm-open-air.de";
};
redis.location = "unix:///run/redis-pretix/redis.sock?db=0";
celery.backend = "redis+socket:///run/redis-pretix/redis.sock?virtual_host=2";
celery.broker = "redis+socket:///run/redis-pretix/redis.sock?virtual_host=1";
};
nginx = {
enable = true;
inherit domain;
};
gunicorn = {
extraArgs = [
"--workers=${toString gunicornWorkers}"
];
};
};
# Add user to `redis-pretix` group
# to grant access to /run/redis-pretix/redis.sock
users.users.pretix.extraGroups = [ "redis-pretix" ];
services.pretix-banktool = {
enable = true;
days = 14;
secretsFile = config.sops.secrets.pretix-banktool-cfg.path;
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = lib.mkIf cfg.nginx.enable {
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts = {
"${cfg.nginx.domain}" = {
enableACME = true;
forceSSL = true;
kTLS = true;
extraConfig = ''
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;
more_set_headers Referrer-Policy same-origin;
more_set_headers X-Content-Type-Options nosniff;
'';
serverAliases = extraDomains;
};
};
security.acme = {
acceptTerms = true;
defaults.email = lib.mkForce "helfer@weinturm-open-air.de";
};
jalr.mailserver = {
@ -101,8 +49,62 @@ in
spam.enable = false;
};
security.acme = {
acceptTerms = true;
defaults.email = lib.mkForce "helfer@weinturm-open-air.de";
services = {
pretix = {
enable = true;
settings = {
pretix = {
instance_name = "Digitaler Dienst GmbH";
url = "https://${domain}";
registration = false;
password_reset = true;
};
locale = {
default = "de";
timezone = "Europe/Berlin";
};
mail = {
from = "no-reply@tickets.weinturm-open-air.de";
};
redis.location = "unix:///run/redis-pretix/redis.sock?db=0";
celery.backend = "redis+socket:///run/redis-pretix/redis.sock?virtual_host=2";
celery.broker = "redis+socket:///run/redis-pretix/redis.sock?virtual_host=1";
};
nginx = {
enable = true;
inherit domain;
};
gunicorn = {
extraArgs = [
"--workers=${toString gunicornWorkers}"
];
};
};
pretix-banktool = {
enable = true;
days = 14;
secretsFile = config.sops.secrets.pretix-banktool-cfg.path;
};
nginx = lib.mkIf cfg.nginx.enable {
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts = {
"${cfg.nginx.domain}" = {
enableACME = true;
forceSSL = true;
kTLS = true;
extraConfig = ''
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;
more_set_headers Referrer-Policy same-origin;
more_set_headers X-Content-Type-Options nosniff;
'';
serverAliases = extraDomains;
};
};
};
};
}