initial commit

This commit is contained in:
2026-05-14 13:39:10 +02:00
commit 7e444146d2
37 changed files with 1537 additions and 0 deletions

4
.sops.yaml Normal file
View File

@@ -0,0 +1,4 @@
creation_rules:
- path_regex: secrets.yaml$
key_group:
age: "age1d8lk7psq7dc3v0dkdymhyp4afvy056twxlersgzgmnp64h2vw39q22ertf"

24
configuration/acme.nix Normal file
View File

@@ -0,0 +1,24 @@
{ config, lib, ... }:
{
options.domainName = lib.mkOption { type = lib.types.str; };
config = {
domainName = "fredinand.xyz";
security.acme = {
acceptTerms = true;
defaults.email = "admin@${config.domainName}";
certs = {
"${config.domainName}" = {
dnsProvider = "porkbun";
environmentFile = config.sops.secrets."porkbun".path;
group = config.services.nginx.group;
extraDomainNames = [
"mail.${config.domainName}"
"www.${config.domainName}"
];
};
};
};
sops.secrets."porkbun" = {};
};
}

View File

@@ -0,0 +1,2 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKXHTk4/OdvFYjhyg+gfHYumJ79UjAESFjj5iy+u7k17 benjamin@DESKTOP-
RVCJMSB

View File

@@ -0,0 +1,79 @@
{ pkgs, user, inputs, lib, ... }:
{
boot = {
loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};
loader.systemd-boot.configurationLimit = 5;
};
programs.nix-ld.enable = true;
programs.nix-ld.libraries = with pkgs; [
libgcc.lib
glibc_multi.out
];
networking.firewall.enable = true;
networking.networkmanager.enable = true;
time.timeZone = "Europe/Berlin";
i18n.defaultLocale = "en_US.UTF-8";
i18n.extraLocaleSettings = {
LC_ADDRESS = "de_DE.UTF-8";
LC_IDENTIFICATION = "de_DE.UTF-8";
LC_MEASUREMENT = "de_DE.UTF-8";
LC_MONETARY = "de_DE.UTF-8";
LC_NAME = "de_DE.UTF-8";
LC_NUMERIC = "de_DE.UTF-8";
LC_PAPER = "de_DE.UTF-8";
LC_TELEPHONE = "de_DE.UTF-8";
LC_TIME = "de_DE.UTF-8";
};
console.keyMap = "de";
users.users.${user} = {
shell = pkgs.zsh;
isNormalUser = true;
description = "${user}";
group = "${user}";
extraGroups = [
"networkmanager"
"wheel"
];
initialPassword = "123";
openssh.authorizedKeys.keyFiles = [
./benjamin.pub
./philipp.pub
];
};
users.groups.admin = {};
programs.zsh.enable = true;
programs.zsh.enableCompletion = false;
environment.systemPackages = with pkgs; [
neovim
nettools
git
htop
];
nixpkgs.config.allowUnfree = true;
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings.trusted-users = [ "@wheel" ];
nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ];
system.stateVersion = "25.11";
documentation.enable = true;
documentation.man.enable = true;
documentation.doc.enable = true;
boot.kernel.sysctl."kernel.sysrq" = 502;
}

View File

@@ -0,0 +1,7 @@
{ lib, ... }:
let
files = lib.attrNames (lib.filterAttrs (_: type: type == "regular") (builtins.readDir ./.));
dirs = lib.attrNames (lib.filterAttrs (_: type: type == "directory") (builtins.readDir ./.));
nixFiles = lib.filter (f: lib.hasSuffix ".nix" f && !lib.hasSuffix "default.nix" f) files;
imports = lib.map (f: ./${f}) (nixFiles ++ dirs);
in { imports = imports; }

View File

@@ -0,0 +1,13 @@
{ pkgs, config, ... }:
{
services.factorio = {
enable = true;
package = pkgs.factorio-headless.override { versionsJson = ./versions.json; };
extraArgs = [
"--rcon-bind=localhost:27015"
"--rcon-password=$(cat ${config.sops.secrets."factorio/rconpass".path})"
];
};
sops.secrets."factorio/rconpass" = {};
networking.firewall.allowedUDPPorts = [ 34197 ];
}

289
configuration/factorio/update.py Executable file
View File

@@ -0,0 +1,289 @@
#!/usr/bin/env nix-shell
#! nix-shell -i python -p "python3.withPackages (ps: with ps; [ ps.absl-py ps.requests ])"
from collections import defaultdict
import copy
from dataclasses import dataclass
import json
import os.path
from typing import Callable, Dict
from absl import app
from absl import flags
from absl import logging
import requests
FACTORIO_RELEASES = "https://factorio.com/api/latest-releases"
FACTORIO_HASHES = "https://factorio.com/download/sha256sums/"
FLAGS = flags.FLAGS
flags.DEFINE_string("out", "", "Output path for versions.json.")
flags.DEFINE_list(
"release_type",
"",
"If non-empty, a comma-separated list of release types to update (e.g. alpha).",
)
flags.DEFINE_list(
"release_channel",
"",
"If non-empty, a comma-separated list of release channels to update (e.g. experimental).",
)
@dataclass
class System:
nix_name: str
url_name: str
tar_name: str
@dataclass
class ReleaseType:
name: str
hash_filename_format: list[str]
needs_auth: bool = False
@dataclass
class ReleaseChannel:
name: str
FactorioVersionsJSON = Dict[str, Dict[str, str]]
OurVersionJSON = Dict[str, Dict[str, Dict[str, Dict[str, str]]]]
FactorioHashes = Dict[str, str]
SYSTEMS = [
System(nix_name="x86_64-linux", url_name="linux64", tar_name="x64"),
]
RELEASE_TYPES = [
ReleaseType(
"alpha",
needs_auth=True,
hash_filename_format=["factorio_linux_{version}.tar.xz"],
),
ReleaseType("demo", hash_filename_format=["factorio-demo_linux_{version}.tar.xz"]),
ReleaseType(
"headless",
hash_filename_format=[
"factorio-headless_linux_{version}.tar.xz",
"factorio_headless_x64_{version}.tar.xz",
],
),
ReleaseType(
"expansion",
needs_auth=True,
hash_filename_format=["factorio-space-age_linux_{version}.tar.xz"],
),
]
RELEASE_CHANNELS = [
ReleaseChannel("experimental"),
ReleaseChannel("stable"),
]
def find_versions_json() -> str:
if FLAGS.out:
return FLAGS.out
try_paths = ["pkgs/by-name/fa/factorio/versions.json", "versions.json"]
for path in try_paths:
if os.path.exists(path):
return path
raise Exception(
"Couldn't figure out where to write versions.json; try specifying --out"
)
def fetch_versions() -> FactorioVersionsJSON:
return json.loads(requests.get(FACTORIO_RELEASES).text)
def fetch_hashes() -> FactorioHashes:
resp = requests.get(FACTORIO_HASHES)
resp.raise_for_status()
out = {}
for ln in resp.text.split("\n"):
ln = ln.strip()
if not ln:
continue
sha256, filename = ln.split()
out[filename] = sha256
return out
def generate_our_versions(factorio_versions: FactorioVersionsJSON) -> OurVersionJSON:
def rec_dd():
return defaultdict(rec_dd)
output = rec_dd()
# Deal with times where there's no experimental version
for rc in RELEASE_CHANNELS:
if rc.name not in factorio_versions or not factorio_versions[rc.name]:
factorio_versions[rc.name] = factorio_versions["stable"]
for rt in RELEASE_TYPES:
if (
rt.name not in factorio_versions[rc.name]
or not factorio_versions[rc.name][rt.name]
):
factorio_versions[rc.name][rt.name] = factorio_versions["stable"][
rt.name
]
for system in SYSTEMS:
for release_type in RELEASE_TYPES:
for release_channel in RELEASE_CHANNELS:
version = factorio_versions[release_channel.name].get(release_type.name)
if version is None:
continue
this_release = {
"name": f"factorio_{release_type.name}_{system.tar_name}-{version}.tar.xz",
"url": f"https://factorio.com/get-download/{version}/{release_type.name}/{system.url_name}",
"version": version,
"needsAuth": release_type.needs_auth,
"candidateHashFilenames": [
fmt.format(version=version)
for fmt in release_type.hash_filename_format
],
"tarDirectory": system.tar_name,
}
output[system.nix_name][release_type.name][release_channel.name] = (
this_release
)
return output
def iter_version(
versions: OurVersionJSON,
it: Callable[[str, str, str, Dict[str, str]], Dict[str, str]],
) -> OurVersionJSON:
versions = copy.deepcopy(versions)
for system_name, system in versions.items():
for release_type_name, release_type in system.items():
for release_channel_name, release in release_type.items():
release_type[release_channel_name] = it(
system_name, release_type_name, release_channel_name, dict(release)
)
return versions
def merge_versions(old: OurVersionJSON, new: OurVersionJSON) -> OurVersionJSON:
"""Copies already-known hashes from version.json to avoid having to re-fetch."""
def _merge_version(
system_name: str,
release_type_name: str,
release_channel_name: str,
release: Dict[str, str],
) -> Dict[str, str]:
old_system = old.get(system_name, {})
old_release_type = old_system.get(release_type_name, {})
old_release = old_release_type.get(release_channel_name, {})
if FLAGS.release_type and release_type_name not in FLAGS.release_type:
logging.info(
"%s/%s/%s: not in --release_type, not updating",
system_name,
release_type_name,
release_channel_name,
)
return old_release
if FLAGS.release_channel and release_channel_name not in FLAGS.release_channel:
logging.info(
"%s/%s/%s: not in --release_channel, not updating",
system_name,
release_type_name,
release_channel_name,
)
return old_release
if "sha256" not in old_release:
logging.info(
"%s/%s/%s: not copying sha256 since it's missing",
system_name,
release_type_name,
release_channel_name,
)
return release
if not all(
old_release.get(k, None) == release[k] for k in ["name", "version", "url"]
):
logging.info(
"%s/%s/%s: not copying sha256 due to mismatch",
system_name,
release_type_name,
release_channel_name,
)
return release
release["sha256"] = old_release["sha256"]
return release
return iter_version(new, _merge_version)
def fill_in_hash(
versions: OurVersionJSON, factorio_hashes: FactorioHashes
) -> OurVersionJSON:
"""Fill in sha256 hashes for anything missing them."""
def _fill_in_hash(
system_name: str,
release_type_name: str,
release_channel_name: str,
release: Dict[str, str],
) -> Dict[str, str]:
for candidate_filename in release["candidateHashFilenames"]:
if candidate_filename in factorio_hashes:
release["sha256"] = factorio_hashes[candidate_filename]
break
else:
logging.error(
"%s/%s/%s: failed to find any of %s in %s",
system_name,
release_type_name,
release_channel_name,
release["candidateHashFilenames"],
FACTORIO_HASHES,
)
return release
if "sha256" in release:
logging.info(
"%s/%s/%s: skipping fetch, sha256 already present",
system_name,
release_type_name,
release_channel_name,
)
return release
return release
return iter_version(versions, _fill_in_hash)
def main(argv):
factorio_versions = fetch_versions()
factorio_hashes = fetch_hashes()
new_our_versions = generate_our_versions(factorio_versions)
old_our_versions = None
our_versions_path = find_versions_json()
if our_versions_path:
logging.info("Loading old versions.json from %s", our_versions_path)
with open(our_versions_path, "r") as f:
old_our_versions = json.load(f)
if old_our_versions:
logging.info("Merging in old hashes")
new_our_versions = merge_versions(old_our_versions, new_our_versions)
logging.info("Updating hashes from Factorio SHA256")
new_our_versions = fill_in_hash(new_our_versions, factorio_hashes)
with open(our_versions_path, "w") as f:
logging.info("Writing versions.json to %s", our_versions_path)
json.dump(new_our_versions, f, sort_keys=True, indent=2)
f.write("\n")
if __name__ == "__main__":
app.run(main)

View File

@@ -0,0 +1,102 @@
{
"x86_64-linux": {
"alpha": {
"experimental": {
"candidateHashFilenames": [
"factorio_linux_2.0.76.tar.xz"
],
"name": "factorio_alpha_x64-2.0.76.tar.xz",
"needsAuth": true,
"sha256": "b1e50891bdc69cce3fdaee4f840cbe4658e18d465309ac87915b6fc41900754c",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.76/alpha/linux64",
"version": "2.0.76"
},
"stable": {
"candidateHashFilenames": [
"factorio_linux_2.0.76.tar.xz"
],
"name": "factorio_alpha_x64-2.0.76.tar.xz",
"needsAuth": true,
"sha256": "b1e50891bdc69cce3fdaee4f840cbe4658e18d465309ac87915b6fc41900754c",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.76/alpha/linux64",
"version": "2.0.76"
}
},
"demo": {
"experimental": {
"candidateHashFilenames": [
"factorio-demo_linux_2.0.76.tar.xz"
],
"name": "factorio_demo_x64-2.0.76.tar.xz",
"needsAuth": false,
"sha256": "d5caee49636290b678d35adc59d2fc80ecbdbad8bf420731f1317971c89f941b",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.76/demo/linux64",
"version": "2.0.76"
},
"stable": {
"candidateHashFilenames": [
"factorio-demo_linux_2.0.76.tar.xz"
],
"name": "factorio_demo_x64-2.0.76.tar.xz",
"needsAuth": false,
"sha256": "d5caee49636290b678d35adc59d2fc80ecbdbad8bf420731f1317971c89f941b",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.76/demo/linux64",
"version": "2.0.76"
}
},
"expansion": {
"experimental": {
"candidateHashFilenames": [
"factorio-space-age_linux_2.0.76.tar.xz"
],
"name": "factorio_expansion_x64-2.0.76.tar.xz",
"needsAuth": true,
"sha256": "dc24bbbc1b6a619e4425d9a720bcfafce3463d908ab369a6cd1bee1a99332b4f",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.76/expansion/linux64",
"version": "2.0.76"
},
"stable": {
"candidateHashFilenames": [
"factorio-space-age_linux_2.0.76.tar.xz"
],
"name": "factorio_expansion_x64-2.0.76.tar.xz",
"needsAuth": true,
"sha256": "dc24bbbc1b6a619e4425d9a720bcfafce3463d908ab369a6cd1bee1a99332b4f",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.76/expansion/linux64",
"version": "2.0.76"
}
},
"headless": {
"experimental": {
"candidateHashFilenames": [
"factorio-headless_linux_2.0.76.tar.xz",
"factorio_headless_x64_2.0.76.tar.xz"
],
"name": "factorio_headless_x64-2.0.76.tar.xz",
"needsAuth": false,
"sha256": "ef3663f66146d76342f7c09a3f743792636f8cd95c39ea26cfca5bd2e0e92430",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.76/headless/linux64",
"version": "2.0.76"
},
"stable": {
"candidateHashFilenames": [
"factorio-headless_linux_2.0.76.tar.xz",
"factorio_headless_x64_2.0.76.tar.xz"
],
"name": "factorio_headless_x64-2.0.76.tar.xz",
"needsAuth": false,
"sha256": "ef3663f66146d76342f7c09a3f743792636f8cd95c39ea26cfca5bd2e0e92430",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.76/headless/linux64",
"version": "2.0.76"
}
}
}
}

View File

@@ -0,0 +1,93 @@
{ pkgs, lib, config, user, ... }:
let fhs = (pkgs.buildFHSEnv {
name = "node-fhs-env";
targetPkgs = pkgs: (with pkgs; [
libgcc.lib
glibc_multi.out
]);
runScript = "${lib.getExe pkgs.nodejs_25}";
});
in {
# systemd.packages = [
# (pkgs.writeTextFile {
# name = "foundryvtt@.service";
# destination = "/etc/systemd/system/foundryvtt@.service";
# text = ''
# [Unit]
# Description = "foundryvtt %i"
# After=network.target
#
# [Service]
# Type=simple
# User=foundryvtt%i
# DynamicUser=yes
# StateDirectory=foundryvtt%i
# ExecStart=${lib.getExe pkgs.nodejs_25} /var/lib/foundryvtt%i/foundryvtt/main.js --dataPath=/var/lib/foundryvtt%i/foundrydata --port=3000%i
# Restart=on-failure
# RestartSec=30
#
# [Install]
# WantedBy=default.target
# '';
# })
# ];
# security.acme.certs."${config.domainName}".extraDomainNames = [ "foundry.${config.domainName}" ];
# services.nginx.virtualHosts."foundry.${config.domainName}" = {
# useACMEHost = config.domainName;
# addSSL = true;
# locations."/".proxyPass = "http://localhost:30000";
# };
security.acme.certs."${config.domainName}".extraDomainNames = [
"mitchskeller.${config.domainName}"
"inferno.${config.domainName}"
"nixland.${config.domainName}"
];
services.nginx.virtualHosts."mitchskeller.${config.domainName}" = {
useACMEHost = config.domainName;
addSSL = true;
locations."/" = {
# make sure websocket connection is forwarded
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
'';
proxyPass = "http://localhost:30000";
};
};
services.nginx.virtualHosts."inferno.${config.domainName}" = {
useACMEHost = config.domainName;
addSSL = true;
locations."/" = {
# make sure websocket connection is forwarded
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
'';
proxyPass = "http://localhost:30001";
};
};
services.nginx.virtualHosts."nixland.${config.domainName}" = {
useACMEHost = config.domainName;
addSSL = true;
locations."/" = {
# make sure websocket connection is forwarded
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
'';
proxyPass = "http://localhost:30002";
};
};
virtualisation.docker.enable = true;
users.users.${user}.extraGroups = [ "docker" ];
users.users.foundry = {
shell = pkgs.zsh;
isNormalUser = true;
group = "${user}";
openssh.authorizedKeys.keyFiles = [
./mitch.pub
] ++ config.users.users.${user}.openssh.authorizedKeys.keyFiles;
};
}

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBvSPgHhppL0aKGIPJlpu6oWgQ5yEsBYyRExp9J5ofTc michi@Mitch-Rechner

View File

@@ -0,0 +1,10 @@
{ lib
, stdenvNoCC
, unzip
}: stdenvNoCC.mkDerivation (finalAttrs: {
pname = "foundryvtt";
version = "14.360";
src = ./FoundryVTT-Node-14.360.zip;
unpackPhase = "${lib.getExe unzip} $src";
buildPhase = "cp -r . $out";
})

10
configuration/gitea.nix Normal file
View File

@@ -0,0 +1,10 @@
{ config, ... }:
{
services.gitea.enable = true;
security.acme.certs."${config.domainName}".extraDomainNames = [ "git.${config.domainName}" ];
services.nginx.virtualHosts."git.${config.domainName}" = {
useACMEHost = config.domainName;
addSSL = true;
locations."/".proxyPass = "http://localhost:${toString config.services.gitea.settings.server.HTTP_PORT}";
};
}

100
configuration/homepage.nix Normal file
View File

@@ -0,0 +1,100 @@
{ config, ... }:
{
services.homepage-dashboard = {
enable = true;
services = [
{
"Filesharing" = [
{
"Nextcloud" = {
icon = "nextcloud.png";
description = "Nextcloud";
href = "https://${config.services.nextcloud.hostName}/";
widget = {
type = "nextcloud";
url = "https://${config.services.nextcloud.hostName}/";
key = "d8287cef8704255787c7207de873c478";
};
};
}
{
"Syncthing" = {
icon = "syncthing.png";
href = "https://syncthing.${config.domainName}/";
};
}
];
}
{
"Communication" = [
{
"Matrix" = {
icon = "matrix-light.png";
href = "https://${config.services.dendrite.settings.global.server_name}";
description = "Matrix Powered Chat";
};
}
{
"Teamspeak" = {
icon = "teamspeak.png";
description = "connect to ${config.domainName}";
};
}
];
}
{
"Games" = [
{
"Factorio" = {
icon = "https://wiki.factorio.com/images/Factorio-logo.png";
description = "connect to ${config.domainName}";
};
}
];
}
{
"Development" = [
{
"Gitea Server" = {
icon = "gitea.png";
href = "https://git.${config.domainName}";
widget = {
type = "gitea";
url = "https://git.${config.domainName}";
key = "23a6cc9ffd664d9ba815c9c0ab6beb42c13578b2";
};
};
}
];
}
{
"FoundryVTT Server" = [
{
"Mitchs Keller" = {
icon = "https://r2.foundryvtt.com/website-static-public/assets/icons/fvtt.png";
href = "https://mitchskeller.${config.domainName}";
};
}
{
"Inferno" = {
icon = "https://r2.foundryvtt.com/website-static-public/assets/icons/fvtt.png";
href = "https://inferno.${config.domainName}";
};
}
{
"Nixland" = {
icon = "https://r2.foundryvtt.com/website-static-public/assets/icons/fvtt.png";
href = "https://nixland.${config.domainName}";
};
}
];
}
];
};
services.nginx.virtualHosts."${config.domainName}" = {
useACMEHost = config.domainName;
addSSL = true;
locations."/".proxyPass = "http://localhost:${toString config.services.homepage-dashboard.listenPort}";
};
}

37
configuration/matrix.nix Normal file
View File

@@ -0,0 +1,37 @@
{ config, ... }:
{
services.dendrite = {
enable = true;
httpsPort = 44443;
tlsCert = "${config.security.acme.certs.${config.domainName}.directory}/fullchain.pem";
tlsKey = "${config.security.acme.certs.${config.domainName}.directory}/key.pem";
environmentFile = config.sops.secrets."matrix/registration".path;
settings = {
global = {
private_key = config.sops.secrets."matrix/private_key".path;
server_name = "matrix.${config.domainName}";
};
};
};
users.users.matrix.isNormalUser = true;
systemd.services.dendrite.serviceConfig.User = "matrix";
sops.secrets."matrix/registration" = { owner = "matrix"; };
sops.secrets."matrix/private_key" = { owner = "matrix"; };
systemd.services.dendrite.serviceConfig.Group = "nginx";
security.acme.certs."${config.domainName}".extraDomainNames = [ config.services.dendrite.settings.global.server_name ];
services.nginx.virtualHosts.${config.services.dendrite.settings.global.server_name} = {
useACMEHost = config.domainName;
addSSL = true;
locations."/".proxyPass = "https://localhost:${toString config.services.dendrite.httpsPort}";
locations."/.well-known/matrix/server".extraConfig =
let inherit (config.services.dendrite) httpsPort settings;
in ''
add_header Content-Type application/json;
add_header Access-Control-Allow-Origin *;
return 200 '{"m.server":"${settings.global.server_name}:${toString httpsPort}"}';
'';
};
# reachability via ip required for federation
networking.firewall.allowedTCPPorts = [ config.services.dendrite.httpsPort ];
}

View File

@@ -0,0 +1,17 @@
{ pkgs, config, ... }:
{
services.nextcloud = {
enable = true;
hostName = "nextcloud.${config.domainName}";
package = pkgs.nextcloud32;
config.dbtype = "pgsql";
database.createLocally = true;
config.adminpassFile = config.sops.secrets."nextcloud/adminpass".path;
};
sops.secrets."nextcloud/adminpass" = {};
security.acme.certs."${config.domainName}".extraDomainNames = [ config.services.nextcloud.hostName ];
services.nginx.virtualHosts.${config.services.nextcloud.hostName} = {
useACMEHost = config.domainName;
addSSL = true;
};
}

View File

@@ -0,0 +1,68 @@
{ pkgs, config, ... }:
{
services.nginx = {
enable = true;
additionalModules = [ pkgs.nginxModules.rtmp ];
appendConfig = ''
rtmp {
server {
listen 1935;
chunk_size 1024;
application stream {
live on;
meta copy;
hls on;
hls_path /var/www/html/stream/hls;
hls_playlist_length 10s;
hls_fragment 1s;
dash on;
dash_path /var/www/html/stream/dash;
allow publish all;
allow play all;
on_publish http://127.0.0.1:8000/publish;
on_publish_done http://127.0.0.1:8000/unpublish;
}
}
}
'';
virtualHosts."main" = {
listen = [ {addr = "0.0.0.0"; port = 80; } ];
locations = {
"/" = {
root = ./html;
index = "index.html";
};
# "/rtmp_stat" = {
# rtmp_stat = "all";
# rtmp_stat_stylesheet = "/stat.xsl";
# };
"/hls" = {
# types = {
# "application/vnd.apple.mpegurl" = "m3u8";
# "video/mp2t" = "ts";
# };
root = "/tmp";
};
"/dash" = {
root = "/tmp";
};
"/watch" = {
root = "/usr/local/nginx/html";
# try_files = "$uri /watch/index.html";
};
# # some issue with the go server not handeling this well even with prefix (in config.toml) set correctly
# "/auth" = {
# basicAuth.admin = "123g";
# proxyPass = "http://localhost:${toString config.services.rtmp-auth.frontendPort}/auth";
# };
};
};
};
systemd.tmpfiles.rules = [
"d /var/www/html/stream/hls 0755 ${config.services.nginx.user} ${config.services.nginx.user} - -"
"d /var/www/html/stream/dash 0755 ${config.services.nginx.user} ${config.services.nginx.user} - -"
];
}

View File

@@ -0,0 +1,20 @@
{ pkgs, ... }:
{
services.nginx = {
enable = true;
additionalModules = [ pkgs.nginxModules.rtmp ];
appendConfig = ''
rtmp {
server {
listen 1935;
chunk_size 1024;
application stream {
live on;
}
}
}
'';
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDXwc0kkJ5ID0VWDZJ0jW+xdHlVu5KZE0H5RyLka5SrMUaDlWPvHaveEwN8Ty6gGTabzMPoDxd7Tf8xH9w8zTlNmF968a7YU+sB7ChJ9pmFFZZ7EVrWAKNg8FYYDb4Swqd3b+eIlWLz9nA1vdqo2yAUXx7RJrGB6qzI76Jb9tuyX218YygGVOjQoeUWDAA6aJPrcyJ/GzwBfLlC1Iu1+z9FHo/E+sF19MpnE57D/eEbrpTAG+DvlHsZYZRW/stbNV7QXNmQlPjWiuy2cKindOzJqEnD6JQjut9xUg/hObHfZi6a1erJ+RLsX63IMVxKun2x4hTXBCrMwPIGLpj4Q+HYzfhhYT+tQs4h4+unon6YIM0ZysTFvssM2ZTQQRFkKe2SwP4X/ZQcon3p8/foXvNJ9Ba/D8jsGyL8ss2vvciFW3kO7laiVIcLSuVDBaRQ6L4JOUKH4X1y4rTKDkaL2TCzE+Qa2oZT5QfN3zYz4lbyfintY68SUWtCRa4lJgelJoc= philipp@arch

30
configuration/pihole.nix Normal file
View File

@@ -0,0 +1,30 @@
{ config, ... }:
let port = "9123";
host = "pihole.${config.domainName}";
in {
services.pihole-ftl = {
enable = false;
openFirewallDNS = true;
lists = [ {
url = "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt";
type = "block";
enabled = true;
description = "hagezi blocklist";
} ];
settings.dns.upstreams = [ "9.9.9.9" "1.1.1.1" ];
};
services.pihole-web = {
enable = config.services.pihole-ftl.enable;
ports = [ "${port}s" ];
hostName = host;
};
security.acme.certs."${config.domainName}".extraDomainNames = [ host ];
services.nginx.virtualHosts.${host} = {
# TODO get secrets from sops
basicAuth.admin = "";
useACMEHost = config.domainName;
addSSL = true;
locations."/".proxyPass = "https://localhost:${port}";
};
}

View File

@@ -0,0 +1,17 @@
[http]
# List of RTMP apps
applications = ["stream"]
# Path prefix to allow frontend to run on a subpath
prefix = ""
# Allow CSRF cookie to be sent across http-connection, not recommended for production
insecure = true
[store]
# Set store backend (file|consul)
#backend = "file"
[store.file]
# Configure file storage path relative to working directory
#path = "store.db"

View File

@@ -0,0 +1,7 @@
{
# imports = [
# ./module.nix
# ];
#
# services.rtmp-auth.enable = true;
}

View File

@@ -0,0 +1,40 @@
{ pkgs, lib, config, ... }:
let cfg = config.services.rtmp-auth; in
{
options.services.rtmp-auth = {
enable = lib.mkEnableOption "rtmp-auth";
package = lib.mkOption {
description = "package to use";
default = pkgs.callPackage ./package.nix {};
type = lib.types.package;
};
host = lib.mkOption {
description = "hostname or address to bind to";
default = "localhost";
type = lib.types.str;
};
apiPort = lib.mkOption {
description = "portnumber for the api";
default = 8000;
type = lib.types.port;
};
frontendPort = lib.mkOption {
description = "portnumber for the frontend";
default = 8001;
type = lib.types.port;
};
};
config = lib.mkIf cfg.enable {
systemd.services.rtmp-auth = {
wantedBy = [ "default.target" ];
requiredBy = [ "network.target" ];
description = "rtmp authentication";
serviceConfig = {
Type = "simple";
ExecStart = ''${pkgs.lib.getExe cfg.package} -config ${./config.toml} -apiAddr "${cfg.host}:${toString cfg.apiPort}" -frontendAddr "${cfg.host}:${toString cfg.frontendPort}" '';
Restart = "on-failure";
RestartSec = 30;
};
};
};
}

View File

@@ -0,0 +1,28 @@
{ lib
, buildGoModule
, fetchFromGitHub
, statik
, protobuf
, protoc-gen-go
}: buildGoModule (finalAttrs: {
pname = "rtmp-auth";
version = "1.1.0";
src = ./src; /* fetchFromGitHub {
owner = "voc";
repo = "${finalAttrs.pname}";
rev = "master";
hash = "sha256-71PvNBKdvkNSqwCHWZZVAHPa1eEx1Ba3RZqmLy4CBn8=";
};
*/
preBuild = ''
PATH=$PATH:${protoc-gen-go}/bin ${lib.getExe protobuf} -I=storage/ --go_opt=paths=source_relative --go_out=storage/ storage/storage.proto
${lib.getExe statik} -f -src=public/ -dest=.
'';
vendorHash = "sha256-rZZMLZtCvXJmMKYr4rPLTaHqtV6QouKClZgRYlJM1sw=";
meta.mainProgram = finalAttrs.pname;
})

8
configuration/sops.nix Normal file
View File

@@ -0,0 +1,8 @@
{ inputs, user, ... }:
{
imports = [ inputs.sops-nix.nixosModules.sops ];
sops.defaultSopsFile = ../secrets.yaml;
sops.defaultSopsFormat = "yaml";
sops.age.keyFile = "/home/${user}/.config/sops/age/keys.txt";
}

View File

@@ -0,0 +1,11 @@
{ config, ... }:
{
services.syncthing.enable = true;
services.syncthing.guiAddress = "0.0.0.0:8384";
security.acme.certs."${config.domainName}".extraDomainNames = [ "syncthing.${config.domainName}" ];
services.nginx.virtualHosts."syncthing.${config.domainName}" = {
useACMEHost = config.domainName;
addSSL = true;
locations."/".proxyPass = "http://localhost:8384";
};
}

View File

@@ -0,0 +1,34 @@
{ pkgs, lib, system, ... }:
let ts = pkgs.callPackage ./package.nix { inherit system; };
in {
systemd.services.teamspeak6-server = {
description = "Teamspeak6 voice communication server daemon";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment.LD_LIBRARY_PATH="${ts}/lib";
serviceConfig = {
ExecStart = ''
${lib.getExe ts} --accept-license --db-sql-create-path ${ts}/share/teamspeak/sql/create_sqlite/ --db-sql-path ${ts}/share/teamspeak/sql/ --log-path /var/lib/teamspeak/
'';
StateDirectory = "teamspeak";
WorkingDirectory = "/var/lib/teamspeak";
DynamicUser = true;
User = "teamspeak";
Group = "teamspeak";
Restart = "on-failure";
};
};
networking.firewall.allowedTCPPorts = [
30033 # file transfer
9987 # voice
10080 # server query http
10022 # server query ssh
];
networking.firewall.allowedUDPPorts = [
30033 # file transfer
9987 # voice
10080 # server query http
10022 # server query ssh
];
}

View File

@@ -0,0 +1,31 @@
{ stdenvNoCC
, libssh
, system
}: let
currentSystem = {
"x86_64-linux" = { arch = "amd64"; os = "linux"; };
"aarch64-linux" = { arch = "arm64"; os = "linux"; };
}.${system};
in stdenvNoCC.mkDerivation (finalAttrs: {
name = "teamspeak6-server";
version = "v6.0.0-beta10";
src = fetchTarball {
url = "https://github.com/teamspeak/${finalAttrs.name}/releases/download/${finalAttrs.version}/${finalAttrs.name}-${currentSystem.os}-${currentSystem.arch}.tar.xz";
sha256 = "0lcx1zsab951dywjq6wjkqa9ckzpy6wszvgyjaa97ad8mkk4vdfk";
};
builtInputs = [ libssh ];
installPhase = ''
mkdir -p $out/bin $out/lib $out/share/teamspeak/
cp libtsdb_sqlite3.so $out/lib/
cp libtsdb_mariadb.so $out/lib/
cp tsserver $out/bin
cp -r serverquerydocs $out/share/teamspeak
cp -r sql $out/share/teamspeak
cp -r doc $out/share/teamspeak
cp CHANGELOG $out/share/teamspeak
cp LICENSE $out/share/teamspeak
cp THIRD_PARTY_LICENSES $out/share/teamspeak
'';
meta.mainProgram = "tsserver";
})

122
flake.lock generated Normal file
View File

@@ -0,0 +1,122 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixlib": {
"locked": {
"lastModified": 1736643958,
"narHash": "sha256-tmpqTSWVRJVhpvfSN9KXBvKEXplrwKnSZNAoNPf/S/s=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "1418bc28a52126761c02dd3d89b2d8ca0f521181",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixos-generators": {
"inputs": {
"nixlib": "nixlib",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1769813415,
"narHash": "sha256-nnVmNNKBi1YiBNPhKclNYDORoHkuKipoz7EtVnXO50A=",
"owner": "nix-community",
"repo": "nixos-generators",
"rev": "8946737ff703382fda7623b9fab071d037e897d5",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixos-generators",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1775710090,
"narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4c1018dae018162ec878d42fec712642d214fdfa",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixos-generators": "nixos-generators",
"nixpkgs": "nixpkgs",
"sops-nix": "sops-nix",
"systems": "systems"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1777944972,
"narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "c591bf665727040c6cc5cb409079acb22dcce33c",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

48
flake.nix Normal file
View File

@@ -0,0 +1,48 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixos-generators = {
url = "github:nix-community/nixos-generators";
inputs.nixpkgs.follows = "nixpkgs";
};
systems.url = "github:nix-systems/default";
flake-utils = {
url = "github:numtide/flake-utils";
inputs.systems.follows = "systems";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, nixos-generators, ... }@inputs:
inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
user = "admin";
self = inputs.self.packages.${system};
in
{
packages = {
nixosConfigurations = builtins.mapAttrs (name: modules:
nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs self user system; };
inherit system modules;
}
) self.nixosModules;
nixosModules = (builtins.mapAttrs (name: modules: [ ./configuration ./host/${name} ] ++ modules) {
server = [ ] ;
});
image = nixos-generators.nixosGenerate {
system = "x86_64-linux";
format = "qcow-efi";
specialArgs = { inherit inputs self user system; };
modules = self.nixosModules.server;
};
};
});
}

9
host/server/default.nix Normal file
View File

@@ -0,0 +1,9 @@
{
imports = [
./hardware-configuration.nix
];
networking = {
hostId = "8bccc72b";
hostName = "server";
};
}

View File

@@ -0,0 +1,24 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/profiles/qemu-guest.nix")
];
boot.initrd.availableKernelModules = [ "ata_piix" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/b76f5c4c-3d9f-4d26-a698-4d1c3bb60d3a";
fsType = "ext4";
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

23
secrets.yaml Normal file
View File

@@ -0,0 +1,23 @@
nextcloud:
adminpass: ENC[AES256_GCM,data:rLczsAngJv8VVa+7vquHGezRXSt1PxQ6Nr5MzQqqgfu2EP2TC8/qk/LnSqw9YyAIGFZ6lZVX2Qg/Yyly3qSukQ==,iv:TFx10AS2mm1VhlhOH0eGfxWyP5epNR2nnzny3hYlgbw=,tag:cOh+FxRRVEsZL2TiUFS69A==,type:str]
matrix:
registration: ENC[AES256_GCM,data:AVnoSkwClOPw5tYdUvHWKGv5vDqRgT7C4tIzEdoOTrS7O+UzSNMwYZVdb9drNhLLBdOOsROaEoKD6/Jnm53BwpRBcaf1m+M=,iv:J/xK1/UnZ9SF7o4CbLUEIzmelq9QmloEB+FqyHWGUvg=,tag:E1BLQZDc74xyzoKyumrNlw==,type:str]
private_key: ENC[AES256_GCM,data:FMg3JcD6GG4ajPMqXl5XGgC5RDOzkYiA2kHkkkEek8TGcVMoqK/cOrNPQ03+BfYAHaaCXqmmjbIkkDuFSMZgwQ0wKBLHdAtLEsXFnRk3jYgMkP8ftLNlePMy0B/BVrD783C0rsOQrfs7iaj0Ss+PsKvgnwxxMEni5GmjBVyAqwvVtm3kMTwi/A==,iv:alZ2U+zuheJu9/c9ktju13jdvLfkCKsjx8j6KSsFlfw=,tag:lb93QTe62qJDPzeSK6nd9Q==,type:str]
factorio:
rconpass: ENC[AES256_GCM,data:kV7bDnJ3JAnm+snScTCoDW/oATdpkQ+FzbpL60i/pJ4=,iv:TXag8LtF0loSoVN8QO6BD92a+/+8gsEYyeEom4kJdFA=,tag:MXWzxZ42L3vFOWcy8rx5tg==,type:str]
porkbun: ENC[AES256_GCM,data:e49mPfmaDDCKUQ+LnM7YobTkDs6Kt++mNDSDNiSfc6wgZzcBbzMNMiuDO4O7h+CFnMg4qQsXGf5lXw1tnUfMKd2i32EBIzQGJs7qC5Ioydt33rSlTkWmiQM0LKqyd1+Vewv6vHiombFcU38ZLDw30Buy3yNmXOMfzJCQNEGNNafRNH0OdjTReW3miWN4852YXugje6k64ZNCC58htUyx+C0a1fIawPySo1zG+xCzP04=,iv:I3wBqi3qRB3pHNDUklB8mHdwXgOQbvuzPc78rQwAJm0=,tag:c258l7FcCygWKFqXr4SD7g==,type:str]
sops:
age:
- recipient: age1d8lk7psq7dc3v0dkdymhyp4afvy056twxlersgzgmnp64h2vw39q22ertf
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhbGp5ZkJQUlZ5L0RyQXJ4
QS9lZnpkWGt2ZEJwV1VSWVlVK3JQVzlCYVVJCnc0UXhOczhMYS9uQmFHZWRyelor
bnJQc3V2MmI1bjdyTllnSmdOdFIvd2MKLS0tIEZKM1ZicUEvWHNLNVZyNHRlekhX
bUVpTTk0R2wvb05qM3NycTY1ZVh1N3MKd/ziZmsm09Nn4p01q8ZU9MrdZBs32vrF
7BlJJUtDEyz2JVpoj0FUbQzngOYxfWeta7+td4OyYog3ktJdTfwwDA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-14T11:07:48Z"
mac: ENC[AES256_GCM,data:xDm/vCRm/HSRPWD75WmE25gPOy465uSD8mAslak6ziFBJKd689LzyaeqhIgZPLGVQsaXYISTJK7faRLOlyE9S08AoC9DQ1JgBiEB535OGGBg/RowFwe7/p3C8SupstbHuXjSX1Sp1uz+3mcpgoDBAcxelM07WheSQitnBe2dDkI=,iv:D5+L0VD3tXeVw0V37LYFuhLPcKN/yUX4BbyYbEdnzLM=,tag:OYExfEWhBuZhY/y+fONJVA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.2