add jellyfin and rclone fuse mount capability finally
This commit is contained in:
parent
8ac2ec8520
commit
a5e11b9918
|
@ -0,0 +1,6 @@
|
|||
[Media]
|
||||
type = webdav
|
||||
url = https://storage-webdav.owo.monster/Media/
|
||||
vendor = other
|
||||
user = media
|
||||
pass = MEDIA_PASSWORD
|
100
hosts/hetzner-arm/containers/jellyfin/default.nix
Normal file
100
hosts/hetzner-arm/containers/jellyfin/default.nix
Normal file
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
self,
|
||||
hostPath,
|
||||
tree,
|
||||
inputs,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
containerName = "jellyfin";
|
||||
|
||||
containerAddresses = import "${hostPath}/data/containerAddresses.nix";
|
||||
|
||||
hostIP = containerAddresses.host;
|
||||
containerIP = containerAddresses.containers.${containerName};
|
||||
in {
|
||||
containers.jellyfin = {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostAddress = hostIP;
|
||||
localAddress = containerIP;
|
||||
|
||||
bindMounts = {
|
||||
"/dev/fuse" = {
|
||||
hostPath = "/dev/fuse";
|
||||
isReadOnly = false;
|
||||
};
|
||||
};
|
||||
|
||||
# Allow rclone mount in container
|
||||
allowedDevices = [
|
||||
{
|
||||
modifier = "rwm";
|
||||
node = "/dev/fuse";
|
||||
}
|
||||
{
|
||||
modifier = "rwm";
|
||||
node = "/dev/mapper/control";
|
||||
}
|
||||
];
|
||||
|
||||
specialArgs = {
|
||||
inherit inputs;
|
||||
inherit tree;
|
||||
inherit self;
|
||||
inherit hostPath;
|
||||
};
|
||||
|
||||
config = {...}: {
|
||||
nixpkgs.pkgs = pkgs;
|
||||
|
||||
imports = with tree;
|
||||
[
|
||||
presets.nixos.containerBase
|
||||
./secrets.nix
|
||||
]
|
||||
++ (with hosts.hetzner-arm.containers.jellyfin.profiles; [
|
||||
mediaMount
|
||||
jellyfin
|
||||
restic
|
||||
]);
|
||||
|
||||
home-manager.users.root.home.stateVersion = "23.05";
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."jellyfin.owo.monster" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
extraConfig = ''
|
||||
client_max_body_size 512M;
|
||||
|
||||
# Security / XSS Mitigation Headers
|
||||
# NOTE: X-Frame-Options may cause issues with the webOS app
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-XSS-Protection "0"; # Do NOT enable. This is obsolete/dangerous
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
# COOP/COEP. Disable if you use external plugins/images/assets
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
||||
|
||||
# Permissions policy. May cause issues on some clients
|
||||
add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), display-capture=(), document-domain=(), encrypted-media=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), payment=(), publickey-credentials-get=(), serial=(), sync-xhr=(), usb=(), xr-spatial-tracking=()" always;
|
||||
|
||||
# Tell browsers to use per-origin process isolation
|
||||
add_header Origin-Agent-Cluster "?1" always;
|
||||
'';
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://${containerIP}:8096";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
proxy_buffering off;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{...}: {
|
||||
services.jellyfin = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
};
|
||||
users.users.jellyfin.uid = 1000;
|
||||
users.groups.jellyfin.gid = 1000;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
secrets = config.services.secrets.secrets;
|
||||
rcloneMedia = pkgs.writeShellScriptBin "rclone-media" ''
|
||||
${pkgs.rclone}/bin/rclone --config ${secrets.rclone_config.path} "$@"
|
||||
'';
|
||||
mountMedia = pkgs.writeShellScriptBin "mount-media" ''
|
||||
${rcloneMedia}/bin/rclone-media mount Media: /Media \
|
||||
--allow-other \
|
||||
--uid=${toString config.users.users.jellyfin.uid} \
|
||||
--gid=${toString config.users.groups.jellyfin.gid} \
|
||||
--fast-list \
|
||||
--umask=666 \
|
||||
--log-level=INFO "$@"
|
||||
'';
|
||||
in {
|
||||
environment.systemPackages = with pkgs; [
|
||||
rclone
|
||||
rcloneMedia
|
||||
fuse
|
||||
fuse3
|
||||
mountMedia
|
||||
];
|
||||
|
||||
programs.fuse.userAllowOther = true;
|
||||
|
||||
systemd.services.jellyfin = {
|
||||
wants = ["media-mount.service"];
|
||||
after = ["media-mount.service"];
|
||||
serviceConfig.ReadWritePaths = "/Media";
|
||||
};
|
||||
|
||||
systemd.services.media-mount = {
|
||||
wantedBy = ["jellyfin.service"];
|
||||
partOf = ["jellyfin.service"];
|
||||
path = with pkgs; [
|
||||
fuse
|
||||
fuse3
|
||||
];
|
||||
serviceConfig.ExecStart = "${mountMedia}/bin/mount-media --syslog";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /Media - jellyfin jellyfin"
|
||||
|
||||
"d /root/.config - root root"
|
||||
"d /root/.config/rclone - root root"
|
||||
"L /root/.config/rclone/rclone.conf - - - - ${secrets.rclone_config.path}"
|
||||
];
|
||||
}
|
39
hosts/hetzner-arm/containers/jellyfin/profiles/restic.nix
Normal file
39
hosts/hetzner-arm/containers/jellyfin/profiles/restic.nix
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
secrets = config.services.secrets.secrets;
|
||||
in {
|
||||
environment.systemPackages = with pkgs; [
|
||||
restic
|
||||
(pkgs.writeShellScriptBin "restic-jellyfin" ''
|
||||
env \
|
||||
RESTIC_PASSWORD_FILE=${secrets.restic_password.path} \
|
||||
$(cat ${secrets.restic_env.path}) \
|
||||
${pkgs.restic}/bin/restic $@
|
||||
'')
|
||||
];
|
||||
|
||||
services.restic.backups.jellyfin = {
|
||||
user = "root";
|
||||
paths = [
|
||||
"/var/lib/jellyfin"
|
||||
];
|
||||
|
||||
# repository is overrided in environmentFile to contain auth
|
||||
# make sure to keep up to date when changing repository
|
||||
repository = "rest:https://storage-restic.owo.monster/Jellyfin";
|
||||
passwordFile = "${secrets.restic_password.path}";
|
||||
environmentFile = "${secrets.restic_env.path}";
|
||||
|
||||
pruneOpts = [
|
||||
"--keep-last 5"
|
||||
];
|
||||
|
||||
timerConfig = {
|
||||
OnBootSec = "10m";
|
||||
OnCalendar = "8h";
|
||||
};
|
||||
};
|
||||
}
|
54
hosts/hetzner-arm/containers/jellyfin/secrets.nix
Normal file
54
hosts/hetzner-arm/containers/jellyfin/secrets.nix
Normal file
|
@ -0,0 +1,54 @@
|
|||
{pkgs, ...}: {
|
||||
services.secrets = {
|
||||
enable = true;
|
||||
|
||||
packages = with pkgs; [
|
||||
rclone
|
||||
];
|
||||
|
||||
vaultLogin = {
|
||||
enable = true;
|
||||
loginUsername = "hetzner-arm-container-jellyfin";
|
||||
};
|
||||
|
||||
autoSecrets = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
requiredVaultPaths = [
|
||||
"api-keys/data/storage/webdav/Media"
|
||||
"api-keys/data/storage/restic/Jellyfin"
|
||||
|
||||
"private-public-keys/data/restic/Jellyfin"
|
||||
];
|
||||
|
||||
secrets = {
|
||||
vault_password = {
|
||||
manual = true;
|
||||
};
|
||||
|
||||
rclone_config = {
|
||||
user = "jellyfin";
|
||||
group = "jellyfin";
|
||||
fetchScript = ''
|
||||
cp ${./data/rclone_config.template} "$secretFile"
|
||||
MEDIA_PASSWORD="$(simple_get "/api-keys/storage/webdav/Media" .media)"
|
||||
MEDIA_PASSWORD="$(rclone obscure "$MEDIA_PASSWORD")"
|
||||
sed -i "s/MEDIA_PASSWORD/$MEDIA_PASSWORD/" "$secretFile"
|
||||
'';
|
||||
};
|
||||
|
||||
restic_password = {
|
||||
fetchScript = ''
|
||||
simple_get "/private-public-keys/restic/Jellyfin" .password > "$secretFile"
|
||||
'';
|
||||
};
|
||||
restic_env = {
|
||||
fetchScript = ''
|
||||
RESTIC_PASSWORD=$(simple_get "/api-keys/storage/restic/Jellyfin" .restic)
|
||||
echo "RESTIC_REPOSITORY=rest:https://restic:$RESTIC_PASSWORD@storage-restic.owo.monster/Jellyfin" > "$secretFile"
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.modules) mkIf mkForce;
|
||||
|
||||
mailConfig = config.services.mailserver;
|
||||
in {
|
||||
config = mkIf (mailConfig.enable && mailConfig.roundcube.enable) {
|
||||
services.roundcube = {
|
||||
enable = true;
|
||||
package = mailConfig.roundcube.package;
|
||||
plugins =
|
||||
mailConfig.roundcube.plugins
|
||||
++ [
|
||||
"managesieve"
|
||||
];
|
||||
hostName = "${mailConfig.roundcube.domain}";
|
||||
extraConfig = ''
|
||||
$config['smtp_server'] = "tls://${mailConfig.fqdn}";
|
||||
$config['smtp_user'] = "%u";
|
||||
$config['smtp_pass'] = "%p";
|
||||
$config['managesieve_host'] = 'tls://${mailConfig.fqdn}';
|
||||
${mailConfig.roundcube.extraConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."${mailConfig.roundcube.domain}" = {
|
||||
forceSSL = mkForce mailConfig.roundcube.forceSSL;
|
||||
enableACME = mkForce mailConfig.roundcube.enableACME;
|
||||
};
|
||||
};
|
||||
}
|
269
hosts/hetzner-arm/containers/music/modules/mpd-fork.nix
Normal file
269
hosts/hetzner-arm/containers/music/modules/mpd-fork.nix
Normal file
|
@ -0,0 +1,269 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
name = "mpd";
|
||||
|
||||
uid = config.ids.uids.mpd;
|
||||
gid = config.ids.gids.mpd;
|
||||
cfg = config.services.mpd-fork;
|
||||
|
||||
credentialsPlaceholder = creds: let
|
||||
placeholders =
|
||||
imap0
|
||||
(i: c: ''password "{{password-${toString i}}}@${concatStringsSep "," c.permissions}"'')
|
||||
creds;
|
||||
in
|
||||
concatStringsSep "\n" placeholders;
|
||||
|
||||
mpdConf = pkgs.writeText "mpd.conf" ''
|
||||
# This file was automatically generated by NixOS. Edit mpd's configuration
|
||||
# via NixOS' configuration.nix, as this file will be rewritten upon mpd's
|
||||
# restart.
|
||||
|
||||
music_directory "${cfg.musicDirectory}"
|
||||
playlist_directory "${cfg.playlistDirectory}"
|
||||
${lib.optionalString (cfg.dbFile != null) ''
|
||||
db_file "${cfg.dbFile}"
|
||||
''}
|
||||
state_file "${cfg.dataDir}/state"
|
||||
sticker_file "${cfg.dataDir}/sticker.sql"
|
||||
|
||||
${optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''}
|
||||
${optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''}
|
||||
${optionalString (cfg.fluidsynth) ''
|
||||
decoder {
|
||||
plugin "fluidsynth"
|
||||
soundfont "${pkgs.soundfont-fluid}/share/soundfonts/FluidR3_GM2-2.sf2"
|
||||
}
|
||||
''}
|
||||
|
||||
${optionalString (cfg.credentials != []) (credentialsPlaceholder cfg.credentials)}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in {
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.mpd-fork = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = lib.mdDoc ''
|
||||
Whether to enable MPD, the music player daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkPackageOption pkgs "mpd" {};
|
||||
|
||||
startWhenNeeded = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = lib.mdDoc ''
|
||||
If set, {command}`mpd` is socket-activated; that
|
||||
is, instead of having it permanently running as a daemon,
|
||||
systemd will start it on the first incoming connection.
|
||||
'';
|
||||
};
|
||||
|
||||
musicDirectory = mkOption {
|
||||
type = with types; either path (strMatching "(http|https|nfs|smb)://.+");
|
||||
default = "${cfg.dataDir}/music";
|
||||
defaultText = literalExpression ''"''${dataDir}/music"'';
|
||||
description = lib.mdDoc ''
|
||||
The directory or NFS/SMB network share where MPD reads music from. If left
|
||||
as the default value this directory will automatically be created before
|
||||
the MPD server starts, otherwise the sysadmin is responsible for ensuring
|
||||
the directory exists with appropriate ownership and permissions.
|
||||
'';
|
||||
};
|
||||
|
||||
playlistDirectory = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.dataDir}/playlists";
|
||||
defaultText = literalExpression ''"''${dataDir}/playlists"'';
|
||||
description = lib.mdDoc ''
|
||||
The directory where MPD stores playlists. If left as the default value
|
||||
this directory will automatically be created before the MPD server starts,
|
||||
otherwise the sysadmin is responsible for ensuring the directory exists
|
||||
with appropriate ownership and permissions.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = lib.mdDoc ''
|
||||
Extra directives added to to the end of MPD's configuration file,
|
||||
mpd.conf. Basic configuration like file location and uid/gid
|
||||
is added automatically to the beginning of the file. For available
|
||||
options see {manpage}`mpd.conf(5)`.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/${name}";
|
||||
description = lib.mdDoc ''
|
||||
The directory where MPD stores its state, tag cache, playlists etc. If
|
||||
left as the default value this directory will automatically be created
|
||||
before the MPD server starts, otherwise the sysadmin is responsible for
|
||||
ensuring the directory exists with appropriate ownership and permissions.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = lib.mdDoc "User account under which MPD runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = lib.mdDoc "Group account under which MPD runs.";
|
||||
};
|
||||
|
||||
network = {
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
example = "any";
|
||||
description = lib.mdDoc ''
|
||||
The address for the daemon to listen on.
|
||||
Use `any` to listen on all addresses.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 6600;
|
||||
description = lib.mdDoc ''
|
||||
This setting is the TCP port that is desired for the daemon to get assigned
|
||||
to.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
dbFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "${cfg.dataDir}/tag_cache";
|
||||
defaultText = literalExpression ''"''${dataDir}/tag_cache"'';
|
||||
description = lib.mdDoc ''
|
||||
The path to MPD's database. If set to `null` the
|
||||
parameter is omitted from the configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
credentials = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
passwordFile = mkOption {
|
||||
type = types.path;
|
||||
description = lib.mdDoc ''
|
||||
Path to file containing the password.
|
||||
'';
|
||||
};
|
||||
permissions = let
|
||||
perms = ["read" "add" "control" "admin"];
|
||||
in
|
||||
mkOption {
|
||||
type = types.listOf (types.enum perms);
|
||||
default = ["read"];
|
||||
description = lib.mdDoc ''
|
||||
List of permissions that are granted with this password.
|
||||
Permissions can be "${concatStringsSep "\", \"" perms}".
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
description = lib.mdDoc ''
|
||||
Credentials and permissions for accessing the mpd server.
|
||||
'';
|
||||
default = [];
|
||||
example = [
|
||||
{
|
||||
passwordFile = "/var/lib/secrets/mpd_readonly_password";
|
||||
permissions = ["read"];
|
||||
}
|
||||
{
|
||||
passwordFile = "/var/lib/secrets/mpd_admin_password";
|
||||
permissions = ["read" "add" "control" "admin"];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
fluidsynth = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = lib.mdDoc ''
|
||||
If set, add fluidsynth soundfont and configure the plugin.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# install mpd units
|
||||
systemd.packages = [cfg.package];
|
||||
|
||||
systemd.sockets.mpd = mkIf cfg.startWhenNeeded {
|
||||
wantedBy = ["sockets.target"];
|
||||
listenStreams = [
|
||||
"" # Note: this is needed to override the upstream unit
|
||||
(
|
||||
if pkgs.lib.hasPrefix "/" cfg.network.listenAddress
|
||||
then cfg.network.listenAddress
|
||||
else "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}"
|
||||
)
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services.mpd = {
|
||||
wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
|
||||
|
||||
preStart =
|
||||
''
|
||||
set -euo pipefail
|
||||
install -m 600 ${mpdConf} /run/mpd/mpd.conf
|
||||
''
|
||||
+ optionalString (cfg.credentials != [])
|
||||
(concatStringsSep "\n"
|
||||
(imap0
|
||||
(i: c: ''${pkgs.replace-secret}/bin/replace-secret '{{password-${toString i}}}' '${c.passwordFile}' /run/mpd/mpd.conf'')
|
||||
cfg.credentials));
|
||||
|
||||
serviceConfig = {
|
||||
User = "${cfg.user}";
|
||||
# Note: the first "" overrides the ExecStart from the upstream unit
|
||||
ExecStart = ["" "${cfg.package}/bin/mpd --systemd /run/mpd/mpd.conf"];
|
||||
RuntimeDirectory = "mpd";
|
||||
StateDirectory =
|
||||
[]
|
||||
++ optionals (cfg.dataDir == "/var/lib/${name}") [name]
|
||||
++ optionals (cfg.playlistDirectory == "/var/lib/${name}/playlists") [name "${name}/playlists"]
|
||||
++ optionals (cfg.musicDirectory == "/var/lib/${name}/music") [name "${name}/music"];
|
||||
};
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == name) {
|
||||
"${name}" = {
|
||||
inherit uid;
|
||||
group = cfg.group;
|
||||
extraGroups = ["audio"];
|
||||
description = "Music Player Daemon user";
|
||||
home = "${cfg.dataDir}";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == name) {
|
||||
"${name}".gid = gid;
|
||||
};
|
||||
};
|
||||
}
|
55
hosts/hetzner-arm/containers/roundcube/default.nix
Normal file
55
hosts/hetzner-arm/containers/roundcube/default.nix
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
self,
|
||||
tree,
|
||||
inputs,
|
||||
config,
|
||||
pkgs,
|
||||
hostPath,
|
||||
...
|
||||
}: let
|
||||
containerAddresses = import "${hostPath}/data/containerAddresses.nix";
|
||||
hostIP = containerAddresses.host;
|
||||
containerIP = containerAddresses.containers.roundcube;
|
||||
in {
|
||||
containers.roundcube = {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostAddress = hostIP;
|
||||
localAddress = containerIP;
|
||||
|
||||
specialArgs = {
|
||||
inherit inputs;
|
||||
inherit tree;
|
||||
inherit self;
|
||||
inherit hostPath;
|
||||
};
|
||||
|
||||
config = {...}: {
|
||||
nixpkgs.pkgs = pkgs;
|
||||
|
||||
imports = with tree; [
|
||||
presets.nixos.containerBase
|
||||
|
||||
profiles.nginx
|
||||
profiles.sshd
|
||||
profiles.firewallAllow.ssh
|
||||
|
||||
./profiles/roundcube.nix
|
||||
];
|
||||
|
||||
home-manager.users.root.home.stateVersion = "23.05";
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."mail.owo.monster" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://unix:/var/lib/nixos-containers/roundcube/var/sockets/roundcube.sock";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
hostPath,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.modules) mkForce;
|
||||
|
||||
localContainersAddresses = import "${hostPath}/data/containerAddresses.nix";
|
||||
in {
|
||||
services.roundcube = {
|
||||
enable = true;
|
||||
hostName = "mail.owo.monster";
|
||||
package = pkgs.roundcube.withPlugins (_plugins:
|
||||
with pkgs.roundcubePlugins; [
|
||||
persistent_login
|
||||
]);
|
||||
plugins = [
|
||||
"persistent_login"
|
||||
"managesieve"
|
||||
];
|
||||
|
||||
database = {
|
||||
host = localContainersAddresses.containers.postgresql;
|
||||
passwordFile = builtins.toFile "pw" "";
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
$config['smtp_server'] = "tls://mail.owo.monster";
|
||||
$config['smtp_user'] = "%u";
|
||||
$config['smtp_pass'] = "%p";
|
||||
$config['managesieve_host'] = 'tls://mail.owo.monster';
|
||||
$config['session_lifetime'] = (60 * 24 * 7 * 2); # 2 Weeks
|
||||
$config['product_name'] = 'Chaos Mail';
|
||||
$config['username_domain'] = "owo.monster";
|
||||
$config['username_domain_forced'] = true;
|
||||
$config['log_driver'] = 'syslog';
|
||||
$config['smtp_debug'] = true;
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/sockets - nginx nginx"
|
||||
];
|
||||
|
||||
systemd.services.nginx.serviceConfig.ReadWritePaths = [
|
||||
"/var/sockets"
|
||||
];
|
||||
|
||||
services.nginx.virtualHosts."mail.owo.monster" = {
|
||||
forceSSL = mkForce false;
|
||||
enableACME = mkForce false;
|
||||
extraConfig = "listen unix:/var/sockets/roundcube.sock;";
|
||||
};
|
||||
}
|
|
@ -18,6 +18,7 @@ in {
|
|||
restic_forgejo = restic + 6;
|
||||
restic_caldav = restic + 7;
|
||||
restic_owncast = restic + 8;
|
||||
restic_jellyfin = restic + 9;
|
||||
|
||||
http_music = http + 0;
|
||||
http_public = http + 1;
|
||||
|
|
|
@ -24,6 +24,25 @@ in {
|
|||
hostAddress = hostIP;
|
||||
localAddress = containerIP;
|
||||
|
||||
bindMounts = {
|
||||
"/dev/fuse" = {
|
||||
hostPath = "/dev/fuse";
|
||||
isReadOnly = false;
|
||||
};
|
||||
};
|
||||
|
||||
# Allow rclone mount in container
|
||||
allowedDevices = [
|
||||
{
|
||||
modifier = "rwm";
|
||||
node = "/dev/fuse";
|
||||
}
|
||||
{
|
||||
modifier = "rwm";
|
||||
node = "/dev/mapper/control";
|
||||
}
|
||||
];
|
||||
|
||||
specialArgs = {
|
||||
inherit inputs;
|
||||
inherit tree;
|
||||
|
@ -46,7 +65,11 @@ in {
|
|||
users
|
||||
]);
|
||||
|
||||
environment.systemPackages = with pkgs; [rclone];
|
||||
environment.systemPackages = with pkgs; [
|
||||
rclone
|
||||
fuse
|
||||
fuse3
|
||||
];
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
|
@ -99,6 +122,7 @@ in {
|
|||
"/Forgejo/".proxyPass = "http://${containerIP}:${toString ports.restic_forgejo}";
|
||||
"/CalDAV/".proxyPass = "http://${containerIP}:${toString ports.restic_caldav}";
|
||||
"/Owncast/".proxyPass = "http://${containerIP}:${toString ports.restic_owncast}";
|
||||
"/Jellyfin/".proxyPass = "http://${containerIP}:${toString ports.restic_jellyfin}";
|
||||
};
|
||||
extraConfig = ''
|
||||
client_max_body_size ${clientMaxBodySize};
|
||||
|
|
|
@ -197,6 +197,16 @@ in {
|
|||
"--baseurl=/Owncast/"
|
||||
];
|
||||
}
|
||||
{
|
||||
id = "restic-jellyfin";
|
||||
remote = "StorageBox:Backups/Restic/Jellyfin";
|
||||
type = "restic";
|
||||
extraArgs = [
|
||||
"--addr=0.0.0.0:${toString ports.restic_jellyfin}"
|
||||
"--htpasswd=${secrets.restic_jellyfin_htpasswd.path}"
|
||||
"--baseurl=/Jellyfin/"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"api-keys/data/storage/restic/Forgejo"
|
||||
"api-keys/data/storage/restic/CalDAV"
|
||||
"api-keys/data/storage/restic/Owncast"
|
||||
"api-keys/data/storage/restic/Jellyfin"
|
||||
|
||||
"api-keys/data/storage/webdav/Main"
|
||||
"api-keys/data/storage/webdav/Media"
|
||||
|
@ -169,6 +170,14 @@
|
|||
'';
|
||||
};
|
||||
|
||||
restic_jellyfin_htpasswd = {
|
||||
user = "storage";
|
||||
group = "storage";
|
||||
fetchScript = ''
|
||||
simple_get_htpasswd "/api-keys/storage/restic/Jellyfin" "$secretFile"
|
||||
'';
|
||||
};
|
||||
|
||||
webdav_main_htpasswd = {
|
||||
user = "storage";
|
||||
group = "storage";
|
||||
|
|
9
hosts/hetzner-arm/containers/stream/data/ports.nix
Normal file
9
hosts/hetzner-arm/containers/stream/data/ports.nix
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
mpd = 6600;
|
||||
mpd-opus-low = 4242;
|
||||
mpd-opus-medium = 4243;
|
||||
mpd-opus-high = 4244;
|
||||
mpd-flac = 4245;
|
||||
slskd = 5000;
|
||||
slskd-web = 5001;
|
||||
}
|
9
hosts/hetzner-arm/containers/stream/music/data/ports.nix
Normal file
9
hosts/hetzner-arm/containers/stream/music/data/ports.nix
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
mpd = 6600;
|
||||
mpd-opus-low = 4242;
|
||||
mpd-opus-medium = 4243;
|
||||
mpd-opus-high = 4244;
|
||||
mpd-flac = 4245;
|
||||
slskd = 5000;
|
||||
slskd-web = 5001;
|
||||
}
|
123
hosts/hetzner-arm/containers/stream/music/default.nix
Normal file
123
hosts/hetzner-arm/containers/stream/music/default.nix
Normal file
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
self,
|
||||
hostPath,
|
||||
tree,
|
||||
lib,
|
||||
inputs,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.modules) mkMerge;
|
||||
inherit (lib.lists) forEach;
|
||||
|
||||
containerName = "music";
|
||||
|
||||
containerAddresses = import "${hostPath}/data/containerAddresses.nix";
|
||||
|
||||
hostIP = containerAddresses.host;
|
||||
containerIP = containerAddresses.containers.${containerName};
|
||||
|
||||
ports = import ./data/ports.nix;
|
||||
|
||||
# these secrets should probs be in host but im lazy
|
||||
containerSecrets = config.containers.${containerName}.config.services.secrets.secrets;
|
||||
pathInContainer = path: "/var/lib/nixos-containers/${containerName}" + path;
|
||||
in {
|
||||
containers.music = {
|
||||
autoStart = true;
|
||||
privateNetwork = true;
|
||||
hostAddress = hostIP;
|
||||
localAddress = containerIP;
|
||||
|
||||
specialArgs = {
|
||||
inherit inputs;
|
||||
inherit tree;
|
||||
inherit self;
|
||||
inherit hostPath;
|
||||
};
|
||||
|
||||
config = {...}: {
|
||||
nixpkgs.pkgs = pkgs;
|
||||
|
||||
imports = with tree;
|
||||
[
|
||||
presets.nixos.containerBase
|
||||
|
||||
profiles.nginx
|
||||
profiles.firewallAllow.httpCommon
|
||||
|
||||
./secrets.nix
|
||||
]
|
||||
++ (with hosts.hetzner-arm.containers.music.profiles; [
|
||||
mpd
|
||||
musicSync
|
||||
soulseek
|
||||
]);
|
||||
|
||||
networking.firewall.allowedTCPPorts = with ports; [
|
||||
mpd
|
||||
mpd-opus-low
|
||||
mpd-opus-medium
|
||||
mpd-opus-high
|
||||
mpd-flac
|
||||
slskd
|
||||
slskd-web
|
||||
];
|
||||
|
||||
home-manager.users.root.home.stateVersion = "23.05";
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."soulseek.owo.monster" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://${containerIP}:${toString ports.slskd-web}";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."mpd.owo.monster" = let
|
||||
extraConfig = ''
|
||||
auth_basic "Music Password";
|
||||
auth_basic_user_file ${pathInContainer containerSecrets.music_stream_passwd.path};
|
||||
'';
|
||||
in {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations = mkMerge [
|
||||
{
|
||||
"/flac" = {
|
||||
proxyPass = "http://${containerIP}:${toString ports.mpd-flac}";
|
||||
inherit extraConfig;
|
||||
};
|
||||
}
|
||||
(mkMerge (forEach ["low" "medium" "high"] (quality: {
|
||||
"/opus-${quality}" = {
|
||||
proxyPass = "http://${containerIP}:${toString ports."mpd-opus-${quality}"}";
|
||||
inherit extraConfig;
|
||||
};
|
||||
})))
|
||||
];
|
||||
};
|
||||
|
||||
networking = {
|
||||
nat.forwardPorts = [
|
||||
{
|
||||
sourcePort = ports.mpd;
|
||||
destination = "${containerIP}\:${toString ports.mpd}";
|
||||
}
|
||||
{
|
||||
sourcePort = ports.slskd;
|
||||
destination = "${containerIP}\:${toString ports.slskd}";
|
||||
}
|
||||
];
|
||||
|
||||
firewall.allowedTCPPorts = with ports; [
|
||||
mpd
|
||||
slskd
|
||||
];
|
||||
};
|
||||
}
|
69
hosts/hetzner-arm/containers/stream/music/profiles/mpd.nix
Normal file
69
hosts/hetzner-arm/containers/stream/music/profiles/mpd.nix
Normal file
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
inherit (lib.lists) forEach;
|
||||
|
||||
ports = import ../data/ports.nix;
|
||||
secrets = config.services.secrets.secrets;
|
||||
in {
|
||||
environment.systemPackages = with pkgs; [
|
||||
mpc_cli
|
||||
];
|
||||
|
||||
services.mpd = {
|
||||
enable = true;
|
||||
network.listenAddress = "0.0.0.0";
|
||||
musicDirectory = "/Music";
|
||||
credentials = [
|
||||
{
|
||||
passwordFile = "${secrets.mpd_control_password.path}";
|
||||
permissions = ["read" "add" "control" "admin"];
|
||||
}
|
||||
];
|
||||
extraConfig =
|
||||
''
|
||||
host_permissions "127.0.0.1 read,add,control,admin"
|
||||
samplerate_converter "0"
|
||||
metadata_to_use "title,artist"
|
||||
auto_update "yes"
|
||||
audio_buffer_size "4096"
|
||||
replaygain "track"
|
||||
audio_output_format "44100:16:2"
|
||||
''
|
||||
+ concatStringsSep "\n" (forEach ["low" "medium" "high"] (quality: let
|
||||
bitrates = {
|
||||
"low" = "64";
|
||||
"medium" = "96";
|
||||
"high" = "128";
|
||||
};
|
||||
bitrate = bitrates.${quality};
|
||||
in ''
|
||||
audio_output {
|
||||
type "httpd"
|
||||
name "HTTP Opus ${bitrate}k"
|
||||
encoder "opus"
|
||||
port "${toString ports."mpd-opus-${quality}"}"
|
||||
bitrate "${bitrate}000"
|
||||
format "44100:16:2"
|
||||
always_on "yes"
|
||||
tags "yes"
|
||||
signal "music"
|
||||
}
|
||||
''))
|
||||
+ ''
|
||||
audio_output {
|
||||
type "httpd"
|
||||
name "HTTP FLAC"
|
||||
encoder "flac"
|
||||
port "${toString ports.mpd-flac}"
|
||||
format "44100:16:2"
|
||||
always_on "yes"
|
||||
tags "yes"
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
{pkgs, ...}: let
|
||||
inherit (pkgs) writeShellScriptBin;
|
||||
inherit (builtins) toFile;
|
||||
|
||||
rcloneConfig = toFile "rclone.conf" ''
|
||||
[Music]
|
||||
type = webdav
|
||||
url = https://storage-webdav.owo.monster/MusicRO/
|
||||
vendor = other
|
||||
'';
|
||||
in {
|
||||
environment.systemPackages = with pkgs; [
|
||||
rclone
|
||||
(writeShellScriptBin "rclone-music" ''
|
||||
rclone --config ${rcloneConfig} "$@"
|
||||
'')
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /Music - mpd mpd"
|
||||
];
|
||||
|
||||
systemd.services.music-sync = {
|
||||
wantedBy = ["multi-user.target"];
|
||||
after = ["network.target"];
|
||||
partOf = ["mpd.service"];
|
||||
|
||||
path = with pkgs; [bash rclone];
|
||||
|
||||
script = ''
|
||||
set -e
|
||||
rclone --config ${rcloneConfig} sync Music: /Music
|
||||
chown -R mpd:mpd /Music
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.music-sync = {
|
||||
wantedBy = ["timers.target"];
|
||||
partOf = ["music-sync.service"];
|
||||
timerConfig.OnCalendar = "hourly";
|
||||
};
|
||||
|
||||
systemd.services.mpd = {
|
||||
after = ["music-copy.service"];
|
||||
serviceConfig = {
|
||||
ReadOnlyPaths = "/Music";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
ports = import ../data/ports.nix;
|
||||
secrets = config.services.secrets.secrets;
|
||||
|
||||
inherit (lib.modules) mkForce;
|
||||
in {
|
||||
services.slskd = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
environmentFile = secrets.slskd_env.path;
|
||||
settings = {
|
||||
remote_configuration = false;
|
||||
remote_file_management = true;
|
||||
soulseek = {
|
||||
username = "chaoticryptidz";
|
||||
description = "chaos's soulseek";
|
||||
listen_port = ports.slskd;
|
||||
};
|
||||
web = {
|
||||
port = ports.slskd-web;
|
||||
authentication = {
|
||||
username = "chaos";
|
||||
};
|
||||
};
|
||||
shares.directories = [
|
||||
"/Music"
|
||||
];
|
||||
};
|
||||
nginx = {
|
||||
enable = true; # I don't think this is even cheked
|
||||
domainName = "soulseek.owo.monster";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."soulseek.owo.monster" = {
|
||||
forceSSL = mkForce false;
|
||||
enableACME = mkForce false;
|
||||
};
|
||||
}
|
57
hosts/hetzner-arm/containers/stream/music/secrets.nix
Normal file
57
hosts/hetzner-arm/containers/stream/music/secrets.nix
Normal file
|
@ -0,0 +1,57 @@
|
|||
{pkgs, ...}: {
|
||||
services.secrets = {
|
||||
enable = true;
|
||||
|
||||
vaultLogin = {
|
||||
enable = true;
|
||||
loginUsername = "hetzner-arm-container-music";
|
||||
};
|
||||
|
||||
autoSecrets = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
requiredVaultPaths = [
|
||||
"api-keys/data/mpd"
|
||||
"api-keys/data/music-stream"
|
||||
"passwords/data/soulseek"
|
||||
"passwords/data/slskd"
|
||||
];
|
||||
|
||||
packages = with pkgs; [
|
||||
apacheHttpd
|
||||
];
|
||||
|
||||
secrets = {
|
||||
vault_password = {
|
||||
manual = true;
|
||||
};
|
||||
|
||||
mpd_control_password = {
|
||||
user = "mpd";
|
||||
group = "mpd";
|
||||
fetchScript = ''
|
||||
simple_get "/api-keys/mpd" .password > "$secretFile"
|
||||
'';
|
||||
};
|
||||
music_stream_passwd = {
|
||||
user = "nginx";
|
||||
group = "nginx";
|
||||
fetchScript = ''
|
||||
username=$(simple_get "/api-keys/music-stream" .username)
|
||||
password=$(simple_get "/api-keys/music-stream" .password)
|
||||
htpasswd -bc "$secretFile" "$username" "$password" 2>/dev/null
|
||||
'';
|
||||
};
|
||||
slskd_env = {
|
||||
fetchScript = ''
|
||||
soulseek_password=$(simple_get "/passwords/soulseek" .password)
|
||||
slskd_password=$(simple_get "/passwords/slskd" .password)
|
||||
echo > "$secretFile"
|
||||
echo "SLSKD_SLSK_PASSWORD=$soulseek_password" >> "$secretFile"
|
||||
echo "SLSKD_PASSWORD=$slskd_password" >> "$secretFile"
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
69
hosts/hetzner-arm/containers/stream/profiles/mpd.nix
Normal file
69
hosts/hetzner-arm/containers/stream/profiles/mpd.nix
Normal file
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
inherit (lib.lists) forEach;
|
||||
|
||||
ports = import ../data/ports.nix;
|
||||
secrets = config.services.secrets.secrets;
|
||||
in {
|
||||
environment.systemPackages = with pkgs; [
|
||||
mpc_cli
|
||||
];
|
||||
|
||||
services.mpd = {
|
||||
enable = true;
|
||||
network.listenAddress = "0.0.0.0";
|
||||
musicDirectory = "/Music";
|
||||
credentials = [
|
||||
{
|
||||
passwordFile = "${secrets.mpd_control_password.path}";
|
||||
permissions = ["read" "add" "control" "admin"];
|
||||
}
|
||||
];
|
||||
extraConfig =
|
||||
''
|
||||
host_permissions "127.0.0.1 read,add,control,admin"
|
||||
samplerate_converter "0"
|
||||
metadata_to_use "title,artist"
|
||||
auto_update "yes"
|
||||
audio_buffer_size "4096"
|
||||
replaygain "track"
|
||||
audio_output_format "44100:16:2"
|
||||
''
|
||||
+ concatStringsSep "\n" (forEach ["low" "medium" "high"] (quality: let
|
||||
bitrates = {
|
||||
"low" = "64";
|
||||
"medium" = "96";
|
||||
"high" = "128";
|
||||
};
|
||||
bitrate = bitrates.${quality};
|
||||
in ''
|
||||
audio_output {
|
||||
type "httpd"
|
||||
name "HTTP Opus ${bitrate}k"
|
||||
encoder "opus"
|
||||
port "${toString ports."mpd-opus-${quality}"}"
|
||||
bitrate "${bitrate}000"
|
||||
format "44100:16:2"
|
||||
always_on "yes"
|
||||
tags "yes"
|
||||
signal "music"
|
||||
}
|
||||
''))
|
||||
+ ''
|
||||
audio_output {
|
||||
type "httpd"
|
||||
name "HTTP FLAC"
|
||||
encoder "flac"
|
||||
port "${toString ports.mpd-flac}"
|
||||
format "44100:16:2"
|
||||
always_on "yes"
|
||||
tags "yes"
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
43
hosts/hetzner-arm/containers/stream/profiles/soulseek.nix
Normal file
43
hosts/hetzner-arm/containers/stream/profiles/soulseek.nix
Normal file
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
ports = import ../data/ports.nix;
|
||||
secrets = config.services.secrets.secrets;
|
||||
|
||||
inherit (lib.modules) mkForce;
|
||||
in {
|
||||
services.slskd = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
environmentFile = secrets.slskd_env.path;
|
||||
settings = {
|
||||
remote_configuration = false;
|
||||
remote_file_management = true;
|
||||
soulseek = {
|
||||
username = "chaoticryptidz";
|
||||
description = "chaos's soulseek";
|
||||
listen_port = ports.slskd;
|
||||
};
|
||||
web = {
|
||||
port = ports.slskd-web;
|
||||
authentication = {
|
||||
username = "chaos";
|
||||
};
|
||||
};
|
||||
shares.directories = [
|
||||
"/Music"
|
||||
];
|
||||
};
|
||||
nginx = {
|
||||
enable = true; # I don't think this is even cheked
|
||||
domainName = "soulseek.owo.monster";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."soulseek.owo.monster" = {
|
||||
forceSSL = mkForce false;
|
||||
enableACME = mkForce false;
|
||||
};
|
||||
}
|
57
hosts/hetzner-arm/containers/stream/secrets.nix
Normal file
57
hosts/hetzner-arm/containers/stream/secrets.nix
Normal file
|
@ -0,0 +1,57 @@
|
|||
{pkgs, ...}: {
|
||||
services.secrets = {
|
||||
enable = true;
|
||||
|
||||
vaultLogin = {
|
||||
enable = true;
|
||||
loginUsername = "hetzner-arm-container-music";
|
||||
};
|
||||
|
||||
autoSecrets = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
requiredVaultPaths = [
|
||||
"api-keys/data/mpd"
|
||||
"api-keys/data/music-stream"
|
||||
"passwords/data/soulseek"
|
||||
"passwords/data/slskd"
|
||||
];
|
||||
|
||||
packages = with pkgs; [
|
||||
apacheHttpd
|
||||
];
|
||||
|
||||
secrets = {
|
||||
vault_password = {
|
||||
manual = true;
|
||||
};
|
||||
|
||||
mpd_control_password = {
|
||||
user = "mpd";
|
||||
group = "mpd";
|
||||
fetchScript = ''
|
||||
simple_get "/api-keys/mpd" .password > "$secretFile"
|
||||
'';
|
||||
};
|
||||
music_stream_passwd = {
|
||||
user = "nginx";
|
||||
group = "nginx";
|
||||
fetchScript = ''
|
||||
username=$(simple_get "/api-keys/music-stream" .username)
|
||||
password=$(simple_get "/api-keys/music-stream" .password)
|
||||
htpasswd -bc "$secretFile" "$username" "$password" 2>/dev/null
|
||||
'';
|
||||
};
|
||||
slskd_env = {
|
||||
fetchScript = ''
|
||||
soulseek_password=$(simple_get "/passwords/soulseek" .password)
|
||||
slskd_password=$(simple_get "/passwords/slskd" .password)
|
||||
echo > "$secretFile"
|
||||
echo "SLSKD_SLSK_PASSWORD=$soulseek_password" >> "$secretFile"
|
||||
echo "SLSKD_PASSWORD=$slskd_password" >> "$secretFile"
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -10,5 +10,6 @@
|
|||
piped-fi = "10.0.1.8";
|
||||
caldav = "10.0.1.9";
|
||||
owncast = "10.0.1.10";
|
||||
jellyfin = "10.0.1.11";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ in {
|
|||
"forgejo"
|
||||
"caldav"
|
||||
"owncast"
|
||||
"jellyfin"
|
||||
] (name: ./containers + "/${name}"))
|
||||
++ (with hosts.hetzner-arm.profiles; [
|
||||
staticSites
|
||||
|
|
8
profiles/minimalServer.nix
Normal file
8
profiles/minimalServer.nix
Normal file
|
@ -0,0 +1,8 @@
|
|||
{lib, ...}: let
|
||||
inherit (lib.modules) mkDefault;
|
||||
in {
|
||||
environment.noXlibs = mkDefault true;
|
||||
documentation.man.enable = mkDefault false;
|
||||
documentation.doc.enable = mkDefault false;
|
||||
fonts.fontconfig.enable = mkDefault false;
|
||||
}
|
Loading…
Reference in a new issue