initial commit
This commit is contained in:
4
.sops.yaml
Normal file
4
.sops.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
creation_rules:
|
||||||
|
- path_regex: secrets.yaml$
|
||||||
|
key_group:
|
||||||
|
age: "age1d8lk7psq7dc3v0dkdymhyp4afvy056twxlersgzgmnp64h2vw39q22ertf"
|
||||||
24
configuration/acme.nix
Normal file
24
configuration/acme.nix
Normal 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" = {};
|
||||||
|
};
|
||||||
|
}
|
||||||
2
configuration/benjamin.pub
Normal file
2
configuration/benjamin.pub
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKXHTk4/OdvFYjhyg+gfHYumJ79UjAESFjj5iy+u7k17 benjamin@DESKTOP-
|
||||||
|
RVCJMSB
|
||||||
79
configuration/configuration.nix
Normal file
79
configuration/configuration.nix
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
7
configuration/default.nix
Normal file
7
configuration/default.nix
Normal 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; }
|
||||||
13
configuration/factorio/default.nix
Normal file
13
configuration/factorio/default.nix
Normal 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
289
configuration/factorio/update.py
Executable 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)
|
||||||
102
configuration/factorio/versions.json
Normal file
102
configuration/factorio/versions.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
configuration/foundryvtt/default.nix
Normal file
93
configuration/foundryvtt/default.nix
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
1
configuration/foundryvtt/mitch.pub
Normal file
1
configuration/foundryvtt/mitch.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBvSPgHhppL0aKGIPJlpu6oWgQ5yEsBYyRExp9J5ofTc michi@Mitch-Rechner
|
||||||
10
configuration/foundryvtt/package.nix
Normal file
10
configuration/foundryvtt/package.nix
Normal 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
10
configuration/gitea.nix
Normal 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
100
configuration/homepage.nix
Normal 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
37
configuration/matrix.nix
Normal 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 ];
|
||||||
|
}
|
||||||
17
configuration/nextcloud.nix
Normal file
17
configuration/nextcloud.nix
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
68
configuration/nginx/backup.nix
Normal file
68
configuration/nginx/backup.nix
Normal 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} - -"
|
||||||
|
];
|
||||||
|
}
|
||||||
20
configuration/nginx/default.nix
Normal file
20
configuration/nginx/default.nix
Normal 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 ];
|
||||||
|
}
|
||||||
BIN
configuration/nginx/html/Fadefillo_Banner_2.png
Normal file
BIN
configuration/nginx/html/Fadefillo_Banner_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
configuration/nginx/html/Fadefillo_T-Shirt_3.png
Normal file
BIN
configuration/nginx/html/Fadefillo_T-Shirt_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
configuration/nginx/html/Rockabilly.ttf
Normal file
BIN
configuration/nginx/html/Rockabilly.ttf
Normal file
Binary file not shown.
BIN
configuration/nginx/html/favicon.ico
Normal file
BIN
configuration/nginx/html/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
228
configuration/nginx/html/index.html
Normal file
228
configuration/nginx/html/index.html
Normal file
File diff suppressed because one or more lines are too long
1
configuration/philipp.pub
Normal file
1
configuration/philipp.pub
Normal 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
30
configuration/pihole.nix
Normal 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}";
|
||||||
|
};
|
||||||
|
}
|
||||||
17
configuration/rtmp-auth/config.toml
Normal file
17
configuration/rtmp-auth/config.toml
Normal 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"
|
||||||
7
configuration/rtmp-auth/default.nix
Normal file
7
configuration/rtmp-auth/default.nix
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
# imports = [
|
||||||
|
# ./module.nix
|
||||||
|
# ];
|
||||||
|
#
|
||||||
|
# services.rtmp-auth.enable = true;
|
||||||
|
}
|
||||||
40
configuration/rtmp-auth/module.nix
Normal file
40
configuration/rtmp-auth/module.nix
Normal 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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
28
configuration/rtmp-auth/package.nix
Normal file
28
configuration/rtmp-auth/package.nix
Normal 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
8
configuration/sops.nix
Normal 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";
|
||||||
|
}
|
||||||
|
|
||||||
11
configuration/syncthing.nix
Normal file
11
configuration/syncthing.nix
Normal 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";
|
||||||
|
};
|
||||||
|
}
|
||||||
34
configuration/teamspeak/default.nix
Normal file
34
configuration/teamspeak/default.nix
Normal 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
|
||||||
|
];
|
||||||
|
}
|
||||||
31
configuration/teamspeak/package.nix
Normal file
31
configuration/teamspeak/package.nix
Normal 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
122
flake.lock
generated
Normal 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
48
flake.nix
Normal 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
9
host/server/default.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./hardware-configuration.nix
|
||||||
|
];
|
||||||
|
networking = {
|
||||||
|
hostId = "8bccc72b";
|
||||||
|
hostName = "server";
|
||||||
|
};
|
||||||
|
}
|
||||||
24
host/server/hardware-configuration.nix
Normal file
24
host/server/hardware-configuration.nix
Normal 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
23
secrets.yaml
Normal 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
|
||||||
Reference in New Issue
Block a user