Compare commits

..

2 commits

258 changed files with 3381 additions and 11087 deletions

View file

@ -1,7 +0,0 @@
((rust-mode
. ((eglot-workspace-configuration
. (:rust-analyzer
(:cargo (:features "all")
:check (:command "clippy")
:rustfmt (:overrideCommand ["leptosfmt" "--stdin" "--rustfmt"])
:linkedProjects ["./pkgs/packages/webserver/Cargo.toml"]))))))

View file

@ -1,5 +1,5 @@
keys:
- &tlater 8322CC1D351D8EFE63F3 D27A8CF496E6E265C6A3!
- &tlater B9C7AE554602D82B1AC2CFD0BC7BB2DB17C78E42!
- &server_tlaternet 8a3737d48f1035fe6c3a0a8fd6a1976ca74c7f3b
- &server_hetzner1 0af7641adb8aa843136cf6d047f71da3e5ad79f9
- &server_staging 2f5caa73e7ceea4fcc8d2881fde587e6737d2dbc

View file

@ -1,82 +1,61 @@
{ self, ... }:
{
perSystem =
self,
nixpkgs,
deploy-rs,
system,
...
}:
let
pkgs = nixpkgs.legacyPackages.${system};
statix' = pkgs.statix.overrideAttrs (old: {
patches = old.patches ++ [
(pkgs.fetchpatch {
url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
})
];
});
runNuCheck =
{
inputs',
lib,
pkgs,
...
name,
packages,
check,
}:
let
mkLint =
{
name,
fileset,
checkInputs ? [ ],
script,
}:
pkgs.stdenvNoCC.mkDerivation {
inherit name;
pkgs.stdenvNoCC.mkDerivation {
inherit name;
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.difference fileset (
lib.fileset.fileFilter (
file: file.type != "regular" || file.name == "hardware-configuration.nix"
) ../.
);
};
src = nixpkgs.lib.cleanSourceWith {
src = self;
filter = nixpkgs.lib.cleanSourceFilter;
};
checkInputs = [ pkgs.nushell ] ++ checkInputs;
dontPatch = true;
dontConfigure = true;
dontBuild = true;
dontInstall = true;
dontFixup = true;
doCheck = true;
checkPhase = ''
nu -c '${script}' | tee $out
'';
checkInputs = nixpkgs.lib.singleton pkgs.nushell ++ packages;
dontPatch = true;
dontConfigure = true;
dontBuild = true;
dontInstall = true;
dontFixup = true;
doCheck = true;
};
in
{
checks = {
nix = mkLint {
name = "nix-lints";
fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.;
checkInputs = lib.attrValues {
inherit (pkgs) deadnix nixfmt-rfc-style;
statix = pkgs.statix.overrideAttrs (old: {
patches = old.patches ++ [
(pkgs.fetchpatch {
url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
})
];
});
};
script = /* bash */ ''
statix check **/*.nix
deadnix --fail **/*.nix
nixfmt --check --strict **/*.nix
'';
};
lockfile = mkLint {
name = "nix-lockfile";
fileset = ../flake.lock;
checkInputs = lib.attrValues { inherit (inputs'.flint.packages) flint; };
script = /* bash */ ''
flint --fail-if-multiple-versions
'';
};
}
// self.nixosConfigurations.hetzner-1.config.serviceTests;
checkPhase = ''
nu ${check}
'';
};
}
in
nixpkgs.lib.recursiveUpdate {
lints = runNuCheck {
name = "lints";
packages = [
pkgs.deadnix
pkgs.nixfmt-rfc-style
pkgs.shellcheck
statix'
];
check = ./lints.nu;
};
} (deploy-rs.lib.${system}.deployChecks self.deploy)

View file

@ -1,8 +1,10 @@
#!/usr/bin/env nu
let shell_files = ls **/*.sh | get name
let nix_files = ls **/*.nix | where name !~ "hardware-configuration.nix|_sources" | get name
let linters = [
([shellcheck] ++ $shell_files)
([nixfmt --check --strict] ++ $nix_files)
([deadnix --fail] ++ $nix_files)
([statix check] ++ $nix_files)

View file

@ -1,6 +1,5 @@
{
pkgs,
lib,
config,
modulesPath,
flake-inputs,
...
@ -9,13 +8,32 @@
imports = [
flake-inputs.disko.nixosModules.disko
flake-inputs.sops-nix.nixosModules.sops
"${modulesPath}/profiles/minimal.nix"
flake-inputs.tlaternet-webserver.nixosModules.default
../modules
./nginx
./services
"${modulesPath}/profiles/minimal.nix"
(import ../modules)
./services/auth
./services/backups.nix
./services/battery-manager.nix
./services/conduit
./services/crowdsec.nix
./services/foundryvtt.nix
./services/gitea.nix
./services/immich.nix
./services/metrics
./services/minecraft.nix
./services/nextcloud.nix
./services/webserver.nix
./services/wireguard.nix
# ./services/starbound.nix -- Not currently used
./services/postgres.nix
./nginx.nix
./sops.nix
];
nixpkgs.overlays = [ (_: prev: { local = import ../pkgs { pkgs = prev; }; }) ];
nix = {
extraOptions = ''
experimental-features = nix-command flakes
@ -25,9 +43,49 @@
settings.trusted-users = [ "@wheel" ];
};
# Optimization for minecraft servers, see:
# https://bugs.mojang.com/browse/MC-183518
boot.kernelParams = [
"highres=off"
"nohz=off"
];
networking = {
usePredictableInterfaceNames = false;
useDHCP = false;
firewall = {
allowedTCPPorts = [
# http
80
443
# ssh
2222
# matrix
8448
# starbound
21025
config.services.coturn.listening-port
config.services.coturn.tls-listening-port
config.services.coturn.alt-listening-port
config.services.coturn.alt-tls-listening-port
];
allowedUDPPorts = [
config.services.coturn.listening-port
config.services.coturn.tls-listening-port
config.services.coturn.alt-listening-port
config.services.coturn.alt-tls-listening-port
];
allowedUDPPortRanges = [
{
from = config.services.coturn.min-port;
to = config.services.coturn.max-port;
}
];
};
};
systemd.network.enable = true;
@ -43,6 +101,7 @@
services = {
openssh = {
enable = true;
allowSFTP = false;
ports = [ 2222 ];
startWhenNeeded = true;
@ -54,36 +113,22 @@
};
logrotate.enable = true;
postgresql = {
package = pkgs.postgresql_14;
enable = true;
# Only enable connections via the unix socket, and check with the
# OS to make sure the user matches the database name.
#
# See https://www.postgresql.org/docs/current/auth-pg-hba-conf.html
authentication = ''
local sameuser all peer
'';
};
};
security = {
sudo.execWheelOnly = true;
pam = {
rssh = {
sshAgentAuth = {
enable = true;
settings.auth_key_file = "/etc/ssh/authorized_keys.d/$ruser";
authorizedKeysFiles = [ "/etc/ssh/authorized_keys.d/%u" ];
};
services.sudo.rssh = true;
services.sudo.sshAgentAuth = true;
};
};
sops.defaultSopsFile = ../keys/production.yaml;
# Remove some unneeded packages
environment.defaultPackages = lib.mkForce [ ];
environment.defaultPackages = [ ];
system.stateVersion = "20.09";
}

View file

@ -2,7 +2,6 @@
imports = [
./hardware-configuration.nix
./disko.nix
./vm.nix
];
# Intel's special encrypted memory<->CPU feature. Hetzner's BIOS
@ -12,10 +11,7 @@
boot.kernelParams = [ "nosgx" ];
networking.hostName = "hetzner-1";
services = {
btrfs.autoScrub.enable = true;
nginx.domain = "tlater.net";
};
services.nginx.domain = "tlater.net";
systemd.network.networks."eth0" = {
matchConfig.MACAddress = "90:1b:0e:c1:8c:62";

View file

@ -80,7 +80,7 @@
inherit mountOptions;
mountpoint = "/var";
};
"/volume/var/lib/private/continuwuity" = {
"/volume/var/lib/private/matrix-conduit" = {
mountOptions = [
# Explicitly don't compress here, since
# conduwuit's database does compression by
@ -89,7 +89,7 @@
# if btrfs compresses it)
"noatime"
];
mountpoint = "/var/lib/private/continuwuity";
mountpoint = "/var/lib/private/matrix-conduit";
};
"/volume/nix-store" = {
inherit mountOptions;

View file

@ -1,70 +0,0 @@
{ lib, ... }:
{
virtualisation.vmVariant = {
users.users.tlater.password = "insecure";
# Disable graphical tty so -curses works
boot.kernelParams = [ "nomodeset" ];
networking.hostName = lib.mkForce "testvm";
services = {
# Sets the base domain for nginx to a local domain so that we can
# easily test locally with the VM.
nginx.domain = lib.mkForce "dev.local";
# Don't run this
batteryManager.enable = lib.mkForce false;
btrfs.autoScrub.enable = lib.mkForce false;
openssh.hostKeys = lib.mkForce [
{
type = "rsa";
bits = 4096;
path = "/etc/staging.key";
}
];
};
# Use the staging secrets
sops.defaultSopsFile = lib.mkOverride 99 ../../../keys/staging.yaml;
systemd.network.networks."10-eth0" = {
matchConfig.Name = "eth0";
gateway = [ "192.168.9.1" ];
networkConfig = {
Address = "192.168.9.2/24";
};
};
# Both so we have a predictable key for the staging env, as well as
# to have a static key for decrypting the sops secrets for the
# staging env.
environment.etc."staging.key" = {
mode = "0400";
source = ../../../keys/hosts/staging.key;
};
# Pretend the acme renew succeeds.
#
# TODO(tlater): Set up pebble to retrieve certs "properly"
# instead
systemd.services."acme-order-renew-tlater.net".script = ''
touch out/acme-success
'';
virtualisation = {
memorySize = 3941;
cores = 2;
graphics = false;
diskSize = 1024 * 20;
qemu = {
networkingOptions = lib.mkForce [
"-device virtio-net,netdev=n1"
"-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)"
];
};
};
};
}

View file

@ -0,0 +1,60 @@
{ lib, ... }:
{
users.users.tlater.password = "insecure";
# Disable graphical tty so -curses works
boot.kernelParams = [ "nomodeset" ];
networking.hostName = "testvm";
services = {
# Sets the base domain for nginx to a local domain so that we can
# easily test locally with the VM.
nginx.domain = "dev.local";
# Don't run this
batteryManager.enable = lib.mkForce false;
openssh.hostKeys = lib.mkForce [
{
type = "rsa";
bits = 4096;
path = "/etc/staging.key";
}
];
};
# Use the staging secrets
sops.defaultSopsFile = lib.mkOverride 99 ../../keys/staging.yaml;
systemd.network.networks."10-eth0" = {
matchConfig.Name = "eth0";
gateway = [ "192.168.9.1" ];
networkConfig = {
Address = "192.168.9.2/24";
};
};
# Both so we have a predictable key for the staging env, as well as
# to have a static key for decrypting the sops secrets for the
# staging env.
environment.etc."staging.key" = {
mode = "0400";
source = ../../keys/hosts/staging.key;
};
virtualisation.vmVariant = {
virtualisation = {
memorySize = 3941;
cores = 2;
graphics = false;
};
virtualisation.qemu = {
networkingOptions = lib.mkForce [
"-device virtio-net,netdev=n1"
"-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)"
];
};
};
}

78
configuration/nginx.nix Normal file
View file

@ -0,0 +1,78 @@
{ config, lib, ... }:
{
services = {
nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
clientMaxBodySize = "10G";
statusPage = true; # For metrics, should be accessible only from localhost
commonHttpConfig = ''
log_format upstream_time '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
'';
};
logrotate.settings =
{
# Override the default, just keep fewer logs
nginx.rotate = 6;
}
// lib.mapAttrs' (
virtualHost: _:
lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" {
frequency = "daily";
rotate = 2;
compress = true;
delaycompress = true;
su = "${config.services.nginx.user} ${config.services.nginx.group}";
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
}
) config.services.nginx.virtualHosts;
backups.acme = {
user = "acme";
paths = lib.mapAttrsToList (
virtualHost: _: "/var/lib/acme/${virtualHost}"
) config.services.nginx.virtualHosts;
};
};
systemd.tmpfiles.rules = lib.mapAttrsToList (
virtualHost: _:
#
"d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}"
) config.services.nginx.virtualHosts;
security.acme = {
defaults.email = "tm@tlater.net";
acceptTerms = true;
certs."tlater.net" = {
extraDomainNames = [
"*.tlater.net"
"tlater.com"
"*.tlater.com"
];
dnsProvider = "porkbun";
group = "ssl-cert";
credentialFiles = {
PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
};
};
};
users.groups.ssl-cert = { };
systemd.services.nginx.serviceConfig.SupplementaryGroups = [
config.security.acme.certs."tlater.net".group
];
}

View file

@ -1,22 +0,0 @@
{ lib, ... }:
{
imports = [
./logging.nix
./ssl.nix
];
options.services.nginx.domain = lib.mkOption {
type = lib.types.str;
description = "The base domain name to append to virtual domain names";
};
config.services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
clientMaxBodySize = "10G";
statusPage = true; # For metrics, should be accessible only from localhost
};
}

View file

@ -1,140 +0,0 @@
{
flake-inputs,
pkgs,
config,
lib,
...
}:
let
hostNames = lib.attrNames config.services.nginx.virtualHosts;
logPath = name: "/var/log/nginx/${name}/access.log";
logFormat = lib.concatStringsSep " " [
"$remote_addr - $remote_user [$time_local]"
''"$request" $status $body_bytes_sent''
''"$http_referer" "$http_user_agent"''
''rt=$request_time uct="$upstream_connect_time"''
''uht="$upstream_header_time" urt="$upstream_response_time"''
];
in
{
# Extend the default configuration for nginx virtual hosts; we'd
# like to create log files for each of them, so that the prometheus
# nginxlog exporter can process per-host logs.
options.services.nginx.virtualHosts = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
config.extraConfig = ''
access_log ${logPath name} upstream_time;
'';
}
)
);
};
config = {
# Create directories for host-specific logs with systemd tmpfiles
systemd.tmpfiles.settings."10-nginx-logs" = lib.listToAttrs (
map (
name:
lib.nameValuePair "/var/log/nginx/${name}" {
d = {
inherit (config.services.nginx) user group;
mode = "0750";
};
}
) hostNames
);
services = {
# Set the nginx-wide log format
nginx.commonHttpConfig = ''
log_format upstream_time '${logFormat}';
'';
# Set up nginxlog to read the file and log format defined above
# for each virtual host
prometheus.exporters.nginxlog = {
enable = true;
listenAddress = "127.0.0.1";
group = "nginx";
settings.namespaces = map (name: {
inherit name;
metrics_override.prefix = "nginxlog";
namespace_label = "vhost";
format = logFormat;
source.files = [ (logPath name) ];
}) hostNames;
};
logrotate.settings = {
# Override the nginx module default, just keep fewer logs
nginx.rotate = 6;
# Configure logrotate for host-specific logs
nginxVirtualHosts = {
files = map logPath hostNames;
frequency = "daily";
rotate = 2;
compress = true;
delaycompress = true;
su = "${config.services.nginx.user} ${config.services.nginx.group}";
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
};
};
};
serviceTests =
let
testHostConfig =
{ config, ... }:
{
imports = [
./.
../../modules/serviceTests/mocks.nix
];
networking.firewall.allowedTCPPorts = [ 80 ];
services.nginx = {
domain = "testHost";
virtualHosts."${config.services.nginx.domain}".locations."/".return = "200 ok";
};
};
in
{
nginxMetricsWork = pkgs.testers.runNixOSTest {
name = "nginx-metrics-work";
node.specialArgs = { inherit flake-inputs; };
nodes = {
testHost = testHostConfig;
client =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.curl ];
};
};
testScript = ''
import time
start_all()
testHost.wait_for_unit("nginx.service")
client.succeed("curl --max-time 10 http://testHost")
# Wait a bit for the prometheus exporter to scrape our logs
time.sleep(5)
res = testHost.succeed("curl localhost:${builtins.toString config.services.prometheus.exporters.nginxlog.port}/metrics")
assert 'nginxlog_http_response_count_total{method="GET",status="200",vhost="testHost"} 1' in res, res
'';
};
};
};
}

View file

@ -1,135 +0,0 @@
{
flake-inputs,
pkgs,
config,
lib,
...
}:
{
options = {
# Add a custom per-host option to enable HSTS
services.nginx.virtualHosts = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ config, ... }:
{
options.enableHSTS = lib.mkEnableOption "HSTS";
config.extraConfig = lib.mkIf config.enableHSTS ''
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
'';
}
)
);
};
};
config = {
# Certificate settings
security.acme = {
defaults.email = "tm@tlater.net";
acceptTerms = true;
certs."tlater.net" = {
extraDomainNames = [
"*.tlater.net"
"tlater.com"
"*.tlater.com"
];
dnsProvider = "porkbun";
group = config.users.groups.ssl-cert.name;
credentialFiles = {
PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
};
};
};
users.groups.ssl-cert = { };
# Back up the SSL certificate, just in case
services.backups.acme = {
user = "acme";
paths = [ "/var/lib/acme/tlater.net" ];
};
systemd.services.nginx.serviceConfig.SupplementaryGroups = [
config.security.acme.certs."tlater.net".group
];
sops.secrets = {
"porkbun/api-key".owner = "acme";
"porkbun/secret-api-key".owner = "acme";
};
serviceTests =
let
testHostConfig =
{ config, ... }:
{
imports = [
./.
../../modules/serviceTests/mocks.nix
];
networking.firewall.allowedTCPPorts = [ 443 ];
security.acme.certs."tlater.net".extraDomainNames = [ config.services.nginx.domain ];
# Pretend the acme renew succeeds.
#
# TODO(tlater): Set up pebble to retrieve certs "properly"
# instead
systemd.services."acme-order-renew-tlater.net".script = ''
touch out/acme-success
'';
services.nginx = {
domain = "testHost.test";
virtualHosts."${config.services.nginx.domain}.local" = {
useACMEHost = "tlater.net";
onlySSL = true;
enableHSTS = true;
locations."/".return = "200 ok";
};
};
};
in
{
testNginxSSL = pkgs.testers.runNixOSTest {
name = "test-nginx-ssl";
node.specialArgs = { inherit flake-inputs; };
nodes = {
testHost = testHostConfig;
client =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.curl ];
networking.hosts."192.168.1.2" = [ "testHost.test" ];
};
};
testScript = ''
start_all()
testHost.wait_for_unit("nginx.service")
testHost.copy_from_vm("/var/lib/acme/tlater.net/", "certs")
client.copy_from_host(f"{testHost.out_dir}/certs", "/certs")
res = client.succeed(" ".join([
"curl",
"--show-error",
"--silent",
"--dump-header -",
"--cacert /certs/tlater.net/fullchain.pem",
"https://testHost.test",
"-o /dev/null"
]))
assert "strict-transport-security: max-age=15552000; includeSubDomains" in res
'';
};
};
};
}

View file

@ -0,0 +1,93 @@
{ config, ... }:
{
systemd.services.authelia-tlaternet.after = [ config.systemd.services.lldap-provisioning.name ];
services = {
authelia.instances.tlaternet = {
enable = true;
environmentVariables.AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE =
config.sops.secrets."authelia/lldap-password".path;
settings = {
authentication_backend.ldap =
let
cfglldap = config.services.lldap.settings;
in
{
# TODO(tlater): Enable when authelia has a webhook notifier:
# https://github.com/authelia/authelia/issues/7695
password_reset.disable = true;
refresh_interval = "1m";
address = "ldap://${cfglldap.ldap_host}:${toString cfglldap.ldap_port}";
implementation = "lldap";
base_dn = cfglldap.ldap_base_dn;
user = "cn=authelia,ou=people,${cfglldap.ldap_base_dn}";
};
password_policy.zxcvbn.enabled = true;
telemetry.metrics.enabled = true;
access_control = {
default_policy = "deny";
rules = [
{
domain = "*.${config.services.nginx.domain}";
policy = "one_factor";
}
];
};
notifier.filesystem.filename = "/var/lib/authelia-tlaternet/notification.txt";
session = {
cookies = [
{
domain = "${config.services.nginx.domain}";
authelia_url = "https://auth.${config.services.nginx.domain}";
}
];
redis.host = config.services.redis.servers.authelia.unixSocket;
};
storage = {
postgres = {
address = "/var/run/postgresql";
username = config.services.authelia.instances.tlaternet.user;
database = config.services.authelia.instances.tlaternet.user;
};
};
# Auth options
default_2fa_method = "totp";
totp.issuer = "tlater.net";
webauthn = {
display_name = "tlater.net";
enable_passkey_login = true;
attestation_conveyance_preference = "direct";
filtering.prohibit_backup_eligibility = true;
metadata = {
enabled = true;
validate_trust_anchor = true;
validate_entry = true;
validate_status = true;
validate_entry_permit_zero_aaguid = false;
};
};
duo_api.disable = true;
};
secrets = {
storageEncryptionKeyFile = config.sops.secrets."authelia/storage-encryption-key".path;
jwtSecretFile = config.sops.secrets."authelia/jwt-secret".path;
sessionSecretFile = config.sops.secrets."authelia/session-secret".path;
};
};
redis.servers.authelia = {
enable = true;
user = config.services.authelia.instances.tlaternet.user;
};
};
}

View file

@ -0,0 +1,6 @@
{
imports = [
./authelia.nix
./lldap.nix
];
}

View file

@ -0,0 +1,44 @@
#!/usr/bin/env nushell
let groups = [{
}]
let users = [
]
let settings = open $env.LLDAP_CONFIG
let url = (
'http://' |
$in + ($settings | get http_host | default '127.0.0.1') |
$in + ':' |
$in + ($settings | get http_port | default '17170' | into string))
let user = $settings | get ldap_user_dn | default admin
let pass = open $env.LLDAP_LDAP_USER_PASS_FILE
let token = { username: $user, password: $pass } | to json | http post $'($url)/auth/simple/login' | get token
def query [operation: string, query: string, variables: list<string>] {
let body = {
query: $query,
operationName: $operation,
variables: $variables
}
let res = $body | to json | http post --headers [Authorization $'Bearer ($token)'] $'($url)/api/graphql'
if ("errors" in $res) {
let msg = "GraphQL query to LLDAP failed:\n" + ($res.errors | each {|e| $'- ($e)' | str join (char newline)})
error make {
msg: $msg,
label: {
text: "Query defined here",
span: (metadata $query).span
}
}
} else {
$res.data
}
}

View file

@ -0,0 +1,64 @@
{
lib,
pkgs,
config,
...
}:
{
services = {
lldap = {
enable = true;
settings = {
ldap_base_dn = "dc=tlater,dc=net";
database_url = "postgres://lldap:@localhost/lldap?host=/var/run/postgresql";
ldap_host = "127.0.0.1";
http_host = "127.0.0.1";
http_url = "https://lldap.${config.services.nginx.domain}";
force_ldap_user_pass_reset = "always";
smtp_options.enable_password_reset = false;
environment = {
LLDAP_JWT_SECRET_FILE = config.sops.secrets."authelia/jwt-secret".path;
LLDAP_LDAP_USER_PASS_FILE = config.sops.secrets."lldap/admin-password".path;
LLDAP_KEY_SEED_FILE = config.sops.secrets."lldap/key".path;
};
};
};
nginx.virtualHosts = {
"lldap.${config.services.nginx.domain}" = {
useACMEHost = "tlater.net";
forceSSL = true;
enableHSTS = true;
locations."/".proxyPass =
"http://${config.services.lldap.settings.http_host}:${toString config.services.lldap.settings.http_port}";
};
};
};
systemd.services.lldap.after = [ config.systemd.services.postgresql.name ];
systemd.services.lldap-provisioning = {
requisite = [ config.systemd.services.lldap.name ];
wantedBy = [ config.systemd.services.lldap.name ];
after = [ config.systemd.services.lldap.name ];
path = [
pkgs.nushell
pkgs.lldap-cli
];
script = "exec nu ${./lldap-provisioning.nu}";
environment = {
LLDAP_LDAP_USER_PASS_FILE = config.sops.secrets."lldap/admin-password".path;
# LLDAP_CONFIG = ((pkgs.formats.toml { }).generate config.services.lldap.settings).outPath;
};
serviceConfig.Type = "oneshot";
};
}

View file

@ -140,121 +140,123 @@ in
};
config = lib.mkIf (config.services.backups != { }) {
systemd.services = {
restic-prune = {
# Doesn't hurt to finish the ongoing prune
restartIfChanged = false;
systemd.services =
{
restic-prune = {
# Doesn't hurt to finish the ongoing prune
restartIfChanged = false;
environment = resticEnv;
environment = resticEnv;
path = with pkgs; [
openssh
rclone
restic
];
script = ''
# TODO(tlater): In an append-only setup, we should be
# careful with this; an attacker could delete backups by
# simply appending ad infinitum:
# https://restic.readthedocs.io/en/stable/060_forget.html#security-considerations-in-append-only-mode
restic forget --keep-last 3 --prune
restic check
'';
serviceConfig = {
DynamicUser = true;
Group = "backup";
CacheDirectory = "restic-prune";
CacheDirectoryMode = "0700";
};
};
}
// lib.mapAttrs' (
name: backup:
lib.nameValuePair "backup-${name}" {
# Don't want to restart mid-backup
restartIfChanged = false;
environment = resticEnv // {
RESTIC_CACHE_DIR = "%C/backup-${name}";
};
path = with pkgs; [
coreutils
openssh
rclone
restic
];
# TODO(tlater): If I ever add more than one repo, service
# shutdown/restarting will potentially break if multiple
# backups for the same service overlap. A more clever
# sentinel file with reference counts would probably solve
# this.
serviceConfig = {
User = backup.user;
Group = "backup";
RuntimeDirectory = "backup-${name}";
CacheDirectory = "backup-${name}";
CacheDirectoryMode = "0700";
PrivateTmp = true;
ExecStart = [
(lib.concatStringsSep " " (
[
"${pkgs.restic}/bin/restic"
"backup"
"--tag"
name
]
++ backup.paths
))
path = with pkgs; [
openssh
rclone
restic
];
ExecStartPre =
map (service: "+${mkShutdownScript service}") backup.pauseServices
++ singleton (
writeScript "backup-${name}-repo-init" [ ] ''
restic snapshots || restic init
''
)
++ optional (backup.preparation.text != null) (
writeScript "backup-${name}-prepare" backup.preparation.packages backup.preparation.text
);
script = ''
# TODO(tlater): In an append-only setup, we should be
# careful with this; an attacker could delete backups by
# simply appending ad infinitum:
# https://restic.readthedocs.io/en/stable/060_forget.html#security-considerations-in-append-only-mode
restic forget --keep-last 3 --prune
restic check
'';
# TODO(tlater): Add repo pruning/checking
ExecStopPost =
map (service: "+${mkRestartScript service}") backup.pauseServices
++ optional (backup.cleanup.text != null) (
writeScript "backup-${name}-cleanup" backup.cleanup.packages backup.cleanup.text
);
serviceConfig = {
DynamicUser = true;
Group = "backup";
CacheDirectory = "restic-prune";
CacheDirectoryMode = "0700";
};
};
}
) config.services.backups;
// lib.mapAttrs' (
name: backup:
lib.nameValuePair "backup-${name}" {
# Don't want to restart mid-backup
restartIfChanged = false;
systemd.timers = {
restic-prune = {
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "Thursday 03:00:00 UTC";
# Don't make this persistent, in case the server was offline
# for a while. This job cannot run at the same time as any
# of the backup jobs.
};
}
// lib.mapAttrs' (
name: _:
lib.nameValuePair "backup-${name}" {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "Wednesday 02:30:00 UTC";
RandomizedDelaySec = "1h";
FixedRandomDelay = true;
Persistent = true;
environment = resticEnv // {
RESTIC_CACHE_DIR = "%C/backup-${name}";
};
path = with pkgs; [
coreutils
openssh
rclone
restic
];
# TODO(tlater): If I ever add more than one repo, service
# shutdown/restarting will potentially break if multiple
# backups for the same service overlap. A more clever
# sentinel file with reference counts would probably solve
# this.
serviceConfig = {
User = backup.user;
Group = "backup";
RuntimeDirectory = "backup-${name}";
CacheDirectory = "backup-${name}";
CacheDirectoryMode = "0700";
PrivateTmp = true;
ExecStart = [
(lib.concatStringsSep " " (
[
"${pkgs.restic}/bin/restic"
"backup"
"--tag"
name
]
++ backup.paths
))
];
ExecStartPre =
map (service: "+${mkShutdownScript service}") backup.pauseServices
++ singleton (
writeScript "backup-${name}-repo-init" [ ] ''
restic snapshots || restic init
''
)
++ optional (backup.preparation.text != null) (
writeScript "backup-${name}-prepare" backup.preparation.packages backup.preparation.text
);
# TODO(tlater): Add repo pruning/checking
ExecStopPost =
map (service: "+${mkRestartScript service}") backup.pauseServices
++ optional (backup.cleanup.text != null) (
writeScript "backup-${name}-cleanup" backup.cleanup.packages backup.cleanup.text
);
};
}
) config.services.backups;
systemd.timers =
{
restic-prune = {
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "Thursday 03:00:00 UTC";
# Don't make this persistent, in case the server was offline
# for a while. This job cannot run at the same time as any
# of the backup jobs.
};
}
) config.services.backups;
// lib.mapAttrs' (
name: _:
lib.nameValuePair "backup-${name}" {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "Wednesday 02:30:00 UTC";
RandomizedDelaySec = "1h";
FixedRandomDelay = true;
Persistent = true;
};
}
) config.services.backups;
users = {
# This user is only used to own the ssh key, because apparently
@ -265,18 +267,5 @@ in
};
groups.backup = { };
};
sops.secrets = {
"restic/storagebox-backups" = {
owner = "root";
group = "backup";
mode = "0440";
};
"restic/storagebox-ssh-key" = {
owner = "backup";
group = "backup";
mode = "0040";
};
};
};
}

View file

@ -13,9 +13,4 @@
log_level = "DEBUG";
};
};
sops.secrets = {
"battery-manager/email" = { };
"battery-manager/password" = { };
};
}

View file

@ -7,52 +7,25 @@
let
inherit (lib.strings) concatMapStringsSep;
cfg = config.services.matrix-continuwuity;
cfg = config.services.matrix-conduit;
domain = "matrix.${config.services.nginx.domain}";
turn-realm = "turn.${config.services.nginx.domain}";
in
{
imports = [ ./heisenbridge.nix ];
networking.firewall = {
allowedTCPPorts = [
# These are for "normal" clients
80
443
# Federation happens on 8448
8448
config.services.coturn.listening-port
config.services.coturn.tls-listening-port
config.services.coturn.alt-listening-port
config.services.coturn.alt-tls-listening-port
];
allowedUDPPorts = [
config.services.coturn.listening-port
config.services.coturn.tls-listening-port
config.services.coturn.alt-listening-port
config.services.coturn.alt-tls-listening-port
];
allowedUDPPortRanges = [
{
from = config.services.coturn.min-port;
to = config.services.coturn.max-port;
}
];
};
imports = [
./heisenbridge.nix
./matrix-hookshot.nix
];
services = {
matrix-continuwuity = {
matrix-conduit = {
enable = true;
package = pkgs.matrix-continuwuity;
settings.global = {
address = [ "127.0.0.1" ];
address = "127.0.0.1";
server_name = domain;
new_user_displayname_suffix = "🦆";
turn_secret_file = "/run/credentials/continuwuity.service/turn-secret";
allow_check_for_updates = true;
# Set up delegation: https://docs.conduit.rs/delegation.html#automatic-recommended
# This is primarily to make sliding sync work
@ -173,39 +146,37 @@ in
locations = {
"/_matrix" = {
proxyPass = "http://${lib.head cfg.settings.global.address}:${toString cfg.settings.global.port}";
proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
# Recommended by conduit
extraConfig = ''
proxy_buffering off;
'';
};
"/.well-known/matrix" = {
proxyPass = "http://${lib.head cfg.settings.global.address}:${toString cfg.settings.global.port}";
proxyPass = "http://${cfg.settings.global.address}:${toString cfg.settings.global.port}";
};
};
};
backups.conduit = {
user = "root";
paths = [ "/var/lib/private/matrix-continuwuity/" ];
paths = [ "/var/lib/private/matrix-conduit/" ];
# Other services store their data in conduit, so no other services
# need to be shut down currently.
pauseServices = [ "continuwuity.service" ];
pauseServices = [ "conduit.service" ];
};
};
systemd.services.continuwuity.serviceConfig.LoadCredential = "turn-secret:${
config.sops.secrets."turn/env".path
}";
systemd.services.conduit.serviceConfig = {
ExecStart = lib.mkForce "${config.services.matrix-conduit.package}/bin/conduwuit";
# Pass in the TURN secret via EnvironmentFile, not supported by
# upstream module currently.
#
# See also https://gitlab.com/famedly/conduit/-/issues/314
EnvironmentFile = config.sops.secrets."turn/env".path;
};
systemd.services.coturn.serviceConfig.SupplementaryGroups = [
config.security.acme.certs."tlater.net".group
];
sops.secrets = {
"turn/env" = { };
"turn/secret" = {
owner = "turnserver";
};
};
}

View file

@ -5,7 +5,7 @@
...
}:
let
conduitCfg = config.services.matrix-continuwuity;
conduitCfg = config.services.matrix-conduit;
matrixLib = pkgs.callPackage ./lib.nix { };
in
{
@ -36,7 +36,7 @@ in
{
description = "Matrix<->IRC bridge";
wantedBy = [ "multi-user.target" ];
after = [ "continuwuity.service" ];
after = [ "conduit.service" ];
serviceConfig = {
Type = "exec";
@ -75,10 +75,4 @@ in
# AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
};
};
sops.secrets = {
# Accessed via systemd cred through /run/secrets/heisebridge
"heisenbridge/as-token" = { };
"heisenbridge/hs-token" = { };
};
}

View file

@ -0,0 +1,166 @@
{
pkgs,
lib,
config,
...
}:
let
matrixLib = pkgs.callPackage ./lib.nix { };
cfg = config.services.matrix-hookshot;
conduitCfg = config.services.matrix-conduit;
domain = conduitCfg.settings.global.server_name;
registration = matrixLib.writeRegistrationScript {
id = "matrix-hookshot";
url = "http://127.0.0.1:9993";
sender_localpart = "hookshot";
namespaces = {
aliases = [ ];
rooms = [ ];
users = [
{
regex = "@${cfg.settings.generic.userIdPrefix}.*:${domain}";
exclusive = true;
}
];
};
# Encryption support
# TODO(tlater): Enable when
# https://github.com/matrix-org/matrix-hookshot/issues/1060 is
# fixed
# extraSettings = {
# "de.sorunome.msc2409.push_ephemeral" = true;
# push_ephemeral = true;
# "org.matrix.msc3202" = true;
# };
runtimeRegistration = "${cfg.registrationFile}";
};
in
{
# users = {
# users.matrix-hookshot = {
# home = "/run/matrix-hookshot";
# group = "matrix-hookshot";
# isSystemUser = true;
# };
# groups.matrix-hookshot = { };
# };
systemd.services.matrix-hookshot = {
serviceConfig = {
Type = lib.mkForce "exec";
LoadCredential = "matrix-hookshot:/run/secrets/matrix-hookshot";
inherit (registration) ExecStartPre;
# Some library in matrix-hookshot wants a home directory
Environment = [ "HOME=/run/matrix-hookshot" ];
# User = "matrix-hookshot";
DynamicUser = true;
StateDirectory = "matrix-hookshot";
RuntimeDirectory = "matrix-hookshot";
RuntimeDirectoryMode = "0700";
RestrictNamespaces = true;
PrivateUsers = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [
# "AF_UNIX"
"AF_INET"
"AF_INET6"
];
LockPersonality = true;
RestrictRealtime = true;
ProtectProc = "invisible";
ProcSubset = "pid";
UMask = 77;
};
};
# services.redis.servers.matrix-hookshot = {
# enable = true;
# user = "matrix-hookshot";
# };
services.matrix-hookshot = {
enable = true;
serviceDependencies = [ "conduit.service" ];
registrationFile = "/run/matrix-hookshot/registration.yaml";
settings = {
bridge = {
inherit domain;
url = "http://localhost:${toString conduitCfg.settings.global.port}";
mediaUrl = conduitCfg.settings.global.well_known.client;
port = 9993;
bindAddress = "127.0.0.1";
};
bot.displayname = "Hookshot";
# cache.redisUri = "redis://${config.services.redis.servers.matrix-hookshot.unixSocket}";
generic = {
enabled = true;
outbound = false;
# Only allow webhooks from localhost for the moment
urlPrefix = "http://127.0.0.1:9000/webhook";
userIdPrefix = "_webhooks_";
allowJsTransformationFunctions = true;
};
# TODO(tlater): Enable when
# https://github.com/matrix-org/matrix-hookshot/issues/1060 is
# fixed
# encryption.storagePath = "/var/lib/matrix-hookshot/cryptostore";
permissions = [
{
actor = "matrix.tlater.net";
services = [
{
service = "*";
level = "notifications";
}
];
}
{
actor = "@tlater:matrix.tlater.net";
services = [
{
service = "*";
level = "admin";
}
];
}
];
listeners = [
{
port = 9000;
resources = [ "webhooks" ];
}
{
port = 9001;
resources = [ "metrics" ];
}
];
metrics.enabled = true;
};
};
}

View file

@ -0,0 +1,50 @@
{
"allowAdminCommands" : true,
"allowAdminCommandsFromAnyone" : false,
"allowAnonymousConnections" : true,
"allowAssetsMismatch" : true,
"anonymousConnectionsAreAdmin" : false,
"bannedIPs" : [],
"bannedUuids" : [],
"checkAssetsDigest" : false,
"clearPlayerFiles" : false,
"clearUniverseFiles" : false,
"clientIPJoinable" : false,
"clientP2PJoinable" : true,
"configurationVersion" : {
"basic" : 2,
"server" : 4
},
"crafting" : {
"filterHaveMaterials" : false
},
"gameServerBind" : "::",
"gameServerPort" : 21025,
"interactiveHighlight" : true,
"inventory" : {
"pickupToActionBar" : true
},
"maxPlayers" : 8,
"maxTeamSize" : 4,
"monochromeLighting" : false,
"playerBackupFileCount" : 3,
"queryServerBind" : "::",
"queryServerPort" : 21025,
"rconServerBind" : "::",
"rconServerPassword" : "",
"rconServerPort" : 21026,
"rconServerTimeout" : 1000,
"runQueryServer" : false,
"runRconServer" : false,
"safeScripts" : true,
"scriptInstructionLimit" : 10000000,
"scriptInstructionMeasureInterval" : 10000,
"scriptProfilingEnabled" : false,
"scriptRecursionLimit" : 100,
"serverFidelity" : "automatic",
"serverName" : "tlater.net",
"serverOverrideAssetsDigest" : null,
"serverUsers" : {
},
"tutorialMessages" : true
}

View file

@ -1,80 +1,44 @@
{ config, lib, ... }:
{
services = {
crowdsec = {
enable = true;
autoUpdateService = true;
pkgs,
config,
lib,
...
}:
{
security.crowdsec = {
enable = true;
settings = {
general.api.server = {
enable = true;
online_client.sharing = false;
};
parserWhitelist = [ "10.45.249.2" ];
lapi.credentialsFile = "/var/lib/crowdsec/state/local_credentials.yaml";
};
extraGroups = [
"systemd-journal"
"nginx"
];
hub = {
collections = [
"crowdsecurity/base-http-scenarios"
"crowdsecurity/http-cve"
"crowdsecurity/linux"
"crowdsecurity/nextcloud"
"crowdsecurity/nginx"
"crowdsecurity/sshd"
];
};
acquisitions = [
{
source = "journalctl";
labels.type = "syslog";
journalctl_filter = [ "SYSLOG_IDENTIFIER=Nextcloud" ];
}
localConfig = {
acquisitions = [
{
labels.type = "syslog";
journalctl_filter = [
"SYSLOG_IDENTIFIER=Nextcloud"
"SYSLOG_IDENTIFIER=sshd-session"
];
source = "journalctl";
}
{
source = "journalctl";
labels.type = "syslog";
journalctl_filter = [ "SYSLOG_IDENTIFIER=sshd-session" ];
}
{
labels.type = "nginx";
filenames = [
"/var/log/nginx/*.log"
]
++ lib.mapAttrsToList (
vHost: _: "/var/log/nginx/${vHost}/access.log"
) config.services.nginx.virtualHosts;
}
];
{
labels.type = "nginx";
filenames =
[ "/var/log/nginx/*.log" ]
++ lib.mapAttrsToList (
vHost: _: "/var/log/nginx/${vHost}/access.log"
) config.services.nginx.virtualHosts;
}
];
parsers.s02Enrich = [
{
name = "nixos/parser-whitelist";
description = "Parser whitelist generated by the crowdsec NixOS module";
whitelist = {
reason = "Filtered by NixOS whitelist";
ip = [ "10.45.249.2" ];
};
}
];
postOverflows.s01Whitelist = [
{
description = "custom matrix whitelist";
name = "tetsumaki/matrix";
whitelist = {
reason = "whitelist false positive for matrix";
expression = [
"evt.Overflow.Alert.Events[0].GetMeta('target_fqdn') == '${config.services.matrix-continuwuity.settings.global.server_name}'"
"evt.Overflow.Alert.GetScenario() in ['crowdsecurity/http-probing', 'crowdsecurity/http-crawl-non_statics']"
];
};
}
];
};
};
crowdsec-firewall-bouncer = {
remediationComponents.firewallBouncer = {
enable = true;
settings.prometheus = {
enabled = true;
@ -82,23 +46,37 @@
listen_port = "60601";
};
};
victoriametrics.scrapeConfigs = {
crowdsec.targets =
let
cfg = config.services.crowdsec.settings.general;
address = cfg.prometheus.listen_addr;
port = cfg.prometheus.listen_port;
in
[ "${address}:${toString port}" ];
csFirewallBouncer.targets =
let
cfg = config.services.crowdsec-firewall-bouncer.settings;
address = cfg.prometheus.listen_addr;
port = cfg.prometheus.listen_port;
in
[ "${address}:${toString port}" ];
};
};
# Add whitelists for matrix
systemd.tmpfiles.settings."10-matrix" =
let
stateDir = config.security.crowdsec.stateDirectory;
in
{
"${stateDir}/config/postoverflows".d = {
user = "crowdsec";
group = "crowdsec";
mode = "0700";
};
"${stateDir}/config/postoverflows/s01-whitelist".d = {
user = "crowdsec";
group = "crowdsec";
mode = "0700";
};
"${stateDir}/config/postoverflows/s01-whitelist/matrix-whitelist.yaml"."L+".argument =
((pkgs.formats.yaml { }).generate "crowdsec-matrix-whitelist.yaml" {
name = "tetsumaki/matrix";
description = "custom matrix whitelist";
whitelist = {
reason = "whitelist false positive for matrix";
expression = [
"evt.Overflow.Alert.Events[0].GetMeta('target_fqdn') == '${config.services.matrix-conduit.settings.global.server_name}'"
"evt.Overflow.Alert.GetScenario() in ['crowdsecurity/http-probing', 'crowdsecurity/http-crawl-non_statics']"
];
};
}).outPath;
};
}

View file

@ -1,17 +0,0 @@
{
imports = [
./backups.nix
./battery-manager.nix
./conduit
./crowdsec.nix
./foundryvtt.nix
./gitea.nix
./immich.nix
./metrics
./ntfy-sh
./minecraft.nix
./nextcloud.nix
./webserver.nix
./wireguard.nix
];
}

View file

@ -11,11 +11,6 @@ in
{
imports = [ flake-inputs.foundryvtt.nixosModules.foundryvtt ];
networking.firewall.allowedTCPPorts = [
80
443
];
services = {
foundryvtt = {
enable = true;
@ -23,7 +18,7 @@ in
minifyStaticFiles = true;
proxySSL = true;
proxyPort = 443;
package = flake-inputs.foundryvtt.packages.${pkgs.stdenv.hostPlatform.system}.foundryvtt_13;
package = flake-inputs.foundryvtt.packages.${pkgs.system}.foundryvtt_13;
};
nginx.virtualHosts."${domain}" =

View file

@ -8,11 +8,6 @@ let
domain = "gitea.${config.services.nginx.domain}";
in
{
networking.firewall.allowedTCPPorts = [
80
443
];
services = {
forgejo = {
enable = true;

View file

@ -8,19 +8,11 @@ let
hostName = "immich.${config.services.nginx.domain}";
in
{
networking.firewall.allowedTCPPorts = [
80
443
];
services = {
immich = {
enable = true;
settings.server.externalDomain = "https://${hostName}";
# We're using vectorchord now
database.enableVectors = false;
environment.IMMICH_TELEMETRY_INCLUDE = "all";
};

View file

@ -1,4 +1,9 @@
{ pkgs, ... }:
{
config,
pkgs,
lib,
...
}:
let
yaml = pkgs.formats.yaml { };
in
@ -63,6 +68,28 @@ in
enable = true;
listenAddress = "127.0.0.1";
};
nginxlog = {
enable = true;
listenAddress = "127.0.0.1";
group = "nginx";
settings.namespaces = lib.mapAttrsToList (name: _: {
inherit name;
metrics_override.prefix = "nginxlog";
namespace_label = "vhost";
format = lib.concatStringsSep " " [
"$remote_addr - $remote_user [$time_local]"
''"$request" $status $body_bytes_sent''
''"$http_referer" "$http_user_agent"''
''rt=$request_time uct="$upstream_connect_time"''
''uht="$upstream_header_time" urt="$upstream_response_time"''
];
source.files = [ "/var/log/nginx/${name}/access.log" ];
}) config.services.nginx.virtualHosts;
};
};
# TODO(tlater):

View file

@ -3,110 +3,68 @@ let
domain = "metrics.${config.services.nginx.domain}";
in
{
networking.firewall.allowedTCPPorts = [
80
443
];
services = {
grafana = {
enable = true;
settings = {
server = {
http_port = 3001; # Default overlaps with gitea
root_url = "https://metrics.tlater.net";
};
security = {
admin_user = "tlater";
admin_password = "$__file{${config.sops.secrets."grafana/adminPassword".path}}";
secret_key = "$__file{${config.sops.secrets."grafana/secretKey".path}}";
cookie_secure = true;
cookie_samesite = "strict";
content_security_policy = true;
};
database = {
user = "grafana";
name = "grafana";
type = "postgres";
host = "/run/postgresql";
};
services.grafana = {
enable = true;
settings = {
server = {
http_port = 3001; # Default overlaps with gitea
root_url = "https://metrics.tlater.net";
};
declarativePlugins = [
pkgs.grafanaPlugins.victoriametrics-metrics-datasource
pkgs.grafanaPlugins.victoriametrics-logs-datasource
];
security = {
admin_user = "tlater";
admin_password = "$__file{${config.sops.secrets."grafana/adminPassword".path}}";
secret_key = "$__file{${config.sops.secrets."grafana/secretKey".path}}";
cookie_secure = true;
cookie_samesite = "strict";
content_security_policy = true;
};
provision = {
enable = true;
datasources.settings.datasources = [
{
name = "Victoriametrics - tlater.net";
url = "http://localhost:8428";
type = "victoriametrics-metrics-datasource";
access = "proxy";
isDefault = true;
}
{
name = "Victorialogs - tlater.net";
url = "http://${config.services.victorialogs.bindAddress}";
type = "victoriametrics-logs-datasource";
access = "proxy";
}
];
alerting.contactPoints.settings.contactPoints = [
{
name = "ntfy";
receivers = [
{
uid = "ntfy";
type = "webhook";
settings.url = "http://${config.services.ntfy-sh.settings.listen-http}/local-alerts?template=grafana";
}
];
}
];
database = {
user = "grafana";
name = "grafana";
type = "postgres";
host = "/run/postgresql";
};
};
postgresql = {
ensureUsers = [
declarativePlugins = [
pkgs.grafanaPlugins.victoriametrics-metrics-datasource
pkgs.grafanaPlugins.victoriametrics-logs-datasource
];
provision = {
enable = true;
datasources.settings.datasources = [
{
name = "grafana";
ensureDBOwnership = true;
name = "Victoriametrics - tlater.net";
url = "http://localhost:8428";
type = "victoriametrics-metrics-datasource";
access = "proxy";
isDefault = true;
}
{
name = "Victorialogs - tlater.net";
url = "http://${config.services.victorialogs.bindAddress}";
type = "victoriametrics-logs-datasource";
access = "proxy";
}
];
ensureDatabases = [ "grafana" ];
};
nginx.virtualHosts."${domain}" = {
forceSSL = true;
useACMEHost = "tlater.net";
enableHSTS = true;
locations = {
"/".proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
"/api/live" = {
proxyWebsockets = true;
proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
};
};
};
};
sops.secrets = {
"grafana/adminPassword" = {
owner = "grafana";
group = "grafana";
};
"grafana/secretKey" = {
owner = "grafana";
group = "grafana";
services.nginx.virtualHosts."${domain}" = {
forceSSL = true;
useACMEHost = "tlater.net";
enableHSTS = true;
locations = {
"/".proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
"/api/live" = {
proxyWebsockets = true;
proxyPass = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
};
};
};
}

View file

@ -154,7 +154,8 @@ in
lib.recursiveUpdate {
inherit (scrape) job_name;
static_configs =
scrape.static_configs ++ lib.optional (scrape.targets != [ ]) { inherit (scrape) targets; };
scrape.static_configs
++ lib.optional (scrape.targets != [ ]) { inherit (scrape) targets; };
} scrape.extraSettings
) config.services.victoriametrics.scrapeConfigs;
};

View file

@ -4,11 +4,15 @@ let
blackbox_port = config.services.prometheus.exporters.blackbox.port;
in
{
services.victoriametrics = {
config.services.victoriametrics = {
enable = true;
extraOptions = [ "-storage.minFreeDiskSpaceBytes=5GB" ];
scrapeConfigs = {
authelia = {
targets = [ "127.0.0.1:9959" ];
};
forgejo = {
targets = [ "127.0.0.1:${toString config.services.forgejo.settings.server.HTTP_PORT}" ];
extraSettings.authorization.credentials_file = config.sops.secrets."forgejo/metrics-token".path;
@ -68,18 +72,32 @@ in
coturn.targets = [ "127.0.0.1:9641" ];
crowdsec.targets =
let
address = config.security.crowdsec.settings.prometheus.listen_addr;
port = config.security.crowdsec.settings.prometheus.listen_port;
in
[ "${address}:${toString port}" ];
csFirewallBouncer.targets =
let
address =
config.security.crowdsec.remediationComponents.firewallBouncer.settings.prometheus.listen_addr;
port =
config.security.crowdsec.remediationComponents.firewallBouncer.settings.prometheus.listen_port;
in
[ "${address}:${toString port}" ];
immich.targets = [
"127.0.0.1:8081"
"127.0.0.1:8082"
];
# Configured in the hookshot listeners, but it's hard to filter
# the correct values out of that config.
matrixHookshot.targets = [ "127.0.0.1:9001" ];
victorialogs.targets = [ config.services.victorialogs.bindAddress ];
};
};
sops.secrets."forgejo/metrics-token" = {
owner = "forgejo";
group = "metrics";
mode = "0440";
};
}

View file

@ -4,9 +4,6 @@
config,
...
}:
let
java = pkgs.jdk21_headless;
in
{
services.minecraft-server = {
enable = true;
@ -42,7 +39,7 @@ in
package = pkgs.writeShellApplication {
name = "minecraft-server";
runtimeInputs = [ java ];
runtimeInputs = with pkgs; [ jdk17_headless ];
text = ''
exec /var/lib/minecraft/run.sh $@
@ -51,19 +48,16 @@ in
};
systemd.services.minecraft-server = {
path = [ java ];
path = with pkgs; [ jdk17_headless ];
# Since we read from our own HTTP server, we need to wait for it
# to be up
after = [ "nginx.service" ];
# Don't auto-start the server
wantedBy = lib.mkForce [ ];
serviceConfig = {
# Use packwiz to install mods
ExecStartPre = [
"${java}/bin/java -jar ${config.services.minecraft-server.dataDir}/packwiz-installer-bootstrap.jar -g -s server 'https://minecraft.${config.services.nginx.domain}/cobblemon-pack/pack.toml'"
"${pkgs.jdk17_headless}/bin/java -jar ${config.services.minecraft-server.dataDir}/packwiz-installer-bootstrap.jar -g -s server 'https://minecraft.${config.services.nginx.domain}/cobblemon-pack/pack.toml'"
];
# Forge requires some bonus JVM options, which they include in a
# little `run.sh` script
@ -77,13 +71,6 @@ in
group = "minecraft";
mode = "0775";
};
"/srv/minecraft/cobblemon-pack"."L+" = {
user = "nginx";
group = "minecraft";
mode = "0775";
argument = "${../../pkgs/minecraftmon}";
};
};
services.nginx.virtualHosts."minecraft.${config.services.nginx.domain}" = {

View file

@ -5,15 +5,10 @@
...
}:
let
nextcloud = pkgs.nextcloud32;
nextcloud = pkgs.nextcloud31;
hostName = "nextcloud.${config.services.nginx.domain}";
in
{
networking.firewall.allowedTCPPorts = [
80
443
];
services = {
nextcloud = {
inherit hostName;
@ -103,22 +98,6 @@ in
};
};
services.postgresql = {
ensureUsers = [
{
name = "nextcloud";
ensureDBOwnership = true;
}
];
ensureDatabases = [ "nextcloud" ];
};
# Ensure that this service doesn't start before postgres is ready
systemd.services.nextcloud-setup.after = [ "postgresql.target" ];
sops.secrets."nextcloud/tlater" = {
owner = "nextcloud";
group = "nextcloud";
};
systemd.services.nextcloud-setup.after = [ "postgresql.service" ];
}

View file

@ -1,188 +0,0 @@
{
pkgs,
config,
flake-inputs,
...
}:
let
domain = "ntfy.${config.services.nginx.domain}";
in
{
imports = [ ./downstream-module.nix ];
networking.firewall.allowedTCPPorts = [
80
443
];
services.ntfy-sh = {
enable = true;
environmentFile = config.sops.secrets."ntfy/users".path;
settings = {
base-url = "https://${domain}";
listen-http = "127.0.0.1:2586";
behind-proxy = true;
# Paths
attachment-cache-dir = "/var/lib/ntfy-sh/attachments";
cache-file = "/var/lib/ntfy-sh/cache-file.db";
auth-file = "/var/lib/ntfy-sh/user.db";
auth-default-access = "deny-all";
auth-access = [ "*:local-*:wo" ];
# Don't want to host the front-end
web-root = "disable";
};
};
services.nginx.virtualHosts."ntfy.${config.services.nginx.domain}" = {
forceSSL = true;
useACMEHost = "tlater.net";
enableHSTS = true;
locations."/" = {
proxyWebsockets = true;
proxyPass = "http://${config.services.ntfy-sh.settings.listen-http}";
extraConfig = ''
client_max_body_size 0; # Stream request body to backend
'';
};
# Don't allow writing to topics with the local prefix, *including*
# webhook writes, since they are set to write-only access from
# anyone.
locations."/local" = {
proxyWebsockets = true;
proxyPass = "http://${config.services.ntfy-sh.settings.listen-http}";
extraConfig = ''
client_max_body_size 0; # Stream request body to backend
limit_except GET OPTIONS {
deny all;
}
location ~ /trigger$ {
deny all;
}
'';
};
};
sops.secrets."ntfy/users" = { };
serviceTests = {
testNtfyConfig = pkgs.testers.runNixOSTest {
name = "test-ntfy-config";
node.specialArgs = { inherit flake-inputs; };
nodes = {
testHost =
{ lib, ... }:
{
imports = [
./.
../../nginx
../../../modules/serviceTests/mocks.nix
];
services.nginx.domain = "testHost";
# Don't care for testing SSL here
services.nginx.virtualHosts."ntfy.testHost" = {
forceSSL = lib.mkForce false;
enableHSTS = lib.mkForce false;
};
};
client =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.curl ];
networking.hosts."192.168.1.2" = [ "ntfy.testHost" ];
};
};
testScript = ''
import json
import time
from contextlib import contextmanager
def read_client_messages():
client.wait_for_unit("messages.service")
messages = [json.loads(line) for line in client.succeed("cat messages").split()]
client.succeed("systemctl stop messages.service")
client.succeed("rm messages")
print(messages)
return messages
@contextmanager
def client_subscribe(topic: str, timeout: int = 2):
systemd_invocation = [
"systemd-run",
"--unit messages",
"--property=Type=oneshot",
"--property=SuccessExitStatus=28",
"--remain-after-exit",
"--setenv=PATH=$PATH",
"--same-dir",
"--no-block",
"/bin/sh -c"
]
curl = [
"curl",
"--silent",
"--show-error",
f"--max-time {2 + timeout}",
"-u tlater:insecure",
f"http://ntfy.testHost/{topic}/json",
"-o messages"
]
client.succeed(f'{" ".join(systemd_invocation)} "{" ".join(curl)}"')
# Give some slack so the host doesn't send messages before
# we're listening
time.sleep(2)
yield
start_all()
testHost.wait_for_unit("ntfy-sh.service")
client.wait_until_succeeds("curl http://ntfy.testHost")
with subtest("subscribing and writing to local topics works"):
with client_subscribe("local-test"):
testHost.succeed("curl --fail --silent --show-error --data test http://127.0.0.1:2586/local-test")
messages = read_client_messages()
t.assertEqual(len(messages), 2)
t.assertEqual(messages[1].get("message"), "test")
with subtest("writing to non-local topics without auth fails"):
testHost.fail("curl --fail --silent --show-error --data test http://127.0.0.1:2586/test")
with subtest("writing to *any* topics from outside localhost fails"):
client.fail("curl --fail --silent --show-error --data test http://ntfy.testHost/test")
client.fail("curl --fail --silent --show-error --data test http://ntfy.testHost/local-test")
# GET requests work by default because websocket shenanigans
client.fail("curl --fail --silent --show-error http://ntfy.testHost/local-test/trigger?message=test")
with subtest("authenticated messaging works from outside localhost"):
with client_subscribe("test", 10):
client.succeed("curl -u tlater:insecure --fail --silent --show-error --data test http://ntfy.testHost/test")
client.succeed("curl -u tlater:insecure --fail --silent --show-error http://ntfy.testHost/test/trigger?message=test2")
messages = read_client_messages()
t.assertEqual(len(messages), 3)
t.assertEqual(messages[1].get("message"), "test")
t.assertEqual(messages[2].get("message"), "test2")
'';
};
};
}

View file

@ -1,70 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.services.ntfy-sh;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "server.yml" cfg.settings;
in
{
# We don't use the upstream module because it's stupid; the author
# doesn't seem to understand `DynamicUser` (or at least be unaware of
# systemd credentials).
disabledModules = [ "services/misc/ntfy-sh.nix" ];
options.services.ntfy-sh = {
enable = lib.mkEnableOption "[ntfy-sh](https://ntfy.sh), a push notification service";
package = lib.mkPackageOption pkgs "ntfy-sh" { };
environmentFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
Environment file; intended to be used for user provisioning.
'';
};
settings = lib.mkOption {
inherit (settingsFormat) type;
default = { };
description = ''
Configuration for ntfy.sh, supported values are [here](https://ntfy.sh/docs/config/#config-options).
'';
};
};
config.systemd.services.ntfy-sh = {
description = "Push notifications server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
Type = "exec";
ExecReload = "kill --signal HUP $MAINPID";
ExecStart = "${lib.getExe' cfg.package "ntfy"} serve -c ${configFile}";
EnvironmentFile = cfg.environmentFile;
StateDirectory = "ntfy-sh";
DynamicUser = true;
PrivateTmp = true;
NoNewPrivileges = true;
ProtectSystem = "full";
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
PrivateDevices = true;
RestrictSUIDSGID = true;
RestrictNamespaces = true;
RestrictRealtime = true;
MemoryDenyWriteExecute = true;
# Upstream Recommandation
LimitNOFILE = 20500;
};
};
}

View file

@ -0,0 +1,45 @@
{ config, pkgs, ... }:
{
services.postgresql = {
package = pkgs.postgresql_14;
enable = true;
# Only enable connections via the unix socket, and check with the
# OS to make sure the user matches the database name.
#
# See https://www.postgresql.org/docs/current/auth-pg-hba-conf.html
authentication = ''
local sameuser all peer
'';
# Note: The following options with ensure.* are set-only; i.e.,
# when permissions/users/databases are removed from these lists,
# that operation needs to be performed manually on the system as
# well.
ensureUsers = [
{
name = config.services.authelia.instances.tlaternet.user;
ensureDBOwnership = true;
}
{
name = "grafana";
ensureDBOwnership = true;
}
{
name = "lldap";
ensureDBOwnership = true;
}
{
name = "nextcloud";
ensureDBOwnership = true;
}
];
ensureDatabases = [
config.services.authelia.instances.tlaternet.user
"grafana"
"lldap"
"nextcloud"
];
};
}

View file

@ -0,0 +1,117 @@
{ pkgs, lib, ... }:
let
inherit (lib) concatStringsSep;
in
{
# Sadly, steam-run requires some X libs
environment.noXlibs = false;
systemd.services.starbound = {
description = "Starbound";
after = [ "network.target" ];
serviceConfig = {
ExecStart = "${pkgs.local.starbound}/bin/launch-starbound ${./configs/starbound.json}";
Type = "simple";
# Credential loading for steam auth (if necessary; prefer
# anonymous login wherever possible).
LoadCredential = "steam:/run/secrets/steam/tlater";
# Security settings
DynamicUser = true;
# This is where the StateDirectory ends up
WorkingDirectory = "/var/lib/starbound";
# Creates /var/lib/starbound (or rather, a symlink there to
# /var/lib/private/starbound), and sets it up to be writeable to
# by the dynamic user.
StateDirectory = "starbound";
# Note some settings below are basically tautologous with
# `NoNewPrivileges`, but they all work slightly differently so
# add additional layers in case of bugs.
## THESE SETTINGS ARE A GOOD IDEA BUT THE STEAM CLIENT IS
## REALLY, REALLY BAD, AND FOR SOME REASON I NEED TO USE IT TO
## DOWNLOAD GAME SERVERS AS WELL:
##
# To guarantee the above (only permits 64-bit syscalls, 32-bit
# syscalls can circumvent the above restrictions).
#
# Obviously, if running a 32 bit game server, change this.
# SystemCallArchitectures = "native";
# Game servers shouldn't need to create new namespaces ever.
#
# TODO: Since steam uses namespaces for things *entirely
# unrelated* to installing game servers, we need to allow
# namespace access. Ideally I'd instead do this in an
# ExecStartPre, but alas, this isn't possible because of
# https://github.com/systemd/systemd/issues/19604.
#
# RestrictNamespaces = true;
# Don't need to let the game server see other user accounts
PrivateUsers = true;
# *Probably* not harmful for game servers, which probably don't update dynamically
ProtectHostname = true;
# Yeah, if a game server tries to edit the hardware clock something's fishy
ProtectClock = true;
# Don't let game servers modify kernel settings, duh
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
# Game servers shouldn't use cgroups themselves either
ProtectControlGroups = true;
# Most game servers will never need other socket types
RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
# Also a no-brainer, no game server should ever need this
LockPersonality = true;
# Some game servers will probably try to set this, but they
# don't need it. It's only required for audio processing and
# such, which the server end doesn't need to do.
RestrictRealtime = true;
# Don't allow a variety of syscalls that gameservers have no
# business using anyway
SystemCallFilter =
"~"
+ (concatStringsSep " " [
"@clock"
"@cpu-emulation"
"@debug"
"@keyring"
"@memlock"
"@module"
# "@mount" TODO: Consider adding when steamcmd is run in ExecStartPre
"@obsolete"
"@raw-io"
"@reboot"
# "@resources" TODO: Ditto
"@setuid"
"@swap"
]);
# Normally only "read-only", but steamcmd will puke if there is
# no home directory to write to (though the nix package will
# implicitly symlink to the path that we set in its override, so
# no actual files are created, besides a symlink).
ProtectHome = "tmpfs";
# Implied by DynamicUser anyway, but it doesn't hurt to add
# these explicitly, at least for reference.
RemoveIPC = true;
PrivateTmp = true;
PrivateDevices = true;
NoNewPrivileges = true;
RestrictSUIDSGID = true;
ProtectSystem = "strict";
# ProtectHome = "read-only"; # See further up
};
};
services.backups.starbound = {
user = "root";
paths = [ "/var/lib/private/starbound/storage/universe/" ];
pauseServices = [ "starbound.service" ];
};
}

View file

@ -1,79 +1,28 @@
{
pkgs,
config,
lib,
flake-inputs,
...
}:
{ config, ... }:
let
inherit (config.services.nginx) domain;
in
{
networking.firewall.allowedTCPPorts = [
80
443
];
systemd.services.tlaternet-webserver = {
description = "tlater.net webserver";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
script = ''
${lib.getExe flake-inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.webserver}
'';
environment = {
TLATERNET_NTFY_INSTANCE = "http://${config.services.ntfy-sh.settings.listen-http}";
LEPTOS_SITE_ADDR = "127.0.0.1:8000";
};
serviceConfig = {
Type = "exec";
LoadCredential = "ntfy-topic:/run/secrets/tlaternet/ntfy-topic";
DynamicUser = true;
ProtectHome = true; # Override the default (read-only)
PrivateDevices = true;
PrivateIPC = true;
PrivateUsers = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictNamespaces = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged @resources @setuid @keyring"
];
services.tlaternet-webserver = {
enable = true;
listen = {
addr = "127.0.0.1";
port = 8000;
};
};
# Set up SSL
services.nginx.virtualHosts."${domain}" = {
serverAliases = [ "www.${domain}" ];
services.nginx.virtualHosts."${domain}" =
let
inherit (config.services.tlaternet-webserver.listen) addr port;
in
{
serverAliases = [ "www.${domain}" ];
forceSSL = true;
useACMEHost = "tlater.net";
enableHSTS = true;
forceSSL = true;
useACMEHost = "tlater.net";
enableHSTS = true;
locations."/".proxyPass =
"http://${config.systemd.services.tlaternet-webserver.environment.LEPTOS_SITE_ADDR}";
};
sops.secrets = {
"tlaternet/ntfy-topic" = { };
};
locations."/".proxyPass = "http://${addr}:${toString port}";
};
}

View file

@ -27,7 +27,7 @@
# yui
{
AllowedIPs = [ "10.45.249.2/32" ];
PublicKey = "WbNuxp7tTWTVve/nyiwC1stfaJS0wORvBxiK9IFTpio=";
PublicKey = "5mlnqEVJWks5OqgeFA2bLIrvST9TlCE81Btl+j4myz0=";
}
];
};
@ -62,10 +62,4 @@
};
};
};
sops.secrets."wireguard/server-key" = {
owner = "root";
group = "systemd-network";
mode = "0440";
};
}

111
configuration/sops.nix Normal file
View file

@ -0,0 +1,111 @@
{
sops = {
defaultSopsFile = ../keys/production.yaml;
secrets = {
"authelia/storage-encryption-key" = {
owner = "authelia-tlaternet";
group = "authelia-tlaternet";
};
"authelia/jwt-secret" = {
owner = "authelia-tlaternet";
group = "authelia-tlaternet";
};
"authelia/session-secret" = {
owner = "authelia-tlaternet";
group = "authelia-tlaternet";
};
"authelia/lldap-password" = {
owner = "authelia-tlaternet";
group = "lldap";
mode = "0440";
};
"battery-manager/email" = { };
"battery-manager/password" = { };
# Gitea
"forgejo/metrics-token" = {
owner = "forgejo";
group = "metrics";
mode = "0440";
};
# Grafana
"grafana/adminPassword" = {
owner = "grafana";
group = "grafana";
};
"grafana/secretKey" = {
owner = "grafana";
group = "grafana";
};
# Heisenbridge
"heisenbridge/as-token" = { };
"heisenbridge/hs-token" = { };
# lldap
"lldap/admin-password" = { };
"lldap/key" = { };
# Matrix-hookshot
"matrix-hookshot/as-token" = { };
"matrix-hookshot/hs-token" = { };
# Nextcloud
"nextcloud/tlater" = {
owner = "nextcloud";
group = "nextcloud";
};
# Porkbub/ACME
"porkbun/api-key" = {
owner = "acme";
};
"porkbun/secret-api-key" = {
owner = "acme";
};
# Restic
"restic/local-backups" = {
owner = "root";
group = "backup";
mode = "0440";
};
"restic/storagebox-backups" = {
owner = "root";
group = "backup";
mode = "0440";
};
"restic/storagebox-ssh-key" = {
owner = "backup";
group = "backup";
mode = "0040";
};
# Steam
"steam/tlater" = { };
# Turn
"turn/env" = { };
"turn/secret" = {
owner = "turnserver";
};
"turn/ssl-key" = {
owner = "turnserver";
};
"turn/ssl-cert" = {
owner = "turnserver";
};
# Wireguard
"wireguard/server-key" = {
owner = "root";
group = "systemd-network";
mode = "0440";
};
};
};
}

View file

@ -1,43 +0,0 @@
{ self, ... }:
{
# Systems on which to make dev utilities runnable; anything
# NixOS-related encodes its own system.
systems = [ "x86_64-linux" ];
perSystem =
{
inputs',
self',
pkgs,
lib,
...
}:
{
apps = {
default = self'.apps.runVm;
runVm = {
type = "app";
program = lib.getExe self.nixosConfigurations.hetzner-1.config.system.build.vm;
meta.description = "Run the test VM";
};
};
devShells = {
default = pkgs.mkShell {
sopsPGPKeyDirs = [
"./keys/hosts/"
"./keys/users/"
];
packages = lib.attrValues {
inherit (inputs'.sops-nix.packages) sops-import-keys-hook sops-init-gpg-key;
inherit (pkgs) deploy-rs;
};
};
minecraft = pkgs.mkShell { packages = lib.attrValues { inherit (pkgs) packwiz; }; };
webserver = self'.packages.webserver.devShell;
};
};
}

996
flake.lock generated

File diff suppressed because it is too large Load diff

180
flake.nix
View file

@ -1,34 +1,21 @@
{
description = "tlater.net host configuration";
inputs = {
nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz";
flake-parts.url = "github:hercules-ci/flake-parts";
## Nix/OS utilities
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05-small";
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
deploy-rs = {
url = "github:serokell/deploy-rs";
inputs.nixpkgs.follows = "nixpkgs";
};
deploy-rs.url = "github:serokell/deploy-rs";
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
## Programs
flint = {
url = "github:NotAShelf/flint";
tlaternet-webserver = {
url = "git+https://gitea.tlater.net/tlaternet/tlaternet.git";
inputs.nixpkgs.follows = "nixpkgs";
};
## Services
foundryvtt = {
url = "github:reckenrode/nix-foundryvtt";
inputs.nixpkgs.follows = "nixpkgs";
@ -36,54 +23,145 @@
sonnenshift = {
url = "git+ssh://git@github.com/sonnenshift/battery-manager";
inputs = {
nixpkgs.follows = "nixpkgs";
crate2nix.inputs = {
flake-compat.follows = "deploy-rs/flake-compat";
devshell.inputs.flake-utils.follows = "deploy-rs/utils";
flake-parts.follows = "flake-parts";
};
};
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{ flake-parts, ... }@inputs:
flake-parts.lib.mkFlake { inherit inputs; } (
{ self, ... }@args:
{
imports = [
(flake-parts.lib.importApply ./flakeModules/deploy-rs.nix args)
./checks
./dev-utils.nix
./pkgs
];
{
self,
nixpkgs,
sops-nix,
deploy-rs,
...
}@inputs:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
flake.nixosConfigurations.hetzner-1 = inputs.nixpkgs.lib.nixosSystem {
vm = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs.flake-inputs = inputs;
modules = [
./configuration
./configuration/hardware-specific/vm.nix
];
};
in
{
##################
# Configurations #
##################
nixosConfigurations = {
# The actual system definition
hetzner-1 = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs.flake-inputs = inputs;
modules = [
./configuration
./configuration/hardware-specific/hetzner
];
};
};
deploy.nodes.hetzner-1 = {
############################
# Deployment configuration #
############################
deploy.nodes = {
hetzner-1 = {
hostname = "116.202.158.55";
profiles.system = {
user = "root";
activation = "nixos";
closure = self.nixosConfigurations.hetzner-1;
sshUser = "tlater";
sshOpts = [
"-p"
"2222"
"-o"
"ForwardAgent=yes"
];
path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.hetzner-1;
};
sshUser = "tlater";
sshOpts = [
"-p"
"2222"
"-o"
"ForwardAgent=yes"
];
};
}
);
};
#########
# Tests #
#########
checks.${system} = import ./checks (inputs // { inherit system; });
###########################
# Garbage collection root #
###########################
packages.${system} =
let
localPkgs = import ./pkgs { inherit pkgs; };
in
{
default = vm.config.system.build.vm;
crowdsec-hub = localPkgs.crowdsec.hub;
crowdsec-firewall-bouncer = localPkgs.crowdsec.firewall-bouncer;
};
###################
# Utility scripts #
###################
apps.${system} = {
default = self.apps.${system}.run-vm;
run-vm = {
type = "app";
program =
(pkgs.writeShellScript "" ''
${vm.config.system.build.vm.outPath}/bin/run-testvm-vm
'').outPath;
};
update-crowdsec-packages =
let
git = pkgs.lib.getExe pkgs.git;
nvfetcher = pkgs.lib.getExe pkgs.nvfetcher;
in
{
type = "app";
program =
(pkgs.writeShellScript "update-crowdsec-packages" ''
cd "$(${git} rev-parse --show-toplevel)"
cd ./pkgs/crowdsec
${nvfetcher}
echo 'Remember to update the vendorHash of any go packages!'
'').outPath;
};
};
###########################
# Development environment #
###########################
devShells.${system}.default = nixpkgs.legacyPackages.${system}.mkShell {
sopsPGPKeyDirs = [
"./keys/hosts/"
"./keys/users/"
];
nativeBuildInputs = [ sops-nix.packages.${system}.sops-import-keys-hook ];
packages = with pkgs; [
sops-nix.packages.${system}.sops-init-gpg-key
deploy-rs.packages.${system}.default
nixpkgs-fmt
cargo
clippy
rustc
rustfmt
rust-analyzer
pkg-config
openssl
];
};
};
}

View file

@ -1,136 +0,0 @@
{ lib, ... }@exportingFlake:
let
inherit (lib) mkOption types;
deploy-rs-for-system =
system:
(import exportingFlake.inputs.nixpkgs {
inherit system;
overlays = [
exportingFlake.inputs.deploy-rs.overlays.default
(_final: prev: {
deploy-rs = {
inherit (exportingFlake.inputs.nixpkgs.legacyPackages.${system}) deploy-rs;
inherit (prev.deploy-rs) lib;
};
})
];
}).deploy-rs;
in
{ config, ... }:
let
cfg = config.deploy;
in
{
options.deploy =
let
genericOptions =
let
mkGenericOption =
type:
mkOption {
type = types.nullOr type;
default = null;
};
in
{
options = {
sshUser = mkGenericOption types.str;
user = mkGenericOption types.str;
sshOpts = mkGenericOption (types.listOf types.str);
fastConnection = mkGenericOption types.bool;
autoRollback = mkGenericOption types.bool;
magicRollback = mkGenericOption types.bool;
confirmTimeout = mkGenericOption types.int;
activationTimeout = mkGenericOption types.int;
tempPath = mkGenericOption types.str;
interactiveSudo = mkGenericOption types.bool;
};
};
profileModule =
{ config, ... }:
{
imports = [ genericOptions ];
options = {
activation = mkOption {
type = types.oneOf [
(types.enum [
"nixos"
"home-manager"
"darwin"
"noop"
])
];
};
closure = mkOption { type = types.raw; };
profilePath = mkOption {
type = types.nullOr types.str;
default = null;
};
path = mkOption {
type = types.raw;
internal = true;
};
};
config =
let
inherit (config.closure.config.nixpkgs.hostPlatform) system;
deploy-rs = deploy-rs-for-system system;
in
lib.mkMerge [
(lib.mkIf (lib.elem config.activation [
"nixos"
"home-manager"
"darwin"
"noop"
]) { path = deploy-rs.lib.activate.${config.activation} config.closure; })
];
};
nodeModule = {
imports = [ genericOptions ];
options = {
hostname = mkOption { type = types.str; };
profilesOrder = mkOption {
type = types.listOf types.str;
default = [ ];
};
profiles = mkOption {
type = types.attrsOf (types.submoduleWith { modules = [ profileModule ]; });
apply = lib.mapAttrs (
_: profile:
lib.filterAttrs (
name: val:
!(lib.elem name [
"activation"
"closure"
])
&& val != null
) profile
);
default = { };
};
};
};
in
{
nodes = mkOption {
default = { };
type = types.attrsOf (types.submoduleWith { modules = [ nodeModule ]; });
apply = lib.mapAttrs (_: node: lib.filterAttrs (_: val: val != null) node);
};
};
config = lib.mkIf (cfg.nodes != { }) { flake.deploy.nodes = cfg.nodes; };
}

File diff suppressed because one or more lines are too long

View file

@ -1,8 +1,8 @@
tlaternet:
ntfy-topic: ENC[AES256_GCM,data:BSNLP9hLQKufDf3STknQ,iv:4WeYARwDEg84fh8qMa4szssQeK2orBl72oiIRywXCi8=,tag:GtS7YAlQiuGAWaUn+JxaYg==,type:str]
ntfy:
#ENC[AES256_GCM,data:B5sUuCWTheo/Lz5kAN5/8NeiT7ohQ6IRYjD7k/c0,iv:E85yGjNcd9RGOgBKFPxRB6LqdqISNcjlvp/pqpR7VPg=,tag:1NrwtWyoVp4qDWiqAt/few==,type:comment]
users: ENC[AES256_GCM,data:zW02xRlLvqJ0/rLJMX9/LSW7CQ26nIyifOLYba4AkstQcrVMOIV1Je4QwaKoy2TPhDcVcjLg6Ce2/0qxpO7Q3rqoXqACbPAUwcxzn14KOt7tEb3IE2S//0Y/law=,iv:sV5Dvplr6A5ivrI/+Cyl6mC+Zxo8NORxfuhEZ/75JbU=,tag:NzqxoN3Hr0YO2CXiVdEo+A==,type:str]
authelia:
storage-encryption-key: ENC[AES256_GCM,data:iMV4DGwvOOq+DZao+Jrc3i15HOPFXHv3m6dzrAb7n8zV8bdLz5c4MCpq61hQy/UfoXsRYJUxwCcj3B70JQn2Ngq6P9ik9U1ZfYrwUWIENSd/iG8CBfdasKqxEijS2F23Lj1rbB4ppTWD9lWqRoKOEaXDL9Rqn02tiLbR3OewOpwiwbzv0PkVlC6yUV+yS3Jx,iv:1V2cwoV4kG3i9e9dv7PWPCNoFPIgYiZ2m3A8Agf3Jpc=,tag:AiFLpQ7nqwx9xZ71sbCn2g==,type:str]
jwt-secret: ENC[AES256_GCM,data:QA64lfervZk=,iv:MtyCZrbGzX+oKTBPW9R+n/r8TaFkK0xSwjn/qUT6ntQ=,tag:z/XnDGiLDkJ0xPVveeR2cA==,type:str]
session-secret: ENC[AES256_GCM,data:lYk4FOO4sQM=,iv:z05n1zPt1ONNqN6sgITUTu+GSe6xev4cYm8c4xzp/Mg=,tag:TRQAbxjvaoo/tnLxO43KKg==,type:str]
lldap-password: ENC[AES256_GCM,data:cafjSRCUXAg=,iv:vZM/6efEhWjjTKNnDuS+Kj2i/eOkn20vT2JmtcHGcSU=,tag:P1GscnNwMoWCMMDbMVMPqA==,type:str]
porkbun:
api-key: ENC[AES256_GCM,data:A5J1sqwq6hs=,iv:77Mar3IX7mq7z7x6s9sSeGNVYc1Wv78HptJElEC7z3Q=,tag:eM/EF9TxKu+zcbJ1SYXiuA==,type:str]
secret-api-key: ENC[AES256_GCM,data:8Xv+jWYaWMI=,iv:li4tdY0pch5lksftMmfMVS729caAwfaacoztaQ49az0=,tag:KhfElBGzVH4ByFPfuQsdhw==,type:str]
@ -14,6 +14,9 @@ forgejo:
grafana:
adminPassword: ENC[AES256_GCM,data:dYfaxUpQpzA=,iv:j5wSem8C5+V4c5qRzXQJhsU7/FOtpvrnaEyFBmW6zJ4=,tag:oc8n3TkEbjF2gjuOobZuLA==,type:str]
secretKey: ENC[AES256_GCM,data:Atruvh2MsNY=,iv:y2MaCUCEzGIydHp6G0DJHfk289S1is0twKm2oUYwDhM=,tag:nAWeg+YqaYqk6k22oBkAhQ==,type:str]
lldap:
admin-password: ENC[AES256_GCM,data:s18N1fvXtzE=,iv:FGXF5+PwDZrQIJylx+pkjY4SO0mmfiGUPZeFAINmGnY=,tag:rpPSFdWzCHhyp4ITddRekg==,type:str]
key: ENC[AES256_GCM,data:spbrfjm4Ozhu6XAPxN1cuQ==,iv:QEDCGfl75aP0T68nbWmqkPem46FHrs8nj7zVkWYcHt4=,tag:P4p3rC5I2KqPm733wbTp9g==,type:str]
nextcloud:
tlater: ENC[AES256_GCM,data:91kDcO4hpng=,iv:ayuILRmRru4ZxTCur9H2xHuLjkDzwPdS/4lEog/tesU=,tag:qYhJxnNDcCwUM7xe7Tlcjw==,type:str]
steam:
@ -21,48 +24,59 @@ steam:
heisenbridge:
as-token: ENC[AES256_GCM,data:tXbOeo7nv8I=,iv:wJAKcOXX9nGIw4n38ThOoj29u7dUWhsxSQG/p79JlEw=,tag:rTVaGS2UuWcea1uBa8YX2g==,type:str]
hs-token: ENC[AES256_GCM,data:VBwvwomv0Xg=,iv:q6INtJ+rg+QiXj8uBdBzQYQZUBBXp+9odxDHwvu8Jxc=,tag:XKhm8nxygAkKaiVPJ2Fcdg==,type:str]
matrix-hookshot:
as-token: ENC[AES256_GCM,data:uSUOo4f2KqA=,iv:Xb9G8Ecv6m59m51kDw2bOfq3SMJt4g9/6/EdH74R+KM=,tag:K9MSfO2c2Y4rlf0eYrmTnw==,type:str]
hs-token: ENC[AES256_GCM,data:0KsyA06InL4=,iv:zAR0Y1fk8SyodcSLBHlQ8I+BAmttz9Hkd8Q3OREFqs4=,tag:t1Et8N/3seq95DeGoUd7Sw==,type:str]
wireguard:
server-key: ENC[AES256_GCM,data:FvY897XdKoa/mckE8JQLCkklsnYD6Wz1wpsu5t3uhEnW3iarnDQxF9msuYU=,iv:jqGXfekM+Vs+J9b5nlZ5Skd1ZKHajoUo2Dc4tMYPm1w=,tag:EehikjI/FCU8wqtpvJRamQ==,type:str]
restic:
local-backups: ENC[AES256_GCM,data:3QjEv03t7wE=,iv:y/6Lv4eUbZZfGPwUONykz8VNL62cAJuWaJy9yk3aAmk=,tag:wMlGsepuG9JjwtUKGWSibw==,type:str]
storagebox-backups: ENC[AES256_GCM,data:NEHk57B3YtI=,iv:0/qnqMVK0662sgfDQoLxcW7L09SKF8E5liCnjaQ2+2k=,tag:RU0BPwGgvI9bgOPr8VItmA==,type:str]
storagebox-ssh-key: ENC[AES256_GCM,data:65+kbJPO90y+rRh3Q5cqLDtQa3VFfbaDPPo1nJLqxgAB7Wm3J7K4qUYAKPcYnkWV4/xFz63R2uCNaq5xv+vuZA==,iv:O7AeE/ujp5p1P7nff7PpghQfN2tQUYBSWL+EHRbE5yA=,tag:Pu/+bEAQuqwmD1Rc//t0cA==,type:str]
turn:
env: ENC[AES256_GCM,data:xjIz/AY109lyiL5N01p5T3HcYco/rM5CJSRTtg==,iv:16bW6OpyOK/QL0QPGQp/Baa9xyT8E3ZsYkwqmjuofk0=,tag:J5re3uKxIykw3YunvQWBgg==,type:str]
secret: ENC[AES256_GCM,data:eQ7dAocoZtg=,iv:fgzjTPv30WqTKlLy+yMn5MsKQgjhPnwlGFFwYEg3gWs=,tag:1ze33U1NBkgMX/9SiaBNQg==,type:str]
ssl-key: ENC[AES256_GCM,data:RYfwHjBvwFXgXxXIEuWUzaycTdrCvmPivsNvvUIwDRynS5G2Dl6RCVp1w9zuLvoNun5ncUPGGuLMmVqN2wkJlw==,iv:UKI3bVTY7iTDNvp5UqrZ3QlQkMZ5p2bjgODEc6DCBfQ=,tag:sz7VTyRWyZxAsP4nE48DnA==,type:str]
#ENC[AES256_GCM,data:bxhKzU5Tzezl749CDu8e8kxa7ahGuZFaPa9K3kxuD+4sg5Hi3apgDlC0n8oK0DeiK4Ks7+9Cyw==,iv:T/zVJUpNAv1rR0a9+6SDTG08ws2A1hFBs5Ia3TpT0uk=,tag:uGXb1VryM+lIJ8r0I5durA==,type:comment]
ssl-cert: ENC[AES256_GCM,data:xHUr14CjKslgbGh/n5jYSOuCw9JRxS6YXE4fxS+aJzFcNeSeGNqoipPeuJupZGBnQP/FCqohiHY=,iv:/OEsVqRshGL9NIvntMC42EPZSNL0u6EfhtUBqgV7qog=,tag:4pxtNjuvy/ibm6nDtKdSkw==,type:str]
sops:
lastmodified: "2025-12-01T11:39:26Z"
mac: ENC[AES256_GCM,data:11VQAYk8Am0k8OO6BtU17qpuEhcJ8ylRhJWQNHVAsmi5BCFjD1zU3NkWhtSstPrBcqHMenG+9XuEzpNnbccHI2ru0qlILsQvNj5OKo96FnvYtzApYlApoAzOetCx08Lfxa4RGLN/XCUSuccjBIU2PZRWEK+z+Cm1wHUFeqc1xPc=,iv:6y9j55Cld+GoOVGWAqsEgURRna6dHA2mGZwHVA+ZOE8=,tag:bSZi3nYmYrn3nFT2+RBPUQ==,type:str]
lastmodified: "2025-05-26T19:48:08Z"
mac: ENC[AES256_GCM,data:dIuIJlkz6yUKt1MrOgwQTYm8DzwldN4o2E0B89p8c0WeCxgHwWVMtIHt8QsrIet9X7aHDx97/S67XzYEGSXI/9Btc+GCEFNcoVsgZzXy+d7r3io2gQxNrQ6yJwJvStW+ZeWPujg5nJ6NNfhcweLFdquwNwNJ4DC1sGvCl5/m/QY=,iv:n5f8urvZVLfIp46nysSJh3ngx+H8qxLuycmDZVYDOCk=,tag:5ccJtPdIRZmFXDVO1V6y3w==,type:str]
pgp:
- created_at: "2025-10-03T21:38:26Z"
- created_at: "2025-01-21T17:55:30Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hF4DjPSW5uJlxqMSAQdAf6HxNOE9bH2dQOF2E5HnAJT6b8cZwEJc0TgNoVpeB08w
bqksAeuTLWp2MI05J2L9lGSgjpNRERacNmwacx3ZYEIq0iuUhiL41bJXcJjgH4j1
0lYBGmTQ3NBGyFucFEvmuZW2X8ZlgTldszehheiuWIzvlUmgFQHue2TlPAaNY8of
ekOj13blDxtmz4bnxvcGjZlWOOBDeYz2ams7UI+FnG+zc5o4GqRywA==
=5y2E
hQEMA7x7stsXx45CAQf9ELnm9TdXCIO6fTPiSCkKthx0tSHqBWX/s63158k4IUu7
v0WWgQy0SKFU3AwIFuVaAYEXB32SaOWKq2WbVAbFZU+xhyUmNe9asg9Fl24+zjGI
oYnPzv3lz/5vcI6Q9rZi8F2uIi2GQZnbscS1XfjA5u17uOalQpb0hjUXr0LMaUvU
Sggm1ZMKE1o1mHAWK6ZT9SrTMIroWFArJRZLS1eY/vI9ja7I5YR3z0MkLqIvdIp+
B4DsOXLlqAqtVoPGcK1CixQiXzzwyQAYHyJc3JFDpaF9Y4S5/bkMLGyQVMA259a+
W7ge+EngdJdXV8Unj4s7ndB1e1iM87Jc+4YOcA7jC9JeAR98n8GL+MN9vE8q2AsB
qSOXiGSwmAkhq7+ZwJHWlivlB1In0coyJ4eMd/yhyBuc2NrstO0t595HlA93GcLN
5JsXIFMqklqGSzE2KgEXhxa2aUoJxcpApVz2BLFPvg==
=X72N
-----END PGP MESSAGE-----
fp: 8322CC1D351D8EFE63F3D27A8CF496E6E265C6A3!
- created_at: "2025-10-03T21:38:26Z"
fp: B9C7AE554602D82B1AC2CFD0BC7BB2DB17C78E42!
- created_at: "2025-01-21T17:55:30Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMA/3lh+ZzfS28ARAAiuuzmYY/KBYSKjRWBnpbG5myhJlddDP1lplkd3S8vk9G
S5tTJRqfSEOUPPMiUW7v0KP9+DkSfE1RnF0JyUNKo8GmMerw3fo6hLsDPa35rse8
QgAmO1o0h2Yd8oUe+c3BYtTTnF2hyMhxv3SxSdxcBN//rggNuIAo+K5M67RKDidV
5q2en9rclWYezI/OT6UPtoDwdff8LeHbF5skkfOpQkACStGCIioQB7fnDaLc5DwG
ExaWWNTHolLjMMc6cuv7LGQNUS0h83Li5X8FM5op7BJdxH6p8UxsmGlzKhhuR5ON
kyljM3ICaS1QJc1OuD/RruYTitbVGCQfZT8/zfgcTJA6PGMNsbDG6oxXoLWjgAwO
ImQ7MmidcN52npzumQDm7Tv4yyQKyv4tgOSQSLL87++uLUZE0KLr490f7n/wOEAE
augOkNiid5icvPOQ8sD8kUTX27vhGPG2nuRfzVWt3EObwak1fde5GmWZSR2famK5
nvKhLthsp5a+Iz1f3mK4zdfxJSneOrRdTrCP35CVIW/BY9F+Hs6PaBeVVh37/6HE
DuDYiWlPpeADt2JV4M0Ri1W4QK6wIqZi9AFH3uesoavR5ONj/Eb6K6chYy2ZGepN
qJgMLuoBa+ZlyC1ju96Qt1TKmtzDKnPcygNQi2OyS0mEYMJrcNrcVTWRffh3VLfS
VgGjmzLSDkyVFthF0D2SQvUp0DzXEr+dZw3NhFgMy+icSOZi6+FHkfCWjJcNiJ10
7bbburzdjQC50fJfg8pNEpbI6WCfXrfj2Gj8zMLIpXOfvnVJ/nxa
=mYN1
hQIMA/3lh+ZzfS28AQ//TPVk5x8wZtKnNcbZjqmXUvi7FzaDMGYFNtiX+bQ17Lkm
qdo9TcH8gZI+xni6wlROQBVmu5MAVkT+NWnZIyxN7pokQPmb2v9zo7oyUPV70owU
dD/LDNVqgyFTVzWKco4wr8CUwQWhOJI9wM0sjTCOTTbCT5hYVnJOLe630sNG9G5b
cJYvaQFxKJeHKJq/DL1I9wT02gOE7vu2xh2OyEozEz3SlB7Bp2mGTLNSiOZBhbzh
DNwFRTeDc1Z/ACQJyoGEXkmj1VLyBFuCfXu4UvUQSmlfyCXKTD1/CUo2uiw1N44W
sYh5UpIrCU2eVAAXAiD1nB6dHxiau3QlapNQfbY2sOZVSnsB21yIja9C3aQN/0Q5
gRrKYcwULzLZ0Z3oQqQxQG9acU8L9CwjKJ1vOKgPVF7hcWkba2bLVsMMaK6seVNc
jazp9gDAj440S2aE86CdQvgcOEsfgPhBZrulYglbhW8ZaIN2SdjDN/xP1Tn5PadP
gS2DVgqILKPRMF4VavzV3uhEA77QF39Hr18SeToNWcDmfqNPNNf7HpnogstGPf3y
xrFAysLbD8IClU4LqI5M19akODON8qeQa5QD+jHOJmAYnMYNRmY/IwHb/SC5WyIm
EPNZg5E+q9cNrTKtEIuWec0SObqpaUz2E/Vt9+dge0uVgTA/QqPvMP7x19XBrRDS
WAFiKkv7MxImNgcqqe7D3StZoeNm+RXJiULaxxuR8qmMnmvaC+L7ggI4QR3TPQw2
mSoi7SkdjQUCa8ut15UwNHTGO+smbRs9aonGP4G7c1cOH90YYvR+BTs=
=q2IG
-----END PGP MESSAGE-----
fp: 2f5caa73e7ceea4fcc8d2881fde587e6737d2dbc
unencrypted_suffix: _unencrypted
version: 3.11.0
version: 3.10.2

View file

@ -1 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMMst2rs9WuvWnRTOuQElDMx0/cf4n9x9lC1+8clT0LZ openpgp:0xDD46BD5E
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1QMrJrjjWOjQf9wF5JC52EHtDh03grRHdS0PHlnc9kLDXYw8n41KEN2vE2skVTKBVufQhuckCAlXd6BpisW1YoyhaLs9Bcp6Un63VH3YvRvXzKtwXJHEv/5SnDrGqxo5UF6xTVTxC9ux0dnSSizAeQiremr9Zuzz4B25NJCiZ2lVZUOi8iG1m4082owuwZWIW6qrFWi3gUGtjz32EM3eUX75BE1ZcDap7BJj2GdYsMHO2iiHBZwQVOXd7Fvk/23f9IeQ95D2RMgeQun8OIF+7X5zaWzyzq1Tky4pijaSsUebnFk9Cxm6KRg+yXgmIjpskskCY6kj82+wqFjPRIgxf cardno:000606358963

View file

@ -1,44 +1,240 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEaN+Y2hYJKwYBBAHaRw8BAQdAxpAAfVB+wwIcxEqLhZXGOLQcGzN8tIvnL4RM
ovzDyOmI0QQfFgoAgwWCaN+Y2gWJBaSPvQMLCQcJEJFYJf4CDDsERxQAAAAAAB4A
IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ8pYda8oUsLzLqj1avZv2DOc
sT3a3jJNKz4NsRpp2/xUAxUKCAKbAQIeCRYhBBypFiLIAlMIN83xvZFYJf4CDDsE
AADVSwD+P3Jy6qBwbgPwgZzf5+cikSnMLwMuEUdI+jY6ICFrB1UA/1uwLVdjveRY
OwqhrPbuc00DFlAUDTXVhudNAwrIo1UFtA88dG1AdGxhdGVyLm5ldD6I0QQTFgoA
gwWCaN+Y2gWJBaSPvQMLCQcJEJFYJf4CDDsERxQAAAAAAB4AIHNhbHRAbm90YXRp
b25zLnNlcXVvaWEtcGdwLm9yZ7YE8O8QdVSn74dRU3+tbzkaA5vOxcG8hPiHxC92
pwUQAxUKCAKbAQIeCRYhBBypFiLIAlMIN83xvZFYJf4CDDsEAAB6fAD+J8JUTiGP
8YsMdL/EQpnrYT6rnn15ubnEFYgq8uoLXREA/0xanMgtFfjlLBl1KJKATcWW1N3Y
UIwVc5fSMQlMMhcCtBRUcmlzdGFuIERhbmnDq2wgTWFhdIjUBBMWCgCGBYJo35ja
BYkFpI+9AwsJBwkQkVgl/gIMOwRHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2Vx
dW9pYS1wZ3Aub3Jn0BVq6ZTyEVzQbva4FzWzthlLI0RLKFehK5KrzYxHtUADFQoI
ApkBApsBAh4JFiEEHKkWIsgCUwg3zfG9kVgl/gIMOwQAADBZAPsFyfzAPbvpTHIM
Iy3NCjnTIEkKyUB0/Bv05XpSTJEIXAD+K6Xt7rXexehL9cU/uHbv79kkalY4dfxo
HIIMDBiFQg+4MwRo35jaFgkrBgEEAdpHDwEBB0BMgkmV8/WIQUCDNFyZ8vy6NRlf
bLUkRoeXs1goAMl1mIkBhQQYFgoBNwWCaN+Y2gWJBaSPvQkQkVgl/gIMOwRHFAAA
AAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnYPaWFUQUEGfbaDyY
ySYmH15HQu+/s1WIYTghFMsDfIICmwK+oAQZFgoAbwWCaN+Y2gkQAuk1AGzy6OdH
FAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnC4a3260kzH8Q
IQldNaxtdHhLScfZJNVvS2oA4ekTGasWIQSCgqelFkdq3xOTIn4C6TUAbPLo5wAA
dvMA/1MQKUrQDmqCTrZY7UB0teiInkZKZRmP3JC3yhVVKCuqAP9ESMmGyJZ6CMxZ
jmJJCfBkXVuTD0On6qFJA8q5TN46CBYhBBypFiLIAlMIN83xvZFYJf4CDDsEAAD1
UAEAmNy3+9KMYoiZee8q3HPjggDLxUxgPqGilGTux2UuIsIBAMtpBoN0RRAw2wau
bG0KsuS7uByzgSvVI3fIBPXW60QAuDMEaN+Y2hYJKwYBBAHaRw8BAQdAwyy3auz1
a69adFM65ASUMzHT9x/if3H2ULX7xyVPQtmJAYUEGBYKATcFgmjfmNoFiQWkj70J
EJFYJf4CDDsERxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y
Z+z6/cvmpd8w4gdbRaiLwucfwxEXr6gHp3BSXh+nKmHtApsgvqAEGRYKAG8Fgmjf
mNoJEARvgyjdRr1eRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdw
Lm9yZ0OtJQh/BjruiyemJkYA8G/Y4dpegcQxcsLerz1YoE1nFiEE3HwYRWAJ0IIM
K3u3BG+DKN1GvV4AABCTAP9wfTED3ymUQUi5ZuRqN7ZOvSjY1rNg/hAP3XFrf1dG
BAD/ZGdj6daBbenCNO2J3kXHdRSX0rFsZd63cGD4KksGfwwWIQQcqRYiyAJTCDfN
8b2RWCX+Agw7BAAARfIBALiWY/wyxxdQe1DQ8seufBPmIWQO06pKknMP4WRPxmlj
AQDm/MS7m+XqALr0M2LL6AxSaSW5hXROgq85ppTMhtQCBrg4BGjfmNoSCisGAQQB
l1UBBQEBB0CLw491EDOnLPf2eKA5nwSp/ZMxjrUgkA/i4IFtcDNxbQMBCAeIxgQY
FgoAeAWCaN+Y2gWJBaSPvQkQkVgl/gIMOwRHFAAAAAAAHgAgc2FsdEBub3RhdGlv
bnMuc2VxdW9pYS1wZ3Aub3Jn3mOPxHpLk/tMD65hWa3xbhfZR1Oq4GFM28/HWoKL
t9wCmwwWIQQcqRYiyAJTCDfN8b2RWCX+Agw7BAAAKBIBAJ61F1bGwQyLlTmYK6Ga
THrdlscsaDkPy4EcFa4czg88AQD+FcRvkydZj6ClgTbFvV2iAI5yjOBccBrp2OK1
M4MECg==
=gDMv
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
GmeDneCrlseO
=ncr2
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,383 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.security.crowdsec;
settingsFormat = pkgs.formats.yaml { };
hub = pkgs.fetchFromGitHub {
owner = "crowdsecurity";
repo = "hub";
rev = "7a3b4753f4577257c0cbeb8f8f90c7f17d2ae008";
hash = "sha256-HB4jHyhiO8gjBkLmpo6bDbwhfm5m5nAtNlKhDkZjt2I=";
};
cscli = pkgs.writeShellScriptBin "cscli" ''
export PATH="$PATH:${cfg.package}/bin/"
sudo=exec
if [ "$USER" != "crowdsec" ]; then
sudo='exec /run/wrappers/bin/sudo -u crowdsec'
fi
$sudo ${cfg.package}/bin/cscli "$@"
'';
acquisitions = ''
---
${lib.concatMapStringsSep "\n---\n" builtins.toJSON cfg.acquisitions}
---
'';
in
{
imports = [ ./remediations ];
options.security.crowdsec =
let
inherit (lib.types)
nullOr
listOf
package
path
str
;
in
{
enable = lib.mkEnableOption "crowdsec";
package = lib.mkOption {
type = package;
default = pkgs.crowdsec;
};
stateDirectory = lib.mkOption {
type = path;
readOnly = true;
description = ''
The state directory of the crowdsec instance. Cannot be
changed, but is exposed for downstream use.
'';
};
settings = lib.mkOption {
inherit (settingsFormat) type;
default = { };
description = ''
The crowdsec configuration. Refer to
<https://docs.crowdsec.net/docs/next/configuration/crowdsec_configuration/>
for details on supported values.
'';
};
parserWhitelist = lib.mkOption {
type = listOf str;
default = [ ];
description = ''
Set of IP addresses to add to a parser-based whitelist.
Addresses can be specified either as plain IP addresses or
in CIDR notation.
'';
};
acquisitions = lib.mkOption {
type = listOf settingsFormat.type;
default = [ ];
description = ''
Log acquisitions.
'';
};
extraGroups = lib.mkOption {
type = listOf str;
default = [ ];
description = ''
Additional groups to make the service part of.
Required to permit reading from various log sources.
'';
};
hubConfigurations = {
collections = lib.mkOption {
type = listOf str;
description = ''
List of pre-made crowdsec collections to install.
'';
};
scenarios = lib.mkOption {
type = listOf str;
description = ''
List of pre-made crowdsec scenarios to install.
'';
};
parsers = lib.mkOption {
type = listOf str;
description = ''
List of pre-made crowdsec parsers to install.
'';
};
postoverflows = lib.mkOption {
type = listOf str;
description = ''
List of pre-made crowdsec postoverflows to install.
'';
};
appsecConfigs = lib.mkOption {
type = listOf str;
description = ''
List of pre-made crowdsec appsec configurations to install.
'';
};
appsecRules = lib.mkOption {
type = listOf str;
description = ''
List of pre-made crowdsec appsec rules to install.
'';
};
};
centralApiCredentials = lib.mkOption {
type = nullOr path;
default = null;
description = ''
The API key to access crowdsec's central API - this is
required to access any of the shared blocklists.
Use of this feature is optional, entering no API key (the
default) turns all sharing or receiving of blocked IPs off.
Note that adding the API key by itself does not enable
sharing of blocked IPs with the central API. This limits the
types of blocklists this instance can access.
To also turn sharing blocked IPs on, set
`api.server.online_client.sharing = true;`.
'';
};
ctiApiKey = lib.mkOption {
type = nullOr path;
default = null;
description = ''
The API key for crowdsec's CTI offering.
'';
};
};
config = lib.mkIf cfg.enable {
# Set up default settings; anything that *shouldn't* be changed is
# set to the default priority so that users need to use
# `lib.mkForce`.
security.crowdsec = {
stateDirectory = "/var/lib/crowdsec";
settings = {
common = {
daemonize = true;
# The default logs to files, which isn't the preferred way
# on NixOS
log_media = "stdout";
};
config_paths = {
config_dir = "${cfg.stateDirectory}/config/";
data_dir = "${cfg.stateDirectory}/data/";
# This "config" file is intended to be written to using the
# cscli tool, so you can temporarily make it so rules don't
# do anything but log what they *would* do for
# experimentation.
simulation_path = "${cfg.stateDirectory}/config/simulation.yaml";
pattern_dir = lib.mkDefault "${cfg.package}/share/crowdsec/config/patterns";
hub_dir = hub;
index_path = "${hub}/.index.json";
# Integrations aren't supported for now
notification_dir = lib.mkDefault "/var/empty/";
plugin_dir = lib.mkDefault "/var/empty/";
};
crowdsec_service.acquisition_path =
# Using an if/else here because `mkMerge` does not work in
# YAML-type options
if cfg.acquisitions == [ ] then
"${cfg.package}/share/crowdsec/config/acquis.yaml"
else
pkgs.writeText "acquis.yaml" acquisitions;
cscli = {
prometheus_uri = lib.mkDefault "127.0.0.1:6060";
};
db_config = {
type = lib.mkDefault "sqlite";
db_path = lib.mkDefault "${cfg.stateDirectory}/data/crowdsec.db";
use_wal = lib.mkDefault true;
flush = {
max_items = lib.mkDefault 5000;
max_age = lib.mkDefault "7d";
};
};
api = {
cti = {
enabled = cfg.ctiApiKey != null;
key = cfg.ctiApiKey;
};
client.credentials_path = "${cfg.stateDirectory}/local_credentials.yaml";
server = {
listen_uri = lib.mkDefault "127.0.0.1:8080";
profiles_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/profiles.yaml";
console_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/console.yaml";
online_client = {
# By default, we don't let crowdsec phone home, since
# this is usually within NixOS users' concerns.
sharing = lib.mkDefault false;
credentials_path = cfg.centralApiCredentials;
};
};
};
# We enable prometheus by default, since cscli relies on it
# for metrics
prometheus = {
enabled = lib.mkDefault true;
level = lib.mkDefault "full";
listen_addr = lib.mkDefault "127.0.0.1";
listen_port = lib.mkDefault 6060;
};
};
};
systemd.packages = [ cfg.package ];
environment = {
systemPackages = [
# To add completions; sadly need to hand-roll this since
# neither `symlinkJoin` nor `buildEnv` have collision
# handling.
(pkgs.runCommandNoCCLocal "cscli" { } ''
mkdir -p $out
ln -s ${cscli}/bin $out/bin
ln -s ${cfg.package}/share $out/share
'')
];
etc."crowdsec/config.yaml".source = settingsFormat.generate "crowdsec-settings.yaml" cfg.settings;
};
systemd = {
tmpfiles.settings."10-crowdsec" = {
"${cfg.stateDirectory}".d = {
user = "crowdsec";
group = "crowdsec";
mode = "0700";
};
# This must be created for the setup service to work
"${cfg.stateDirectory}/config".d = {
user = "crowdsec";
group = "crowdsec";
mode = "0700";
};
"${cfg.stateDirectory}/config/parsers".d = lib.mkIf (cfg.parserWhitelist != [ ]) {
user = "crowdsec";
group = "crowdsec";
mode = "0700";
};
"${cfg.stateDirectory}/config/parsers/s02-enrich".d = lib.mkIf (cfg.parserWhitelist != [ ]) {
user = "crowdsec";
group = "crowdsec";
mode = "0700";
};
"${cfg.stateDirectory}/config/parsers/s02-enrich/nixos-whitelist.yaml" =
lib.mkIf (cfg.parserWhitelist != [ ])
{
"L+".argument =
(settingsFormat.generate "crowdsec-nixos-whitelist.yaml" {
name = "nixos/parser-whitelist";
description = "Parser whitelist generated by the crowdsec NixOS module";
whitelist = {
reason = "Filtered by NixOS whitelist";
ip = lib.lists.filter (ip: !(lib.hasInfix "/" ip)) cfg.parserWhitelist;
cidr = lib.lists.filter (ip: lib.hasInfix "/" ip) cfg.parserWhitelist;
};
}).outPath;
};
};
services = {
crowdsec-setup = {
# TODO(tlater): Depend on tmpfiles path for
# /var/lib/crowdsec/config
description = "Crowdsec database and config preparation";
script = ''
if [ ! -e '${cfg.settings.config_paths.simulation_path}' ]; then
cp '${cfg.package}/share/crowdsec/config/simulation.yaml' '${cfg.settings.config_paths.simulation_path}'
fi
if [ ! -e '${cfg.settings.api.client.credentials_path}' ]; then
${cfg.package}/bin/cscli machines add --auto --file '${cfg.settings.api.client.credentials_path}'
fi
'';
serviceConfig = {
User = "crowdsec";
Group = "crowdsec";
StateDirectory = "crowdsec";
Type = "oneshot";
RemainAfterExit = true;
};
};
# Note that the service basics are already defined upstream
crowdsec = {
enable = true;
after = [ "crowdsec-setup.service" ];
bindsTo = [ "crowdsec-setup.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = "crowdsec";
Group = "crowdsec";
SupplementaryGroups = cfg.extraGroups;
StateDirectory = "crowdsec";
};
};
};
};
users = {
users.crowdsec = {
isSystemUser = true;
home = cfg.stateDirectory;
group = "crowdsec";
};
groups = {
crowdsec = { };
};
};
};
}

View file

@ -0,0 +1,87 @@
{
flake-inputs,
pkgs,
lib,
config,
...
}:
let
inherit (flake-inputs.self.packages.${pkgs.system}) crowdsec-firewall-bouncer;
crowdsecCfg = config.security.crowdsec;
cfg = crowdsecCfg.remediationComponents.firewallBouncer;
settingsFormat = pkgs.formats.yaml { };
in
{
options.security.crowdsec.remediationComponents.firewallBouncer = {
enable = lib.mkEnableOption "cs-firewall-bouncer";
settings = lib.mkOption {
inherit (settingsFormat) type;
default = { };
description = ''
The bouncer configuration. Refer to
<https://docs.crowdsec.net/u/bouncers/firewall/> for details
on supported values.
'';
};
};
config = lib.mkIf cfg.enable {
security.crowdsec.remediationComponents.firewallBouncer.settings = {
mode = lib.mkDefault "${if config.networking.nftables.enable then "nftables" else "iptables"}";
log_mode = "stdout";
iptables_chains = [ "nixos-fw" ];
# Don't let users easily override this; unfortunately we need to
# set up this key through substitution at runtime.
api_key = lib.mkForce "\${API_KEY}";
api_url = lib.mkDefault "http://${crowdsecCfg.settings.api.server.listen_uri}";
};
systemd = {
packages = [ crowdsec-firewall-bouncer ];
services = {
crowdsec-firewall-bouncer-setup = {
description = "Crowdsec firewall bouncer config preparation";
script = ''
if [ ! -e '${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml' ]; then
${crowdsecCfg.package}/bin/cscli -oraw bouncers add "cs-firewall-bouncer-$(${pkgs.coreutils}/bin/date +%s)" > \
${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml
fi
# Stdout redirection is deliberately used to forcibly
# overwrite the file if it exists
API_KEY="$(<${crowdsecCfg.stateDirectory}/firewall_bouncer_credentials.yaml)" \
${lib.getExe pkgs.envsubst} \
-i ${settingsFormat.generate "crowdsec-firewall-bouncer.yaml" cfg.settings} \
> /var/lib/crowdsec/config/crowdsec-firewall-bouncer.yaml
'';
serviceConfig = {
User = "crowdsec";
Group = "crowdsec";
Type = "oneshot";
RemainAfterExit = true;
};
};
crowdsec-firewall-bouncer = {
enable = true;
after = [ "crowdsec-firewall-bouncer-setup.service" ];
bindsTo = [ "crowdsec-firewall-bouncer-setup.service" ];
requiredBy = [ "crowdsec.service" ];
path =
lib.optionals (cfg.settings.mode == "ipset" || cfg.settings.mode == "iptables") [ pkgs.ipset ]
++ lib.optional (cfg.settings.mode == "iptables") pkgs.iptables
++ lib.optional (cfg.settings.mode == "nftables") pkgs.nftables;
};
};
};
};
}

View file

@ -0,0 +1 @@
{ imports = [ ./cs-firewall-bouncer.nix ]; }

View file

@ -1 +1,6 @@
{ imports = [ ./serviceTests/stub.nix ]; }
{
imports = [
./crowdsec
./nginxExtensions.nix
];
}

View file

@ -0,0 +1,59 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
services.nginx.domain = lib.mkOption {
type = lib.types.str;
description = "The base domain name to append to virtual domain names";
};
services.nginx.virtualHosts =
let
extraVirtualHostOptions =
{ name, config, ... }:
{
options = {
enableHSTS = lib.mkEnableOption "Enable HSTS";
addAccessLog = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Add special logging to `/var/log/nginx/''${serverName}`
'';
};
};
config = {
extraConfig = lib.concatStringsSep "\n" [
(lib.optionalString config.enableHSTS ''
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
'')
(lib.optionalString config.addAccessLog ''
access_log /var/log/nginx/${name}/access.log upstream_time;
'')
];
};
};
in
lib.mkOption { type = lib.types.attrsOf (lib.types.submodule extraVirtualHostOptions); };
};
config = {
# Don't attempt to run acme if the domain name is not tlater.net
systemd.services =
let
confirm = ''[[ "tlater.net" = ${config.services.nginx.domain} ]]'';
in
lib.mapAttrs' (
cert: _:
lib.nameValuePair "acme-${cert}" {
serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' '';
}
) config.security.acme.certs;
};
}

View file

@ -1,27 +0,0 @@
/**
Module containing mock definitions for service test runners.
*/
{ flake-inputs, lib, ... }:
{
imports = [
flake-inputs.sops-nix.nixosModules.sops
../.
../../configuration/services/backups.nix
];
sops.defaultSopsFile = ../../keys/staging.yaml;
environment.etc."staging.key" = {
mode = "0400";
source = ../../keys/hosts/staging.key;
};
services.openssh = {
enable = true;
hostKeys = lib.mkForce [
{
type = "rsa";
bits = 4096;
path = "/etc/staging.key";
}
];
};
}

View file

@ -1,20 +0,0 @@
/**
Module to make writing service-specific tests easy.
*/
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
options = {
serviceTests = mkOption {
type = types.attrsOf types.package;
description = ''
NixOS tests to run.
'';
default = { };
};
};
}

View file

@ -0,0 +1,42 @@
{
"crowdsec-firewall-bouncer": {
"cargoLocks": null,
"date": null,
"extract": null,
"name": "crowdsec-firewall-bouncer",
"passthru": null,
"pinned": false,
"src": {
"deepClone": false,
"fetchSubmodules": false,
"leaveDotGit": false,
"name": null,
"owner": "crowdsecurity",
"repo": "cs-firewall-bouncer",
"rev": "v0.0.31",
"sha256": "sha256-59MWll8v00CF4WA53gjHZSTFc8hpYaHENg9O7LgTCrA=",
"type": "github"
},
"version": "v0.0.31"
},
"crowdsec-hub": {
"cargoLocks": null,
"date": "2025-05-17",
"extract": null,
"name": "crowdsec-hub",
"passthru": null,
"pinned": false,
"src": {
"deepClone": false,
"fetchSubmodules": false,
"leaveDotGit": false,
"name": null,
"owner": "crowdsecurity",
"repo": "hub",
"rev": "850614b9fcd4298f559b422c5ac685a69aa2e5ff",
"sha256": "sha256-96MMwFN5KongQA3YJVSuk7Kanbr1gR94CCyiflmez2k=",
"type": "github"
},
"version": "850614b9fcd4298f559b422c5ac685a69aa2e5ff"
}
}

View file

@ -0,0 +1,27 @@
# This file was generated by nvfetcher, please do not modify it manually.
{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
{
crowdsec-firewall-bouncer = {
pname = "crowdsec-firewall-bouncer";
version = "v0.0.31";
src = fetchFromGitHub {
owner = "crowdsecurity";
repo = "cs-firewall-bouncer";
rev = "v0.0.31";
fetchSubmodules = false;
sha256 = "sha256-59MWll8v00CF4WA53gjHZSTFc8hpYaHENg9O7LgTCrA=";
};
};
crowdsec-hub = {
pname = "crowdsec-hub";
version = "850614b9fcd4298f559b422c5ac685a69aa2e5ff";
src = fetchFromGitHub {
owner = "crowdsecurity";
repo = "hub";
rev = "850614b9fcd4298f559b422c5ac685a69aa2e5ff";
fetchSubmodules = false;
sha256 = "sha256-96MMwFN5KongQA3YJVSuk7Kanbr1gR94CCyiflmez2k=";
};
date = "2025-05-17";
};
}

View file

@ -0,0 +1,9 @@
{ pkgs }:
let
sources = pkgs.callPackage ./_sources/generated.nix { };
callPackage = pkgs.lib.callPackageWith (pkgs // { inherit sources; });
in
{
hub = callPackage ./hub.nix { };
firewall-bouncer = callPackage ./firewall-bouncer.nix { };
}

View file

@ -0,0 +1,26 @@
{
lib,
sources,
buildGoModule,
envsubst,
coreutils,
}:
let
envsubstBin = lib.getExe envsubst;
in
buildGoModule {
inherit (sources.crowdsec-firewall-bouncer) pname version src;
vendorHash = "sha256-7Jxvg8UEjUxnIz1llvXyI2AefJ31OVdNzhWD/C8wU/Y=";
postInstall = ''
mkdir -p $out/lib/systemd/system
CFG=/var/lib/crowdsec/config BIN=$out/bin/cs-firewall-bouncer ${envsubstBin} \
-i ./config/crowdsec-firewall-bouncer.service \
-o $out/lib/systemd/system/crowdsec-firewall-bouncer.service
substituteInPlace $out/lib/systemd/system/crowdsec-firewall-bouncer.service \
--replace-fail /bin/sleep ${coreutils}/bin/sleep
'';
}

1
pkgs/crowdsec/hub.nix Normal file
View file

@ -0,0 +1 @@
{ sources }: sources.crowdsec-hub.src

View file

@ -0,0 +1,7 @@
[crowdsec-hub]
src.git = "https://github.com/crowdsecurity/hub.git"
fetch.github = "crowdsecurity/hub"
[crowdsec-firewall-bouncer]
src.github = "crowdsecurity/cs-firewall-bouncer"
fetch.github = "crowdsecurity/cs-firewall-bouncer"

View file

@ -1,10 +1,5 @@
{ pkgs }:
{
perSystem =
{ pkgs, ... }:
{
packages = pkgs.lib.packagesFromDirectoryRecursive {
inherit (pkgs) callPackage;
directory = ./packages;
};
};
crowdsec = import ./crowdsec { inherit pkgs; };
starbound = pkgs.callPackage ./starbound { };
}

View file

@ -1,3 +0,0 @@
.direnv
.envrc
pack.zip

View file

@ -1,811 +0,0 @@
[Etc]
#ScreenShake(on/off)
"ScreenShake(on/off)" = true
#Forced viewpoint change when hit by a grab attack
"setThirdPerson(on/off)" = true
#Forced viewpoint change when hit by a grab attack
"setFirstPerson(on/off)" = true
["bosses Common settings"]
#custombossbar(on/off)
"custombossbar(on/off)" = true
#BossMusic(on/off)
"BossMusic(on/off)" = true
#BossMusicVolume(denominator)
# Default: 1
# Range: 1 ~ 1000000
BossMusicVolume = 1
#If the boss leaves the summoned location and there is no target, it returns to the summoned location. When set to 0, it does not return
# Default: 20
# Range: 0 ~ 200
ReturnHome = 20
[Weapon]
#Armor Infinity Durability(on/off)
"Armor Infinity Durability(on/off)" = true
#Bulwark of the Flame's Cooldown
# Default: 80
# Range: 0 ~ 1000000
BulwarkOfTheFlameCooldown = 80
#Gauntlet of Bulwark's Cooldown
# Default: 80
# Range: 0 ~ 1000000
GauntletOfBulwarkCooldown = 80
#Infernal Forge's Cooldown
# Default: 80
# Range: 0 ~ 1000000
InfernalForgeCooldown = 80
#Void Forge's Cooldown
# Default: 120
# Range: 0 ~ 1000000
VoidForgeCooldown = 120
#The Incinerator's Cooldown
# Default: 400
# Range: 0 ~ 1000000
TheIncineratorCooldown = 400
#Wither Assault Shoulder Weapon's Missile Cooldown
# Default: 40
# Range: 0 ~ 1000000
WASWMissileCooldown = 40
#WASW's Wither Missile's Damage
# Default: 16.0
# Range: 0.0 ~ 1000000.0
"WASW's WitherMissiledamage" = 16.0
#Wither Assault Shoulder Weapon's Howitzer Cooldown
# Default: 100
# Range: 0 ~ 1000000
WASWHowitzerCooldown = 100
#Void Assault Shoulder Weapon's Cooldown
# Default: 120
# Range: 0 ~ 1000000
VASWCooldown = 120
#Void Core's Cooldown
# Default: 160
# Range: 0 ~ 1000000
VoidCoreCooldown = 160
#Sandstorm's cooldown
# Default: 300
# Range: 0 ~ 1000000
Sandstormcooldown = 300
#Soul Render's Timer
# Default: 100
# Range: 0 ~ 1000000
SoulRenderCooldown = 100
#Gauntlet of Maelstrom's Timer
# Default: 180
# Range: 0 ~ 1000000
gauntletofMaelstromCooldown = 180
#The Immolator's Timer
# Default: 300
# Range: 0 ~ 1000000
immolatorCooldown = 300
#Storm Bringer's LightningStorm Damage
# Default: 6.0
# Range: 0.0 ~ 1000000.0
"Ceraunus 's Lightning Storm Damage" = 6.0
#Ceraunus's Wave Damage
# Default: 6.0
# Range: 0.0 ~ 1000000.0
"Ceraunus's Wave Damage'" = 6.0
#Ceraunus's Cooldown
# Default: 150
# Range: 0 ~ 1000000
"Ceraunus Cooldown" = 150
#Astrape's LightningStorm Damage
# Default: 11.0
# Range: 0.0 ~ 1000000.0
"Astrape's Lightning Spear Damage" = 11.0
#Astrape's Wave Damage
# Default: 2.0
# Range: 0.0 ~ 1000000.0
"Astrape's Area Damage'" = 2.0
#Astrape's Cooldown
# Default: 80
# Range: 0 ~ 1000000
"Astrape Cooldown" = 80
[Block]
#Cursed Tombstone Summon cooldown Minute
# Default: 1
# Range: 1 ~ 300
"Cursed Tombstone Summon cooldown Minute" = 1
["Entity damage"]
#Void Rune's Damage
# Default: 7.0
# Range: 0.0 ~ 1000000.0
Voidrunedamage = 7.0
#Ashen Breath's Damage
# Default: 4.0
# Range: 0.0 ~ 1000000.0
Ashenbreathdamage = 4.0
#Death Laser's Damage
# Default: 5.0
# Range: 0.0 ~ 1000000.0
DeathLaserdamage = 5.0
#Death Laser's Hp Damage
# Default: 5.0
# Range: 0.0 ~ 100.0
DeathLaserHpdamage = 5.0
#Player's Laser's Damage
# Default: 7.0
# Range: 0.0 ~ 1000000.0
Laserdamage = 7.0
#Blazing Bone's Damage
# Default: 5.0
# Range: 0.0 ~ 1000000.0
BlazingBonedamage = 5.0
#Lionfish Spike's Damage
# Default: 4.0
# Range: 0.0 ~ 1000000.0
LionfishSpikedamage = 4.0
#Wither Howizter's Damage
# Default: 8.0
# Range: 0.0 ~ 1000000.0
WitherHowizterdamage = 8.0
#Dimensional Rift's Damage
# Default: 10.0
# Range: 0.0 ~ 1000000.0
DimensionalRiftdamage = 10.0
#Wither Homing Missile's Damage
# Default: 3.0
# Range: 0.0 ~ 1000000.0
WitherHomingMissiledamage = 3.0
#Abyss Blast's Damage
# Default: 10.0
# Range: 0.0 ~ 1000000.0
AbyssBlastdamage = 10.0
#Abyss Blast's Hp Damage
# Default: 0.1
# Range: 0.0 ~ 1.0
AbyssBlastHpdamage = 0.1
#Abyss Orb's Damage
# Default: 4.0
# Range: 0.0 ~ 1000000.0
AbyssOrbdamage = 4.0
#Lava bomb's Radius
# Default: 2
# Range: 1 ~ 7
Lavabombradius = 2
#Amethyst Cluster's Damage
# Default: 12.0
# Range: 0.0 ~ 1000000.0
"Amethyst Cluster Damage" = 12.0
#Sandstorm's Damage
# Default: 5.0
# Range: 0.0 ~ 1000000.0
"Sandstorm Damage" = 5.0
#Ancient Desert Stele's Damage
# Default: 18.0
# Range: 0.0 ~ 1000000.0
"Ancient Desert Stele Damage" = 18.0
#Player's Phantom Arrow's Damage
# Default: 8.0
# Range: 0.0 ~ 1000000.0
"Phantom Arrow Damage" = 8.0
#Phantom Halberd's Damage
# Default: 12.0
# Range: 0.0 ~ 1000000.0
"Phantom Halberd Damage" = 12.0
#Cursed Sandstorm's Damage
# Default: 7.5
# Range: 0.0 ~ 1000000.0
"Cursed Sandstorm Damage" = 7.5
#Flame jet's Damage
# Default: 7.0
# Range: 0.0 ~ 1000000.0
"Flame Jet Damage" = 7.0
#Flare Bomb's Damage
# Default: 7.0
# Range: 0.0 ~ 1000000.0
"Flare Bomb Damage" = 7.0
["Ender Guardian"]
#EnderGuardian's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
EnderGuardianHealthMultiplier = 1.0
#EnderGuardian's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
EnderGuardianDamageMultiplier = 1.0
#EnderGuardian's DamageCap
# Default: 22.0
# Range: 0.0 ~ 1000000.0
EnderGuardianDamageCap = 22.0
#EnderGuardian's DamageTime
# Default: 30
# Range: 0 ~ 100
EnderGuardianDamageTime = 30
#EnderGuardian's Healing with out target
# Default: 25.0
# Range: 0.0 ~ 1000000.0
EnderGuardianNatureHealing = 25.0
#Ender guardian's block breaking ignore the MobGriefing
EnderguardianBlockBreaking = true
#Guardian's Immune to Long distance attack range.
# Default: 12.0
# Range: 1.0 ~ 1000000.0
"Guardian's prevent attacks from far away Range" = 12.0
#Guardian's gravity Punch Hp Damage
# Default: 0.05
# Range: 0.0 ~ 1.0
"Guardian's gravity Punch Hp Damage" = 0.05
#Guardian's Teleport attack Hp Damage
# Default: 0.05
# Range: 0.0 ~ 1.0
"Guardian's Teleport attack Hp Damage" = 0.05
#Guardian's Punch Hp Damage
# Default: 0.06
# Range: 0.0 ~ 1.0
"Guardian's knockback Hp Damage" = 0.06
#Guardian's Uppercut Hp Damage
# Default: 0.1
# Range: 0.0 ~ 1.0
"Guardian's Uppercut Hp Damage" = 0.1
#Guardian's RocketPunch Hp Damage
# Default: 0.1
# Range: 0.0 ~ 1.0
"Guardian's RocketPunch Hp Damage" = 0.1
#Guardian's etc area attack Hp Damage
# Default: 0.08
# Range: 0.0 ~ 1.0
"Guardian's area attack Hp Damage" = 0.08
#EnderGuardianBlockBreaking radius
# Default: 15
# Range: 0 ~ 20
"EnderGuardianBlockBreaking X" = 15
#EnderGuardianBlockBreaking radius
# Default: 2
# Range: 0 ~ 10
"EnderGuardianBlockBreaking Y" = 2
#EnderGuardianBlockBreaking radius
# Default: 15
# Range: 0 ~ 20
"EnderGuardianBlockBreaking Z" = 15
["Netherite Monstrosity"]
#Monstrosity's Lavabomb magazine.
# Default: 3
# Range: 1 ~ 1000000
LavabombMagazine = 3
#Monstrosity's Lavabomb amount
# Default: 3
# Range: 1 ~ 1000000
Lavabombamount = 3
#Lava Bomb of Monstrosity's Duration
# Default: 350
# Range: 1 ~ 10000
LavaBombDuration = 350
#Lava Bomb of Monstrosity's additional random duration size
# Default: 150
# Range: 1 ~ 10000
LavaBombRandomDuration = 150
#Monstrosity's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
MonstrosityHealthMultiplier = 1.0
#Monstrosity's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
MonstrosityDamageMultiplier = 1.0
#Monstrosity's Healing Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
MonstrosityHealingMultiplier = 1.0
# Monstrosity's Healing with out target
# Default: 25.0
# Range: 0.0 ~ 1000000.0
MonstrosityNatureHealing = 25.0
#Monstrosity's DamageCap
# Default: 22.0
# Range: 0.0 ~ 1000000.0
MonstrosityDamageCap = 22.0
#Monstrosity's DamageTime
# Default: 10
# Range: 0 ~ 100
MonstrosityDamageTime = 10
#Monstrosity's bodyBlocking verdict
NetheritemonstrosityBodyBloking = true
#Monstrosity's attack Hp Damage
# Default: 0.08
# Range: 0.0 ~ 1.0
"Monstrosity's attack Hp Damage" = 0.08
#Monstrosity's Immune to Long distance attack range.
# Default: 18.0
# Range: 1.0 ~ 1000000.0
"Monstrosity's prevent attacks from far away Range" = 18.0
#Monstrosity's block breaking ignore the MobGriefing
monstrosityBlockBreaking = true
["Ender Golem"]
#Ender Golem's block breaking ignore the MobGriefing
EndergolemBlockBreaking = false
#Endergolem's Immune to Long distance attack range.
# Default: 6.0
# Range: 1.0 ~ 1000000.0
"Endergolem's prevent attacks from far away Range" = 6.0
#Golem's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
GolemHealthMultiplier = 1.0
#Golem's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
GolemDamageMultiplier = 1.0
[Ignis]
#Ignis's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
IgnisHealthMultiplier = 1.0
#Ignis's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
IgnisDamageMultiplier = 1.0
#Ignis's Healing with out target
# Default: 25.0
# Range: 0.0 ~ 1000000.0
IgnisNatureHealing = 25.0
#Ignis's Healing Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
IgnisHealingMultiplier = 1.0
#Ignis's Immune to Long distance attack range.
# Default: 15.0
# Range: 1.0 ~ 1000000.0
"Ignis's prevent attacks from far away Range" = 15.0
#Ignis's DamageCap
# Default: 20.0
# Range: 0.0 ~ 1000000.0
IgnisDamageCap = 20.0
#Ignis's DamageTime
# Default: 15
# Range: 0 ~ 100
IgnisDamageTime = 15
#Ignis's cracked block breaking ignore the MobGriefing
IgnisBlockBreaking = true
[revenant]
#Revenant's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
RevenantHealthMultiplier = 1.0
#Revenant's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
RevenantDamageMultiplier = 1.0
["The Prowler"]
#The Prowler's Immune to Long distance attack range.
# Default: 16.0
# Range: 1.0 ~ 1000000.0
"The Prowler's prevent attacks from far away Range" = 16.0
#Prowler's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
ProwlerHealthMultiplier = 1.0
#Prowler's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
ProwlerDamageMultiplier = 1.0
["The Harbinger"]
#Harbinger's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
HarbingerHealthMultiplier = 1.0
#Harbinger's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
HarbingerDamageMultiplier = 1.0
#Harbinger's Healing Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
HarbingerHealingMultiplier = 1.0
#Harbinger's Wither Missile's Damage
# Default: 8.0
# Range: 0.0 ~ 1000000.0
"Harbinger's WitherMissiledamage" = 8.0
#Harbinger's laser's Damage
# Default: 5.0
# Range: 0.0 ~ 1000000.0
"Harbinger's laser damage" = 5.0
#Harbinger's Immune to Long distance attack range.
# Default: 35.0
# Range: 1.0 ~ 1000000.0
"The Harbinger's prevent attacks from far away Range" = 35.0
#Harbinger's DamageCap
# Default: 22.0
# Range: 0.0 ~ 1000000.0
"The Harbinger DamageCap" = 22.0
#Harbinger's DamageTime
# Default: 12
# Range: 0 ~ 100
"The Harbinger DamageTime" = 12
#Harbinger's lasers can light a fire in MobGriefing
"The Harbinger Light A Fire" = true
#The Harbinger's charge attack Hp Damage
# Default: 0.06
# Range: 0.0 ~ 1.0
"The Harbinger's charge attack Hp Damage" = 0.06
["The Leviathan"]
#Leviathan's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
LeviathanHealthMultiplier = 1.0
#Leviathan's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
LeviathanDamageMultiplier = 1.0
#Leviathan's Healing with out target
# Default: 25.0
# Range: 0.0 ~ 1000000.0
LeviathanNatureHealing = 25.0
#Leviathan's Immune to Long distance attack range.
# Default: 38.0
# Range: 1.0 ~ 1000000.0
"Leviathan's prevent attacks from far away Range" = 38.0
#Leviathan's Bite Hp Damage
# Default: 0.1
# Range: 0.0 ~ 1.0
"Leviathan's Bite Hp Damage" = 0.1
#Leviathan's Rush Hp Damage
# Default: 0.05
# Range: 0.0 ~ 1.0
"Leviathan's Rush Hp Damage" = 0.05
#Leviathan's TailSwing Hp Damage
# Default: 0.08
# Range: 0.0 ~ 1.0
"Leviathan's TailSwing Hp Damage" = 0.08
#Leviathan's Tentacle Hp Damage
# Default: 0.03
# Range: 0.0 ~ 1.0
"Leviathan's Tentacle Hp Damage" = 0.03
#Leviathan's DamageCap
# Default: 20.0
# Range: 0.0 ~ 1000000.0
LeviathanDamageCap = 20.0
#Leviathan's DamageTime
# Default: 15
# Range: 0 ~ 100
"Leviathan DamageTime" = 15
#Leviathan's block breaking ignore the MobGriefing
LeviathanBlockBreaking = true
#Leviathan Immune Out of Water
LeviathanImmuneOutofWater = true
["The Baby Leviathan"]
#BabyLeviathan's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
BabyLeviathanHealthMultiplier = 1.0
#BabyLeviathan's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
BabyLeviathanDamageMultiplier = 1.0
["Modern Remnant"]
#Modern Remnant's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
ModernRemnantHealthMultiplier = 1.0
#Modern Remnant's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
ModernRemnantDamageMultiplier = 1.0
["Netherite Ministrosity"]
#Ministrosity's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
MinistrosityHealthMultiplier = 1.0
["Amethyst Crab"]
#Amethyst Crab's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
AmethystCrabHealthMultiplier = 1.0
#Amethyst Crab's EarthQuake Damage
# Default: 5.0
# Range: 0.0 ~ 1000000.0
AmethystCrabEarthQuakeDamage = 5.0
#Amethyst Crab's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
AmethystCrabDamageMultiplier = 1.0
["Ancient Remnant"]
#Ancient Remnant's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
AncientRemnantHealthMultiplier = 1.0
#Ancient Remnant's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
AncientRemnantDamageMultiplier = 1.0
#AncientRemnant's Healing with out target
# Default: 25.0
# Range: 0.0 ~ 1000000.0
AncientRemnantNatureHealing = 25.0
#Ancient Remnant's Immune to Long distance attack range.
# Default: 14.0
# Range: 1.0 ~ 1000000.0
"Ancient Remnant's prevent attacks from far away Range" = 14.0
#Ancient Remnant's DamageCap
# Default: 21.0
# Range: 0.0 ~ 1000000.0
AncientRemnantCap = 21.0
#Ancient Remnant's DamageTime
# Default: 12
# Range: 0 ~ 100
"Ancient Remnant DamageTime" = 12
#Ancient Remnant's block breaking ignore the MobGriefing
AncientRemnantBlockBreaking = true
#Remnant's Charge Hp Damage
# Default: 0.1
# Range: 0.0 ~ 1.0
"Remnant's Charge Hp Damage" = 0.1
#Remnant's Hp Damage
# Default: 0.05
# Range: 0.0 ~ 1.0
"Remnant's Normal attack Hp Damage" = 0.05
#Remnant's Stomp Hp Damage
# Default: 0.03
# Range: 0.0 ~ 1.0
"Remnant's Stomp Hp Damage" = 0.03
#Remnant's EarthQuake Damage
# Default: 11.0
# Range: 0.0 ~ 1000000.0
"Remnant's EarthQuakeDamage" = 11.0
[Koboleton]
#Cause Koboleton to Drop Item In Hand Percent
# Default: 5.0
# Range: 0.0 ~ 100.0
CauseKoboletontoDropItemInHandPercent = 5.0
[Kobolediator]
#Kobolediator's block breaking ignore the MobGriefing
KobolediatorBlockBreaking = false
#Kobolediator's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
KobolediatorHealthMultiplier = 1.0
#Kobolediator's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
KobolediatorDamageMultiplier = 1.0
[Wadjet]
#Wadjet's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
WadjetHealthMultiplier = 1.0
#Wadjet's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
WadjetDamageMultiplier = 1.0
[Aptrgangr]
#Aptrgangr's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
AptrgangrHealthMultiplier = 1.0
#Aptrgangr's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
AptrgangrDamageMultiplier = 1.0
#Axe Blade's Damage
# Default: 8.0
# Range: 0.0 ~ 1000000.0
AptrgangrAxeBladeDamage = 8.0
[Clawdian]
#Clawdian's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
ClawdianHealthMultiplier = 1.0
#Clawdian's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
ClawdianDamageMultiplier = 1.0
[Scylla]
#Scylla's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
ScyllaHealthMultiplier = 1.0
#Scylla's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
ScyllaDamageMultiplier = 1.0
#Scylla's Immune to Long distance attack range.
# Default: 10.0
# Range: 1.0 ~ 1000000.0
"Scylla's prevent attacks from far away Range" = 10.0
#Scylla's Spear Damage
# Default: 14.0
# Range: 0.0 ~ 1000000.0
"Scylla's Spear Damage" = 14.0
#Scylla's Lightning Storm
# Default: 10.0
# Range: 0.0 ~ 1000000.0
"Scylla's Lightning Storm" = 10.0
#Scylla's Lightning Area
# Default: 4.0
# Range: 0.0 ~ 1000000.0
"Scylla's Lightning Area" = 4.0
#Scylla's Storm Serpent
# Default: 16.0
# Range: 0.0 ~ 1000000.0
"Scylla's Snake Damage" = 16.0
#Scylla's Anchor Damage
# Default: 16.0
# Range: 0.0 ~ 1000000.0
"Scylla's Anchor Damage" = 16.0
#Scylla's Healing with out target
# Default: 25.0
# Range: 0.0 ~ 1000000.0
"Scylla NatureHealing" = 25.0
#Scylla's DamageCap
# Default: 22.0
# Range: 0.0 ~ 1000000.0
"Scylla DamageCap" = 22.0
#Scylla's DamageTime
# Default: 25
# Range: 0 ~ 100
"Scylla DamageTime" = 25
#Scylla's HP Damage
# Default: 0.05
# Range: 0.0 ~ 1000000.0
"Scylla's HP Damage" = 0.05
#Scylla's Spin HP Damage
# Default: 0.07
# Range: 0.0 ~ 1000000.0
"Scylla's Spin Hp Damage" = 0.07
#Scylla's Lightning Storm HP Damage
# Default: 0.04
# Range: 0.0 ~ 1000000.0
"Scylla's Lightning Storm HP Damage" = 0.04
[Maledictus]
#Maledictus's Health Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
MaledictusHealthMultiplier = 1.0
#Maledictus's Damage Multiplier
# Default: 1.0
# Range: 0.0 ~ 1000000.0
MaledictusDamageMultiplier = 1.0
#Maledictus's Immune to Long distance attack range.
# Default: 12.0
# Range: 1.0 ~ 1000000.0
"Maledictus's prevent attacks from far away Range" = 12.0
#Maledictus's Healing with out target
# Default: 25.0
# Range: 0.0 ~ 1000000.0
MaledictusNatureHealing = 25.0
#Maledictus's Phantom Halberd Damage
# Default: 10.0
# Range: 0.0 ~ 1000000.0
"Maledictus' Phantom Halberd Damage'" = 10.0
#Maledictus's DamageCap
# Default: 20.0
# Range: 0.0 ~ 1000000.0
MaledictusDamageCap = 20.0
#Maledictus's DamageTime
# Default: 30
# Range: 0 ~ 100
"Maledictus DamageTime" = 30
#Maledictus's melee Hp Damage
# Default: 0.05
# Range: 0.0 ~ 1.0
"Maledictus's melee Hp Damage" = 0.05
#Maledictus's Shock wave Hp Damage
# Default: 0.03
# Range: 0.0 ~ 1.0
"Maledictus's Shock Wave Hp Damage" = 0.03
#Maledictus's AOE Hp Damage
# Default: 0.15
# Range: 0.0 ~ 1.0
"Maledictus's AOE Hp Damage" = 0.15
#Maledictus's flying Smash Hp Damage
# Default: 0.1
# Range: 0.0 ~ 1.0
"Maledictus's Flying Smash Hp Damage" = 0.1
#Maledictus's Jump Smash Hp Damage
# Default: 0.08
# Range: 0.0 ~ 1.0
"Maledictus's Jump Smash Hp Damage" = 0.08
#Maledictus's Phantom Arrow's Damage
# Default: 5.0
# Range: 0.0 ~ 1000000.0
"Maledictus's Phantom Arrow Damage" = 5.0
#Maledictus's cracked block breaking ignore the MobGriefing
MaledictusBlockBreaking = true
[spawning]
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 2
# Range: 0 ~ 1000
DeeplingSpawnWeight = 0
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 30
# Range: > 0
DeeplingSpawnRolls = 30
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 1
# Range: 0 ~ 1000
DeeplingBruteSpawnWeight = 0
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 50
# Range: > 0
DeeplingBruteSpawnRolls = 50
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 2
# Range: 0 ~ 1000
DeeplingAnglerSpawnWeight = 0
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 30
# Range: > 0
DeeplingAnglerSpawnRolls = 30
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 1
# Range: 0 ~ 1000
DeeplingPriestSpawnWeight = 0
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 70
# Range: > 0
DeeplingPriestSpawnRolls = 70
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 1
# Range: 0 ~ 1000
DeeplingWarlockSpawnWeight = 0
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 70
# Range: > 0
DeeplingWarlockSpawnRolls = 70
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 1
# Range: 0 ~ 1000
CoralgolemSpawnWeight = 1
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 70
# Range: > 0
CoralgolemSpawnRolls = 70
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 1
# Range: 0 ~ 1000
AmethystCrabSpawnWeight = 1
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 20
# Range: > 0
AmethystCrabSpawnRolls = 20
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 15
# Range: 0 ~ 1000
KoboletonSpawnWeight = 15
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 1
# Range: > 0
KoboletonSpawnRolls = 1
#Spawn Weight, added to a pool of other mobs for each biome. Higher number = higher chance of spawning. 0 = disable spawn
# Default: 5
# Range: 0 ~ 1000
IgnitedBerserkerSpawnWeight = 5
#Random roll chance to enable mob spawning. Higher number = lower chance of spawning
# Default: 2
# Range: > 0
IgnitedBerserkerSpawnRolls = 2
["World Generation"]
#Defines the area in which the structure check for height variances (1 means 9 chunks will be checked (center + area around it)) - 0 disables this check
# Default: 2
# Range: 0 ~ 5
cursedPyramidCheckRange = 2
#Allowed height variance for the check - if the variance is lower than this value the structure will not spawn (has no effect if the are check is disabled)
# Default: 2
# Range: 0 ~ 32
cursedPyramidHeightVariance = 2

View file

@ -1,249 +0,0 @@
#Settings for Inventory HUD
[inventoryhud]
#Inventory HUD mini mode
invMini = true
#Inventory HUD vertical mode
invVert = false
#Inventory HUD alpha
# Default: 0
# Range: 0 ~ 100
invAlpha = 0
#Toggle on by default
byDefault = false
#Animate recently picked up items
animatedInv = false
#Hide background if inventory is empty
hideBackground = false
#Show Inventory HUD when debug menu is open
invWithDebug = true
#Settings for ArmorStatus HUD
[armorhud]
#Is Armor Damage HUD enabled
ArmorDamage = true
#Hide if durability is above this (in percentage):
# Default: 100
# Range: 0 ~ 100
armAbove = 100
#Show/Hide armor
showArmor = true
#Show/Hide armor
showMain = true
#Show/Hide armor
showOff = true
#Show/Hide armor
showArrows = true
#Show/Hide armor
showInv = true
#Armor HUD durability view (PERCENTAGE, DAMAGE, DAMAGE LEFT)
#Allowed Values: PERCENTAGE, DAMAGE, DAMAGE_LEFT, OFF
armView = "PERCENTAGE"
#Show item durability bar
armBars = false
#Move all items at once or each one
moveAll = true
#Show/Hide empty slot icon
showEmpty = true
#Show overall count of items in main/off hand
showCount = false
#ArmorHUD scale in persentage from 50 to 150
# Default: 100
# Range: 50 ~ 150
armScale = 100
#Show Armor HUD when debug menu is open
armWithDebug = true
#Show arrows count when no weapon in your hands
showArrowsWithoutWeapon = true
#Settings for Potions HUD
[potionshud]
#Is Potions HUD enabled
Potions = true
#Potion HUD alpha
# Default: 100
# Range: 0 ~ 100
potAlpha = 100
#Potion HUD gap
# Default: 0
# Range: -5 ~ 5
potGap = 0
#Potion HUD mini mode
potMini = false
#Potion HUD horizontal mode
potHor = false
#Full bar duration
# Default: 300
# Range: > 1
barDuration = 300
#Show hidden effects
showHiddenEffects = true
#Disable icons for effects in this list
effectsBlackList = []
#Show Potion HUD when debug menu is open
potWithDebug = true
#Show all levels of effects
potionLevels = false
#DONT TOUCH THESE FIELDS!
[positions]
#Inventory HUD vertical align
#Allowed Values: TOP, CENTER, BOTTOM
invValign = "BOTTOM"
#Inventory HUD horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
invHalign = "MIDDLE"
#Inventory HUD position (X)
# Default: 0
# Range: -9999 ~ 9999
xPos = 0
#Inventory HUD position (Y)
# Default: 150
# Range: -9999 ~ 9999
yPos = 150
#Armor HUD vertical align
#Allowed Values: TOP, CENTER, BOTTOM
armValign = "BOTTOM"
#Armor HUD horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
armHalign = "MIDDLE"
#Armor HUD position (X)
# Default: 0
# Range: -9999 ~ 9999
xArmPos = 0
#Armor HUD position (Y)
# Default: 70
# Range: -9999 ~ 9999
yArmPos = 70
#Potion HUD vertical align
#Allowed Values: TOP, CENTER, BOTTOM
potValign = "TOP"
#Potion HUD horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
potHalign = "LEFT"
#Potion HUD position (X)
# Default: 20
# Range: -9999 ~ 9999
xPotionPos = 20
#Potion HUD position (Y)
# Default: 20
# Range: -9999 ~ 9999
yPotionPos = 20
#Helmet position (X)
# Default: 103
# Range: -9999 ~ 9999
helmPosX = 103
#Helmet position (Y)
# Default: 54
# Range: -9999 ~ 9999
helmPosY = 54
#Chestplate position (X)
# Default: 103
# Range: -9999 ~ 9999
chestPosX = 103
#Chestplate position (Y)
# Default: 37
# Range: -9999 ~ 9999
chestPosY = 37
#Leggings position (X)
# Default: -103
# Range: -9999 ~ 9999
legPosX = -103
#Leggings position (Y)
# Default: 54
# Range: -9999 ~ 9999
legPosY = 54
#Boots position (X)
# Default: -103
# Range: -9999 ~ 9999
bootPosX = -103
#Boots position (Y)
# Default: 37
# Range: -9999 ~ 9999
bootPosY = 37
#MainHand position (X)
# Default: 103
# Range: -9999 ~ 9999
mainPosX = 103
#MainHand position (Y)
# Default: 71
# Range: -9999 ~ 9999
mainPosY = 71
#OffHand position (X)
# Default: -103
# Range: -9999 ~ 9999
offPosX = -103
#OffHand position (Y)
# Default: 71
# Range: -9999 ~ 9999
offPosY = 71
#Arrows position (X)
# Default: 103
# Range: -9999 ~ 9999
arrPosX = 103
#Arrows position (Y)
# Default: 20
# Range: -9999 ~ 9999
arrPosY = 20
#InvIcon position (X)
# Default: -103
# Range: -9999 ~ 9999
invPosX = -103
#InvIcon position (Y)
# Default: 20
# Range: -9999 ~ 9999
invPosY = 20
#Helmet horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
helmHal = "MIDDLE"
#Helmet vertical align
#Allowed Values: TOP, CENTER, BOTTOM
helmVal = "BOTTOM"
#Chestplate horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
chestHal = "MIDDLE"
#Chestplate vertical align
#Allowed Values: TOP, CENTER, BOTTOM
chestVal = "BOTTOM"
#Leggings horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
legHal = "MIDDLE"
#Leggings vertical align
#Allowed Values: TOP, CENTER, BOTTOM
legVal = "BOTTOM"
#Boots horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
bootHal = "MIDDLE"
#Boots vertical align
#Allowed Values: TOP, CENTER, BOTTOM
bootVal = "BOTTOM"
#MainHand horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
mainHal = "MIDDLE"
#MainHand vertical align
#Allowed Values: TOP, CENTER, BOTTOM
mainVal = "BOTTOM"
#OffHand horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
offHal = "MIDDLE"
#OffHand vertical align
#Allowed Values: TOP, CENTER, BOTTOM
offVal = "BOTTOM"
#Arrows horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
arrHal = "MIDDLE"
#Arrows vertical align
#Allowed Values: TOP, CENTER, BOTTOM
arrVal = "BOTTOM"
#InvIcon horizontal align
#Allowed Values: LEFT, MIDDLE, RIGHT
invHal = "MIDDLE"
#InvIcon vertical align
#Allowed Values: TOP, CENTER, BOTTOM
invVal = "BOTTOM"
#Notification settings
[notification]
#Last notified mod version
lastNotifiedVersion = "3.4.28"
#Notify more than once
keepNotifying = true

View file

@ -1,9 +0,0 @@
#This file stores configuration options for Iris, such as the currently active shaderpack
#Thu Jul 31 02:25:56 HKT 2025
allowUnknownShaders=false
colorSpace=SRGB
disableUpdateMessage=false
enableDebugOptions=false
enableShaders=true
maxShadowRenderDistance=32
shaderPack=ComplementaryReimagined_r5.5.1 + Colorwheel_0.2.4.zip

View file

@ -1,66 +0,0 @@
{
"animation_settings": {
"animation": true,
"water": true,
"lava": true,
"fire": true,
"portal": true,
"block_animations": true,
"sculk_sensor": true
},
"particle_settings": {
"particles": true,
"rain_splash": true,
"block_break": true,
"block_breaking": true,
"other": {
"subtle_effects:item_rarity": false,
"subtle_effects:frosty_breath": false
}
},
"detail_settings": {
"sky": true,
"sun": true,
"moon": true,
"stars": true,
"rain_snow": true,
"biome_colors": true,
"sky_colors": true
},
"render_settings": {
"fog_distance": 0,
"fog_start": 100,
"multi_dimension_fog_control": false,
"dimensionFogDistance": {},
"light_updates": true,
"item_frame": true,
"armor_stand": true,
"painting": true,
"piston": true,
"beacon_beam": true,
"limit_beacon_beam_height": false,
"enchanting_table_book": true,
"item_frame_name_tag": true,
"player_name_tag": true
},
"extra_settings": {
"overlay_corner": "TOP_LEFT",
"text_contrast": "NONE",
"show_fps": false,
"show_f_p_s_extended": true,
"show_coords": false,
"reduce_resolution_on_mac": false,
"use_adaptive_sync": false,
"cloud_height": 192,
"cloud_distance": 100,
"toasts": true,
"advancement_toast": true,
"recipe_toast": true,
"system_toast": true,
"tutorial_toast": true,
"instant_sneak": false,
"prevent_shaders": false,
"steady_debug_hud": true,
"steady_debug_hud_refresh_interval": 1
}
}

View file

@ -1,18 +0,0 @@
[sodiumdynamiclights]
#Lighting mode
#Allowed Values: OFF, SLOW, FAST, REALTIME
mode = "OFF"
#Enable entities light source.
entities = false
#Enable first-person player light source.
self = true
#Enable block entities light source.
block_entities = true
#Enables water-sensitive light sources check. This means that water-sensitive items will not light up when submerged in water.
water_sensitive_check = true
#TNT lighting mode. May be off, simple or fancy.
#Allowed Values: OFF, SIMPLE, FANCY
tnt = "SIMPLE"
#Creeper lighting mode. May be off, simple or fancy.
#Allowed Values: OFF, SIMPLE, FANCY
creeper = "OFF"

View file

@ -1,135 +0,0 @@
[embeddiumextras.general]
#Set Fullscreen mode
#Borderless let you change between screens more faster and move your mouse across monitors
#Allowed Values: WINDOWED, BORDERLESS, FULLSCREEN
fullscreen = "WINDOWED"
#Configure FPS Display mode
#Complete mode gives you min FPS count and average count
#Allowed Values: OFF, SIMPLE, ADVANCED
fpsDisplay = "ADVANCED"
#Configure FPS Display gravity
#Places counter on specified corner of your screen
#Allowed Values: LEFT, CENTER, RIGHT
fpsDisplayGravity = "LEFT"
#Shows GPU and memory usage onto FPS display
#Allowed Values: OFF, ON, GPU, RAM
fpsDisplaySystem = "ON"
#Configure FPS Display margin
#Give some space between corner and text
# Default: 12
# Range: 0 ~ 48
fpsDisplayMargin = 12
#Toggle FPS Display shadow
#In case sometimes you can't see the text
fpsDisplayShadow = false
[embeddiumextras.quality]
#Toggle fog feature
#Fog was a vanilla feature, toggling off may increases performance
fog = true
#Raise clouds
#Modify clouds height perfect for a adaptative world experience
# Default: 192
# Range: 0 ~ 512
cloudsHeight = 192
#Chunks fade in speed
#This option doesn't affect performance, just changes speed
#Allowed Values: OFF, FAST, SLOW
chunkFadeSpeed = "SLOW"
[embeddiumextras.quality.darkness]
#Configure Darkness Mode
#Each config changes what is considered 'true darkness'
#Allowed Values: PITCH_BLACK, TOTAL_DARKNESS, DARK, DIM, OFF
mode = "OFF"
#Toggle Darkness on Overworld dimension
enableOnOverworld = true
#Toggle Darkness on Nether dimension
enableOnNether = false
#Configure fog brightness on nether when darkness is enabled
# Default: 0.5
# Range: 0.0 ~ 1.0
netherFogBright = 0.5
#Toggle Darkness on End dimension
enableOnEnd = false
#Configure fog brightness on nether when darkness is enabled
# Default: 0.5
# Range: 0.0 ~ 1.0
endFogBright = 0.5
#Toggle Darkness default mode for modded dimensions
valueByDefault = false
#List of all dimensions to use True Darkness
#This option overrides 'valueByDefault' state
dimensionWhitelist = []
#Toggle darkness when dimension has no SkyLight
enableOnNoSkyLight = false
#Disables all bright sources of darkness like moon or fog
#Only affects darkness effect
enableBlockLightOnly = false
#Toggles if moon phases affects darkness in the overworld
affectedByMoonPhase = true
#Configure max moon brightness level with darkness
# Default: 0.25
# Range: 0.0 ~ 1.0
fullMoonBright = 0.25
#Configure min moon brightness level with darkness
# Default: 0.0
# Range: 0.0 ~ 1.0
newMoonBright = 0.0
[embeddiumextras.performance]
#Toggles JREI item rendering until searching
#Increases performance a little bit and cleans your screen when you don't want to use it
hideJREI = false
#Toggles Minecraft Fonts shadows
#Depending of the case may increase performance
#Gives a flat style text
fontShadows = true
[embeddiumextras.performance.distanceCulling.tileEntities]
#Toggles distance culling for Block Entities
#Maybe you use another mod for that :(
enable = true
#Configure horizontal max distance before cull Block entities
#Value is squared, default was 64^2 (or 64x64)
# Default: 4096
# Range: > 0
cullingMaxDistanceX = 4096
#Configure vertical max distance before cull Block entities
#Value is raw
# Default: 32
# Range: 0 ~ 512
cullingMaxDistanceY = 32
#List of all Block Entities to be ignored by distance culling
#Uses ResourceLocation to identify it
#Example 1: "minecraft:chest" - Ignores chests only
#Example 2: "ae2:*" - ignores all Block entities from Applied Energetics 2
whitelist = ["waterframes:*"]
[embeddiumextras.performance.distanceCulling.entities]
#Toggles distance culling for entities
#Maybe you use another mod for that :(
enable = true
#Configure horizontal max distance before cull entities
#Value is squared, default was 64^2 (or 64x64)
# Default: 4096
# Range: > 0
cullingMaxDistanceX = 4096
#Configure vertical max distance before cull entities
#Value is raw
# Default: 32
# Range: 0 ~ 512
cullingMaxDistanceY = 32
#List of all Entities to be ignored by distance culling
#Uses ResourceLocation to identify it
#Example 1: "minecraft:bat" - Ignores bats only
#Example 2: "alexsmobs:*" - ignores all entities for alexmobs mod
whitelist = ["minecraft:ghast", "minecraft:ender_dragon", "iceandfire:*", "create:*"]
[embeddiumextras.others]
#Configure if borderless fullscreen option should be attached to F11 or replace vanilla fullscreen
#Allowed Values: ATTACH, REPLACE, OFF
borderlessAttachModeOnF11 = "OFF"
#Toggles fast language reload
#Embeddedt points it maybe cause troubles to JEI, so ¿why not add it as a toggleable option?
fastLanguageReload = true

View file

@ -1,50 +0,0 @@
key_iris.keybind.reload:key.keyboard.unknown:
key_iris.keybind.toggleShaders:key.keyboard.unknown:
key_iris.keybind.shaderPackSelection:key.keyboard.unknown:
key_key.corpse.death_history:key.keyboard.unknown:
key_key.curios.open.desc:key.keyboard.unknown:
key_key.cobblemon.hideparty:key.keyboard.unknown:
key_key.cobblemon.throwpartypokemon:key.keyboard.z
key_accessories.key.open_accessories_screen:key.keyboard.unknown:
key_key.jei.toggleOverlay:key.keyboard.unknown:
key_key.jei.focusSearch:key.keyboard.unknown:
key_key.jei.bookmark:key.keyboard.unknown:
key_key.jei.showRecipe:key.keyboard.unknown:
key_key.jei.showRecipe2:key.keyboard.unknown:
key_key.jei.showUses:key.keyboard.unknown:
key_key.jei.showUses2:key.keyboard.unknown:
key_key.jei.transferRecipeBookmark:key.keyboard.unknown:
key_key.jei.maxTransferRecipeBookmark:key.keyboard.unknown:
key_key.jei.clearSearchBar:key.keyboard.unknown:
key_key.jei.previousSearch:key.keyboard.unknown:
key_key.jei.nextSearch:key.keyboard.unknown:
key_key.jei.cheatOneItem:key.keyboard.unknown:
key_key.jei.cheatOneItem2:key.keyboard.unknown:
key_key.jei.cheatItemStack:key.keyboard.unknown:
key_key.jei.cheatItemStack2:key.keyboard.unknown:
key_key.jei.toggleCheatModeConfigButton:key.keyboard.unknown:
key_key.jei.toggleHideIngredient:key.keyboard.unknown:
key_key.jei.toggleWildcardHideIngredient:key.keyboard.unknown:
key_key.jei.recipeBack:key.keyboard.unknown:
key_key.jei.previousRecipePage:key.keyboard.unknown:
key_key.jei.nextRecipePage:key.keyboard.unknown:
key_key.jei.previousCategory:key.keyboard.unknown:
key_key.jei.nextCategory:key.keyboard.unknown:
key_key.jei.closeRecipeGui:key.keyboard.unknown:
key_key.aether.open_accessories.desc:key.keyboard.unknown:
key_key.aether.invisibility_toggle.desc:key.keyboard.unknown:
key_key.deep_aether.stratus_dash_ability.desc:key.keyboard.unknown:
key_key.deep_aether.toggle_skyjade_transparency:key.keyboard.unknown:
key_key.cataclysm.ability:key.keyboard.unknown:
key_key.cataclysm.helmet_ability:key.keyboard.unknown:
key_key.cataclysm.chestplate_ability:key.keyboard.unknown:
key_key.cataclysm.boots_ability:key.keyboard.unknown:
key_key.fightorflight.startbattle:key.keyboard.unknown:
key_key.journeymap.minimap_type:key.keyboard.unknown:
key_key.journeymap.minimap_preset:key.keyboard.unknown:
key_key.journeymap.create_waypoint:key.keyboard.unknown:
key_key.journeymap.toggle_entity_names:key.keyboard.unknown:
key_key.irons_spellbooks.spell_wheel:key.keyboard.v
key_key.irons_spellbooks.spellbook_cast:key.keyboard.r
key_key.inventoryhud.toggle:key.keyboard.unknown:
key_key.inventoryhud.openconfig:key.keyboard.unknown:

View file

@ -1 +0,0 @@
guiScale:2

View file

@ -1,842 +0,0 @@
hash-format = "sha256"
[[files]]
file = "config/defaultoptions/extra/config/cataclysm.toml"
hash = "9a76bf60248db9056912d34a2f5c303882f9e4bd4c1b9ffe61fef99b8eb0de38"
[[files]]
file = "config/defaultoptions/extra/config/inventoryhud-client.toml"
hash = "61d89f6c1a9c1398ff109bc4f13ec30b68f3e71408b005ec096ebab163c05a53"
[[files]]
file = "config/defaultoptions/extra/config/iris.properties"
hash = "7c386f2921f9c1a94c984114ff8df28d7c127b5b986ce344615e4ac4cb02c4eb"
[[files]]
file = "config/defaultoptions/extra/config/sodium-extra-options.json"
hash = "92a62325cccecccf843d04e1be02f036fa1a3506a21290ebf040fbb78b46fc1f"
[[files]]
file = "config/defaultoptions/extra/config/sodiumdynamiclights-client.toml"
hash = "33516556e1f7761835817a2ad13a42c3563010cf4df15296b2f685e95d0ddcb1"
[[files]]
file = "config/defaultoptions/extra/config/sodiumextras-client.toml"
hash = "8b493ad01cbdb351da8f51d98ad791b9ae9519e69d353acf82a2f893094853ef"
[[files]]
file = "config/defaultoptions/keybindings.txt"
hash = "64852eef84ab3f853973c3ad6affb00999638d0fc49515b9daa42981e3b9f5b2"
[[files]]
file = "config/defaultoptions/options.txt"
hash = "624a8e4974cae64389b28e8046c3cc18a94e609a02df63f5397ec2ca0c0f7ee8"
[[files]]
file = "config/defaultoptions/servers.dat"
hash = "0e7bfc4d1ae85b5e1bb57914cb3d07abf75a4ef5158763095f5208891f43bfe7"
[[files]]
file = "mods/aether-emissivity.pw.toml"
hash = "d245f988ce58a69b2734cbd656afc8c0ba510a7ddcc7b779c0f05de3275c52d3"
metafile = true
[[files]]
file = "mods/aether-enhanced-extinguishing.pw.toml"
hash = "3ab0e784d15c52edda001697336bc3d7bceb1d016c89b166c77b10f6fb689737"
metafile = true
[[files]]
file = "mods/aether.pw.toml"
hash = "5e62bb12eb55e4575d1ca5e3570b5cc18473138973c80ac7093e61e459339db1"
metafile = true
[[files]]
file = "mods/ambientsounds.pw.toml"
hash = "f63a7e300c9556f4bcb76b7afd3aecc4d19d5323d24c48709083f5dafcbde09f"
metafile = true
[[files]]
file = "mods/appleskin.pw.toml"
hash = "3213c881d019cd8f952f738c10d5a950ee52ff0084750b81f656a58a4a78515d"
metafile = true
[[files]]
file = "mods/architectury-api.pw.toml"
hash = "ea336d25c3e21ef168aec20e1ea470c96949ad29632646ee8e8ab0d74b4bbfc9"
metafile = true
[[files]]
file = "mods/armourers-workshop.pw.toml"
hash = "735660605aff58b7a921c328a62195c3600d23b9e7709784c3863bd618adc19a"
metafile = true
[[files]]
file = "mods/attributefix.pw.toml"
hash = "586341de7078015cea16ffaf8e2e198a16c43b306d70bc439a15ea0298f9d3cb"
metafile = true
[[files]]
file = "mods/bad-wither-no-cookie.pw.toml"
hash = "574807cedb57ba6360060d6bf4a359cea9d00bf8700caee866d48ab949005b89"
metafile = true
[[files]]
file = "mods/badoptimizations.pw.toml"
hash = "c92c73551dc3ba9f116e0cef1e90bb553bf59f34530c7c281af28ac624a3a9d6"
metafile = true
[[files]]
file = "mods/balm.pw.toml"
hash = "c51f5bc71fff2b0df7420b1c3df9f68cadb2572f6d8df239734db83c186b6715"
metafile = true
[[files]]
file = "mods/barbeques-delight-forge.pw.toml"
hash = "ae63e4f67427755b231839f860c21db3737d8aae799e7d2677e756271294238c"
metafile = true
[[files]]
file = "mods/berry-good.pw.toml"
hash = "781a0931f5ab4cc333717c2d6a56ce311474d2c0bd27681c4126aa8f24e97aa8"
metafile = true
[[files]]
file = "mods/better-advancements.pw.toml"
hash = "93c4acd165a291c6d1e69c27b05b2747afc926b38a414712263fbc95f42da323"
metafile = true
[[files]]
file = "mods/better-animations-collection.pw.toml"
hash = "fbb8968dd31ab07131a8885ddf4f70999fad54cdd68d448dbb09dd56b2bf7919"
metafile = true
[[files]]
file = "mods/better-third-person.pw.toml"
hash = "e1f1588b18947699ddc9b4f7c5b5406c5559155b8cb52dcd0f203c07c31cd0b8"
metafile = true
[[files]]
file = "mods/betterf3.pw.toml"
hash = "c9aca350959a8936b054c7164fb55d789ab1e0d38cff2af48b78f70af2bc0c6d"
metafile = true
[[files]]
file = "mods/blueprint.pw.toml"
hash = "02c2891e2548adf5c59d0ef237ed01ffd89632b4908290269b513c4958ca9043"
metafile = true
[[files]]
file = "mods/bookshelf-lib.pw.toml"
hash = "293ec22e4e8342a9bd0f338505a14e3794b44cd7ca1e5278a86f41f99c69a3af"
metafile = true
[[files]]
file = "mods/cant-sleep-clowns-will-eat-me.pw.toml"
hash = "c9b95c629249ab90b4baeb88f501c51592b52f3edddb20ebf6eed96631a54839"
metafile = true
[[files]]
file = "mods/cloth-config.pw.toml"
hash = "75181b55e0407d1fca1e2f346c6501f919f5109545990ddfb66db9cd1b771f8a"
metafile = true
[[files]]
file = "mods/clumps.pw.toml"
hash = "2acaff560bbda5741538528a2967355ec86a8f7564adba0b2316989c66892374"
metafile = true
[[files]]
file = "mods/cobblemon-additions.pw.toml"
hash = "d94f943034bca0270c72a3e56a9ef4864d81e2300c5e64418ee2b2619b7d979b"
metafile = true
[[files]]
file = "mods/cobblemon-counter.pw.toml"
hash = "46e0f70fdcc4d47e3750b8b62a9d69c2accc3b08aafc25891ba1617c3c586714"
metafile = true
[[files]]
file = "mods/cobblemon-fight-or-flight-reborn.pw.toml"
hash = "c6c63f3818b49bf0de7d5000e43ef6a381faf7d46198b13771ab6080c751f56c"
metafile = true
[[files]]
file = "mods/cobblemon-integrations.pw.toml"
hash = "d21c41437169bb49b9b92bc97a2f05daa45bcd56744f590a2a858840a5f56044"
metafile = true
[[files]]
file = "mods/cobblemon-pokenav.pw.toml"
hash = "0d1d81244b3898bfeced61aaba79e063ced9778f0c70aaddd0bd2442131066b5"
metafile = true
[[files]]
file = "mods/cobblemon-unchained.pw.toml"
hash = "98f1c04523e6d97b0d91a40614aff3d15a9e6e3c3107876e1584fdcfcb014d67"
metafile = true
[[files]]
file = "mods/cobblemon.pw.toml"
hash = "e3cb1eb238192071af93f764e3ff2c493f4f437802bb56273f664bafdea13f1d"
metafile = true
[[files]]
file = "mods/colorwheel-patcher.pw.toml"
hash = "2d8c419acad3bab8eb54c6d5461f99ab76d924b753a9c811e7e94d624fe32f61"
metafile = true
[[files]]
file = "mods/colorwheel.pw.toml"
hash = "9da820e4bd91294a900ec2c2859fa8ae261a573922df3a15ca64cb838c76df9f"
metafile = true
[[files]]
file = "mods/comforts.pw.toml"
hash = "5e0a875282f4abed66a2cccf7929488ebb5e88447d2bb3369e8ddc728fc66fc0"
metafile = true
[[files]]
file = "mods/connector.pw.toml"
hash = "9f920c5f1df2577cf374285e4aad4f2982a30ea2a3da1cf7ddaf25e04b3a2e31"
metafile = true
[[files]]
file = "mods/controlling.pw.toml"
hash = "65c8086d2ec3d39acc7bd36c7a0306ccc43b602b38fa07d13291bc478ef7dfff"
metafile = true
[[files]]
file = "mods/corn-delight.pw.toml"
hash = "a7216dad68fe8cabfbdcced66ad94e1da20d23fe531e03022f877ffead92df50"
metafile = true
[[files]]
file = "mods/corpse.pw.toml"
hash = "d0c4db41717ede48a6f9c47ce2cd30a4336b7520279cac56649f96939cd1e7cb"
metafile = true
[[files]]
file = "mods/crabbers-delight.pw.toml"
hash = "cd66e48a19b8447ef66c6148bbcd5ecb363f4a7b472dba385b4b790feae0ad0f"
metafile = true
[[files]]
file = "mods/create-dragons-plus.pw.toml"
hash = "8e88d68a9dee5ca107ea3fd5d5394e52ae4e4f03decfc33974d6a958ccb200b5"
metafile = true
[[files]]
file = "mods/create-enchantment-industry.pw.toml"
hash = "0177b98d7552743df5bd62e45d2efe012d8d416c53f94735a0d4074468f5a2ca"
metafile = true
[[files]]
file = "mods/create.pw.toml"
hash = "7f25446123f66f57e7cc8e148155a3da39d85b31963c663b5b9be77fc1bacbe6"
metafile = true
[[files]]
file = "mods/creativecore.pw.toml"
hash = "6028668d5c1c5e49797eb97bb597d818be33a67a939d0cc356a702460b504675"
metafile = true
[[files]]
file = "mods/curios.pw.toml"
hash = "36debf7653e95a855b4fedacc19027cf2e01960fc0b6d55ef2ffbffbcb455690"
metafile = true
[[files]]
file = "mods/cut-through.pw.toml"
hash = "4c16ee75d3b04443e96d854eec79344293963e9f4beaaf8e7215023e8ef53b88"
metafile = true
[[files]]
file = "mods/deep-aether.pw.toml"
hash = "5e0f65ac9e6e260e213f3d9a6657cafdd35f509965b09404a8eb9ab55c3ffbec"
metafile = true
[[files]]
file = "mods/default-options.pw.toml"
hash = "8950c7377844b642f7e6547b821691f395a6d3f90f8cf9a6d740e390792a2de3"
metafile = true
[[files]]
file = "mods/diagonal-fences.pw.toml"
hash = "632743d54d410c8b78fb92c94d8f3bb6a7a3faed45171f91e554877f94f82163"
metafile = true
[[files]]
file = "mods/diagonal-walls.pw.toml"
hash = "bbc15c019fd260bbb9058b14a8e03232a55df77608d9aca466d051007d62abd2"
metafile = true
[[files]]
file = "mods/diagonal-windows.pw.toml"
hash = "218dcd780f0251a15f78b097206ea22a519d57b974b39e2fdd43dce91ac07133"
metafile = true
[[files]]
file = "mods/distanthorizons.pw.toml"
hash = "34fc0845727b08bc39e5b56444e0695b55f6a5471380f2d1d40c5548105da28e"
metafile = true
[[files]]
file = "mods/distinguished-potions.pw.toml"
hash = "fa1ff0b4d2c6fc9c26b74ba4b06d30f1e6d77763c6e2823b42fb2c08f5b72f13"
metafile = true
[[files]]
file = "mods/dynamic-fps.pw.toml"
hash = "975f1280c712d2e86dd4ecddf89c516afbf3f1fb684499d3e5c71fc666c4888c"
metafile = true
[[files]]
file = "mods/edivadlib.pw.toml"
hash = "e2ee2481b63a012bdfb8c9f588788dd2f116ff48298014bb3e00d08a224fe14f"
metafile = true
[[files]]
file = "mods/emi-create-schematics.pw.toml"
hash = "8a9bf4dfd8d29112e64dbffd71a84aba5279f6c85ed31233f026c84ebc4ef688"
metafile = true
[[files]]
file = "mods/emi-enchanting.pw.toml"
hash = "ff029ee6fc50f2696e96cdbcc696f42e75b483c9d7b83f2b1447dcf5c865e664"
metafile = true
[[files]]
file = "mods/emi-loot.pw.toml"
hash = "bbf03bec2bd4927ace16eef106c45b0f3ec53c48d5f43119520123ad98f3fdf1"
metafile = true
[[files]]
file = "mods/emi-ores.pw.toml"
hash = "0b921f73d5b1ad6615740956567d6cb06950f5520f094dbb7a045bb4bd5ba309"
metafile = true
[[files]]
file = "mods/emi-professions-(emip).pw.toml"
hash = "0477d572817d9de3a5569109a8d07a2e519af9ccf0e8236cae621369afbc6e4c"
metafile = true
[[files]]
file = "mods/emi.pw.toml"
hash = "d3c9b48d7caa2ed964e5d1a4dab1da5655c63f0a7e4a59231c4369be7ffc30e1"
metafile = true
[[files]]
file = "mods/emiffect.pw.toml"
hash = "34342d4a5a9a4853c653cf768309d5b42f651250dc832bc239e746c736c70411"
metafile = true
[[files]]
file = "mods/enchantment-descriptions.pw.toml"
hash = "0b380e24e46e328c362bda7cff892c5b0e471789ca8c429fd2a588eaf63b8712"
metafile = true
[[files]]
file = "mods/ends-delight.pw.toml"
hash = "c31e8aea1f4ddc50d79c6dfa46dec8cbbf1a6108ff74db140baa0395ea4c5766"
metafile = true
[[files]]
file = "mods/entityculling.pw.toml"
hash = "65026277699cc4b73c3d2c57ab7aad5173f161c0d6ff95e184911826387dbdec"
metafile = true
[[files]]
file = "mods/every-compat.pw.toml"
hash = "6235e7170c7322559d2b0e76a97c891dbab60e79c15a9d4cb3a4805b5d23fbda"
metafile = true
[[files]]
file = "mods/extra-mod-integrations.pw.toml"
hash = "6f59e18621f30c6dc0ffa7f34324b423f3e3a7a8c251560c6a1dd835b6bce87a"
metafile = true
[[files]]
file = "mods/extrastorage.pw.toml"
hash = "6569836247170fbd7c8bc9397cf07961f8f67f7e73b404bfe479172da2f84279"
metafile = true
[[files]]
file = "mods/farmers-delight.pw.toml"
hash = "c4027b8071cc6dfc7f0d49e702a5e25b1e36841e50acba1e699f308be156b8f2"
metafile = true
[[files]]
file = "mods/forgified-fabric-api.pw.toml"
hash = "5b34f5aff5000de94ed9e132315bad7288a72a9cbb8f902df2799a579433b7af"
metafile = true
[[files]]
file = "mods/friends-and-foes-beekeeper-hut-forge.pw.toml"
hash = "75c642b17b484c0d7c4ec9863295c24364ef69785937d15a80de568efafd585a"
metafile = true
[[files]]
file = "mods/friends-and-foes-flowery-mooblooms-forge.pw.toml"
hash = "1cdeed5adabcbac7ca3d06ead6392d43ae400557f71275c5449ec2505008cd40"
metafile = true
[[files]]
file = "mods/friends-and-foes-forge.pw.toml"
hash = "1f1c592232159b04b7009d2413eebb2b18b5a92f7d0168cd4dde2f446bf45653"
metafile = true
[[files]]
file = "mods/fruits-delight.pw.toml"
hash = "70eaf1f619cfa0c05d2b74a3b74f3a8f28466e13feb23aaed538efcb85178a99"
metafile = true
[[files]]
file = "mods/fzzy-config.pw.toml"
hash = "e00b467d81fc5f35bf7432bc3b5d371281b0719eeae4377854a5f8ca33968190"
metafile = true
[[files]]
file = "mods/geckolib.pw.toml"
hash = "9e80e6f2a680b052f2a66ade20a5abbd98a0abbebde6f30472d6ae6b993e9464"
metafile = true
[[files]]
file = "mods/hardcore-revival.pw.toml"
hash = "2c0d0941d6ade8fb28d43b2ba211678aa643d229f0df0c2112442de1e5073a7b"
metafile = true
[[files]]
file = "mods/immersiveengineering.pw.toml"
hash = "91875c996a9958aeea794843fb449915218625f52aaf0234ccd1faffa887e277"
metafile = true
[[files]]
file = "mods/immersivemusicmod.pw.toml"
hash = "9b105ded2255d63b2b4d8b3bb24c14e467617d56019dc9abafe64cbb0c0d3cfa"
metafile = true
[[files]]
file = "mods/inventory-hud-forge.pw.toml"
hash = "bacbda049bf918a3a048f017a23e3197a91d31ab905e25f7a811d69d55344ca6"
metafile = true
[[files]]
file = "mods/iris.pw.toml"
hash = "f67aa60c765593a8c70f78296a460349c6aef5f4a16d173eae02e8803b545ad4"
metafile = true
[[files]]
file = "mods/irons-spells-n-spellbooks.pw.toml"
hash = "cf1bb72bbbb1ad55af745b6ccb8baf8ff5d6914dc11e41e4752212ca3eb17440"
metafile = true
[[files]]
file = "mods/jade.pw.toml"
hash = "e7d3f7e0472ba9a3ef946fa5defcb157e4126636e603c2306d4ad6065936d5e2"
metafile = true
[[files]]
file = "mods/jei.pw.toml"
hash = "8d3e669a813af2e685efafeca797f95ad8a99c344078e72d2ce0ee1273369bd8"
metafile = true
[[files]]
file = "mods/journey-pac.pw.toml"
hash = "c1a7d513c59eeb49bd2f377236357a280cc2e1d206c3eb3985a3a4615ed97aec"
metafile = true
[[files]]
file = "mods/journeymap-integration.pw.toml"
hash = "f161cd39e3619182a8606ec812634d74713d32ca30c3585bdf7d40b7bb573919"
metafile = true
[[files]]
file = "mods/journeymap.pw.toml"
hash = "cc903c56f854019dc420ab9a088e1a3a328e7839291aafae038f37005e59988e"
metafile = true
[[files]]
file = "mods/kiwi.pw.toml"
hash = "a5e837dd0b40e4c730252cd8b571a28bb8baab22ab8c277d2293b39ab403c201"
metafile = true
[[files]]
file = "mods/kotlin-for-forge.pw.toml"
hash = "297eb75ce1aedc28f183a8891fde9c67318953e6c8adf3d7531a17b211f9349d"
metafile = true
[[files]]
file = "mods/l_enders-cataclysm.pw.toml"
hash = "b07bbd76636d50d623dc1c41d27ca42f5f7ba196e3e2a9c846add0ee10c26531"
metafile = true
[[files]]
file = "mods/leave-my-bars-alone.pw.toml"
hash = "6fc46d72336be11582b933177410f6e0e1e89c5464e19a1885c5ce80738b01c5"
metafile = true
[[files]]
file = "mods/leaves-be-gone.pw.toml"
hash = "6917553073df67745da1d535cc6107839a76b54081b1b6f368a38c723f5fd8c7"
metafile = true
[[files]]
file = "mods/lionfish-api.pw.toml"
hash = "b821efc793dd7308be6788095bea26a2a0479f9a3ddc3b0d6a6a2af351153fc0"
metafile = true
[[files]]
file = "mods/lithostitched.pw.toml"
hash = "c18ca4f4035e67182ea949ee2d6cd2c31409306ffc404c0c14bc5c1c212747a8"
metafile = true
[[files]]
file = "mods/magnum-torch.pw.toml"
hash = "f0c97c0212ddda5c012ba451a123d4b39e89f402cc70d38d31dc9098736ec07f"
metafile = true
[[files]]
file = "mods/mmlib.pw.toml"
hash = "59f033615895cd019f9fb168fa93590422c5050274630a8faec46460979a5156"
metafile = true
[[files]]
file = "mods/modelfix.pw.toml"
hash = "6f0dcbcffd4400c6cef07f1eeb48cdc25bac0fa01270936feab4c549aff4738e"
metafile = true
[[files]]
file = "mods/moderately-enough-effect-descriptions-(meed)-jeed-addon.pw.toml"
hash = "67cf0829e2773cb44edda983e169b8197deb8e87b1cc8e1669c6ad5e4c760880"
metafile = true
[[files]]
file = "mods/modernfix.pw.toml"
hash = "a9593968393bae08257ba9aae887ad60fc49991f676b637006b2bcd89907f09a"
metafile = true
[[files]]
file = "mods/moonlight.pw.toml"
hash = "2219a1b4f53f8a96bf1e9827aa695e84ad9d53623f7fe40b88ccd260c732cc98"
metafile = true
[[files]]
file = "mods/more-concrete.pw.toml"
hash = "af08c145608a13f68a624394b68611753f7d7d6f439538e2c328eea60be5a8e1"
metafile = true
[[files]]
file = "mods/mouse-tweaks.pw.toml"
hash = "6bc414cc0f2d4c078fe83f384d786343f701f371d657c5279b5b0df1595804bd"
metafile = true
[[files]]
file = "mods/no-chat-reports.pw.toml"
hash = "5da18814472be5dc93a21886983646da3a7236413800075ac75229be82d114fa"
metafile = true
[[files]]
file = "mods/noisium.pw.toml"
hash = "b3e831b3e4d6ddbf23806b0fec6087eca264af1ebc04e747c89024972aaa8607"
metafile = true
[[files]]
file = "mods/notenoughrecipebook.pw.toml"
hash = "8c36ea5d11344480d76f8463f452c4ecc8ee59781667589ea2fd7b69f6ee86e8"
metafile = true
[[files]]
file = "mods/nullscape.pw.toml"
hash = "71dba581a68de19790dd6888e515d8b693debfc915a18ffb82af473c395f4dd3"
metafile = true
[[files]]
file = "mods/oceans-delight.pw.toml"
hash = "152f6cfe8ee891ba80faa800dd3df3058be14882f9f8d07cb38eb6a6f96ece4b"
metafile = true
[[files]]
file = "mods/octo-lib.pw.toml"
hash = "94828636fa5e8b258a60049250c84ba8c5a0310a98bc1530fc02c351a06aab8b"
metafile = true
[[files]]
file = "mods/ominous-phantoms.pw.toml"
hash = "086a276f1ab191871a11914448792bcdacc29de00770978b55ebe7d40eb6f4c9"
metafile = true
[[files]]
file = "mods/open-loader.pw.toml"
hash = "904c4adb3f8087a2c5381efd5d210183484934540372a5c839ab4790325e5cff"
metafile = true
[[files]]
file = "mods/open-parties-and-claims.pw.toml"
hash = "28d546c3ea37927bd45975200aa74c1e8fb8bd82d2a29e611abe577389818702"
metafile = true
[[files]]
file = "mods/overflowing-bars.pw.toml"
hash = "cb40fc0e1e0a3338d57b34a5ba2612ae9a9f0e32619695c4da637a98b4e52cf3"
metafile = true
[[files]]
file = "mods/owo-lib.pw.toml"
hash = "46831c04077a06df823bcd0f254833a90a9a9ccc78f5069f4b4c27fa301ac0cf"
metafile = true
[[files]]
file = "mods/pick-up-notifier.pw.toml"
hash = "a1b19740a94f43405c4a9d303e9313911b3d12e48958699bfeaed3dafc57a24f"
metafile = true
[[files]]
file = "mods/platter.pw.toml"
hash = "f82f7a5186b43d1e40773b8576a711f4d93bf09f4e3dcacc811e896f8271ad9c"
metafile = true
[[files]]
file = "mods/playeranimator.pw.toml"
hash = "c83705becd548879473132552de960f9d5975fa0edaeeb2d4665a4e35457423e"
metafile = true
[[files]]
file = "mods/prickle.pw.toml"
hash = "a69a8ba0d9e52361b1853cf59b2cf2a38b668b1d508a565f315f2fa30407f546"
metafile = true
[[files]]
file = "mods/puzzles-lib.pw.toml"
hash = "e91052ce0d9999e7a1a62ebbe9fe5f347a7cd0b0bfd3b4395ae33e5efa164773"
metafile = true
[[files]]
file = "mods/reeses-sodium-options.pw.toml"
hash = "09306b955060349986ccf6a6d2a67f1e019fa172a7dc690dd6007f81c966309a"
metafile = true
[[files]]
file = "mods/refined-storage-emi-integration.pw.toml"
hash = "4a5af515250fbde913593346d51b2c4ac8f660e71ba21f65f49a02209bb65509"
metafile = true
[[files]]
file = "mods/refined-storage.pw.toml"
hash = "dedc0295cba84c8241eeaa078838f333631dd6c67d693b3a0b8dd93cdfdae33d"
metafile = true
[[files]]
file = "mods/resource-pack-overrides.pw.toml"
hash = "b8101dca20d054277d99e75b43dd8db44dea64a761d2ab69fdda9f6d59d9e6f1"
metafile = true
[[files]]
file = "mods/resourceful-lib.pw.toml"
hash = "c0b066684294c33972d562e0cb08ea17b2d31bcf86cd1d6c63b8e968f0eac7a2"
metafile = true
[[files]]
file = "mods/respawning-animals.pw.toml"
hash = "c24f3a995c06808a047abf9b8b97fb1e09defcbab121755cb23f7316a6b0bb4c"
metafile = true
[[files]]
file = "mods/rrls.pw.toml"
hash = "b78044fd79ce21750d05dc61b6aede33c4b87cdc16a26f5aff472d4f73ab623c"
metafile = true
[[files]]
file = "mods/searchables.pw.toml"
hash = "ce9ddada6ffd55a512149cc474c6a453281a997a5fdf1b7b4a2cc73f875e704a"
metafile = true
[[files]]
file = "mods/silent-gear.pw.toml"
hash = "3ef17cc38cbe19ee924f20f3bfebb7c52bb2bb0db5e578e32473870bd1de67fa"
metafile = true
[[files]]
file = "mods/silent-lib.pw.toml"
hash = "0270a241e1d1c3c8e1ecda59aeaa5004771a57d46a4a470566cb7dd6d7e89c85"
metafile = true
[[files]]
file = "mods/simpletms-tms-and-trs-for-cobblemon.pw.toml"
hash = "f8ba20a06c8391dedc25741ab2817e1af2bfb9a182eee248471dae4b41eb7d77"
metafile = true
[[files]]
file = "mods/snow-real-magic.pw.toml"
hash = "a1deef8071fb83a13df63c289576813b82f0687eebda9b48b7667620d5d45103"
metafile = true
[[files]]
file = "mods/snow-under-trees.pw.toml"
hash = "82aedef3c457ffe51e27a4153aaf73ef7c073b87059a021495af487e41ed2e99"
metafile = true
[[files]]
file = "mods/sodium-dynamic-lights.pw.toml"
hash = "c1e526d3d17cb3563e596d45c7567ed1fe60c31bdc3457f7a747ef8150bd3c1b"
metafile = true
[[files]]
file = "mods/sodium-extra.pw.toml"
hash = "a1fb713df668ed21dda0602e99cb23b991c8faff841e130d7aae46258c3d6a45"
metafile = true
[[files]]
file = "mods/sodium-extras.pw.toml"
hash = "02b29315d2ec0d055d0ebba1f36879da5bcacc823328a668c7e7340fb2cb03d4"
metafile = true
[[files]]
file = "mods/sodium-options-api.pw.toml"
hash = "d193173f0f5dfb4a921d44e29e8d5cee656dd3a40872939069715bb482b66fc9"
metafile = true
[[files]]
file = "mods/sodium.pw.toml"
hash = "0281ed9713f3141bdac363056a791845510e14761ea65b4b2813cf96d664b20a"
metafile = true
[[files]]
file = "mods/sophisticated-backpacks.pw.toml"
hash = "b298b954e05956871a5f724569c226d97cc57059d8f587e5a768f3aa781de014"
metafile = true
[[files]]
file = "mods/sophisticated-core.pw.toml"
hash = "02c8e010cf0edca6b868cbf16dcc6e70bddea0d6d5451b1ff7fcc10056972adc"
metafile = true
[[files]]
file = "mods/sophisticated-storage-create-integration.pw.toml"
hash = "27ff0f0f6845e5ca640a1d0eaa70ec9af869e5794c18abf65546a23c9ceac765"
metafile = true
[[files]]
file = "mods/sophisticated-storage.pw.toml"
hash = "f21fbfc70bc381e1beb3d8e123ec7a9fe5290e70bc0b91fe53e26e2244c71ef0"
metafile = true
[[files]]
file = "mods/spice-of-life-onion.pw.toml"
hash = "bce75bb35eb4719687f61b217bb57d83ef0e63040c291354c1b2369f3c508680"
metafile = true
[[files]]
file = "mods/storage-delight.pw.toml"
hash = "0ccdb1ac3f7fffacc28ffa235bdf7e37105c088095f96b6e3fc9748d64328b94"
metafile = true
[[files]]
file = "mods/stylish-effects.pw.toml"
hash = "07208867dff9eb56eed1f370186187dab4ac0c1086d57ce5fd9ce07ea93a5320"
metafile = true
[[files]]
file = "mods/subtle-effects.pw.toml"
hash = "dc70a1f2d451deb0718c82d08bbdef00bb7b86fb4ecde86a2b63c87a27a6d7a0"
metafile = true
[[files]]
file = "mods/tax-free-levels.pw.toml"
hash = "dbfe27c8d868f5d934b882b8d26f3772fad78d97da191937d051a4f5c81a0b3b"
metafile = true
[[files]]
file = "mods/tectonic.pw.toml"
hash = "3601406f8001d9e5a1564e0bed50a4b92f75d435af37415e44900ae74273bb0e"
metafile = true
[[files]]
file = "mods/terralith.pw.toml"
hash = "da7ee99189ad35aac094919fe33cac647d98533b23c228f10d4811f08989e2b8"
metafile = true
[[files]]
file = "mods/tidal-towns.pw.toml"
hash = "0ccb1c696758d7935d02dd5160987cab8d3594ab24f89deb01c28f9e596094e7"
metafile = true
[[files]]
file = "mods/wall-jump-txf.pw.toml"
hash = "4ba37fc038fb52396b3557d6249d96c45acd8feec0355589032ea625d0a9edd6"
metafile = true
[[files]]
file = "mods/yacl.pw.toml"
hash = "aa9070a0e763f00f0d0b325e9bb980e53faabe9854bae25f2e542a7b81b7492e"
metafile = true
[[files]]
file = "mods/yeetus-experimentus.pw.toml"
hash = "5e25dccab496945a5f19bb82383e0ebf98452ffc14cd25c7e0259cc44312281a"
metafile = true
[[files]]
file = "mods/yungs-api.pw.toml"
hash = "d9c0c9209bac0b693af2a7c1e47ffb28d366bad19c91df31dc6d345fa75250a1"
metafile = true
[[files]]
file = "mods/yungs-better-desert-temples.pw.toml"
hash = "c2cb28859e7bcc0eb4ce59c5792f90fa5f8ab8865f5867ba26a5a345ec60b498"
metafile = true
[[files]]
file = "mods/yungs-better-dungeons.pw.toml"
hash = "1496ffa30020676979e65921b91e583a2fdc77e50001e2ed55f99edf1a0ae880"
metafile = true
[[files]]
file = "mods/yungs-better-end-island.pw.toml"
hash = "03d964cba45be30fbcbef52f89f3953185737f55c8934d12e199b67194c2b368"
metafile = true
[[files]]
file = "mods/yungs-better-jungle-temples.pw.toml"
hash = "c2dc09f214e7c1cecca6cf434776c2616f6d36bd27c06a6b39f985da0453a4f5"
metafile = true
[[files]]
file = "mods/yungs-better-mineshafts.pw.toml"
hash = "e839608ebc974e9e7819adc7bf29132ea86fc41b46af540fe0fa39b2007b2439"
metafile = true
[[files]]
file = "mods/yungs-better-nether-fortresses.pw.toml"
hash = "50c1731148d91a4ef05cfc47f759ca8fb3dad254e15ce4ecc5732d6fbfa68637"
metafile = true
[[files]]
file = "mods/yungs-better-ocean-monuments.pw.toml"
hash = "a2d7176026abf4a68a663b375811a603c508f36062cc7fb5b45416e1462682a8"
metafile = true
[[files]]
file = "mods/yungs-better-strongholds.pw.toml"
hash = "03084966c05070b5c4b17cd89234f3c0d813940a2d76b405a6699deac10a0ab8"
metafile = true
[[files]]
file = "mods/yungs-better-witch-huts.pw.toml"
hash = "9854b699d5a067c46ac99b1dedeeb529bff3fc18dd079e29b438c4cbf9725f68"
metafile = true
[[files]]
file = "mods/yungs-bridges.pw.toml"
hash = "c5e5f5aed37edd8073ec736e179b6159530ff497f4fe4c4dfaf954c810910433"
metafile = true
[[files]]
file = "mods/yungs-extras.pw.toml"
hash = "cfb7ce4c2b00cedd2e61f7d345b824d6bcc1471021668d2319ee2e4079108639"
metafile = true
[[files]]
file = "shaderpacks/complementary-reimagined.pw.toml"
hash = "3385e33cb8e2cbf402562dcb68669cc2905fec01b4be9fe27a516010c5aaff87"
metafile = true

View file

@ -1,13 +0,0 @@
name = "Aether Addon: Emissivity"
filename = "aether_emissivity-1.21.1-1.0.1-neoforge.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/sKHHynnb/versions/NgXUuFDh/aether_emissivity-1.21.1-1.0.1-neoforge.jar"
hash-format = "sha512"
hash = "51d68890596958d9dba32afce0f27b8d03fcfbaafec654c8604e847beef681adb895b85a349380997d448449c86d255825b0061290daf0db5fe890bc31016689"
[update]
[update.modrinth]
mod-id = "sKHHynnb"
version = "NgXUuFDh"

View file

@ -1,13 +0,0 @@
name = "Aether Addon: Enhanced Extinguishing"
filename = "aether_enhanced_extinguishing-1.21.1-1.0.0-neoforge.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/FDrEl7QY/versions/6O6CJTuj/aether_enhanced_extinguishing-1.21.1-1.0.0-neoforge.jar"
hash-format = "sha512"
hash = "006a921f1c52bd8aa0c195cda646ab6787c28bbc70ebed66d7d444838f464ba8be479c74b801deeb9aaf3ddf7350ea238fc73228af275a7112783d91c064d4b0"
[update]
[update.modrinth]
mod-id = "FDrEl7QY"
version = "6O6CJTuj"

View file

@ -1,13 +0,0 @@
name = "The Aether"
filename = "aether-1.21.1-1.5.8-neoforge.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/YhmgMVyu/versions/IOyjRzWX/aether-1.21.1-1.5.8-neoforge.jar"
hash-format = "sha512"
hash = "3343a8d87f49ae46eeae1aa657bcec8a6a6451110ffee18bc7d6e6b5df5a85c01a534c1bc01ef7f62648abc8a8ee408ac741891cd066804fa3fe6761b9532f7e"
[update]
[update.modrinth]
mod-id = "YhmgMVyu"
version = "IOyjRzWX"

View file

@ -1,13 +0,0 @@
name = "AmbientSounds"
filename = "AmbientSounds_NEOFORGE_v6.1.12_mc1.21.1.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/fM515JnW/versions/YY79wyx3/AmbientSounds_NEOFORGE_v6.1.12_mc1.21.1.jar"
hash-format = "sha512"
hash = "9af4fdae4d520924299baea52a56c2bb3ce6997d356ba7e74d5a48cf42ee6353583037fa3cbc36172c97cff806b321bbb8d9b8d4065465cfbf8d883b67c80340"
[update]
[update.modrinth]
mod-id = "fM515JnW"
version = "YY79wyx3"

View file

@ -1,13 +0,0 @@
name = "AppleSkin"
filename = "appleskin-neoforge-mc1.21-3.0.7.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/EsAfCjCV/versions/kztxpjAA/appleskin-neoforge-mc1.21-3.0.7.jar"
hash-format = "sha512"
hash = "03a94fe4143250b8e80abe97770918ba0af1265110bb73c25444674f9cdf86164464332a913cbac29af82d8ed02dce6ef19bbb62fced92620817a7ec1e761b71"
[update]
[update.modrinth]
mod-id = "EsAfCjCV"
version = "kztxpjAA"

View file

@ -1,13 +0,0 @@
name = "Architectury API"
filename = "architectury-13.0.8-neoforge.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/lhGA9TYQ/versions/ZxYGwlk0/architectury-13.0.8-neoforge.jar"
hash-format = "sha512"
hash = "65e3664953385d880320dd6bb818bcb96d361c07c53e2a7f65e64c6a47720ee26b233224ae9cad465ef0b2bbaefdaf30fb0175a983cecd91de058817d6fcf57e"
[update]
[update.modrinth]
mod-id = "lhGA9TYQ"
version = "ZxYGwlk0"

View file

@ -1,13 +0,0 @@
name = "Armourer's Workshop"
filename = "armourersworkshop-forge-1.21.1-3.2.3-beta.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/y4JF3gXL/versions/gWKk3ffs/armourersworkshop-forge-1.21.1-3.2.3-beta.jar"
hash-format = "sha512"
hash = "d36aa65096c3293db2b6f4056123f737823bd55eeaf86984e9bfd83d3ab33e68f0e38770962913530d4113ceebac6ebb53676c739975aac8a56f646eccd7fd0c"
[update]
[update.modrinth]
mod-id = "y4JF3gXL"
version = "gWKk3ffs"

View file

@ -1,13 +0,0 @@
name = "AttributeFix"
filename = "attributefix-neoforge-1.21.1-21.1.2.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/lOOpEntO/versions/a0oKmnPU/attributefix-neoforge-1.21.1-21.1.2.jar"
hash-format = "sha512"
hash = "bbfe003ed29a2bb0b4dd0a61d64b411748c9f5cb48ccbfd954dd2d813c452c540638749b6f19e7472e44ae1e966a3629b135190ffba3099cfdf45355618c0b97"
[update]
[update.modrinth]
mod-id = "lOOpEntO"
version = "a0oKmnPU"

View file

@ -1,13 +0,0 @@
name = "Bad Wither No Cookie - Reloaded"
filename = "bwncr-neoforge-1.21.1-3.20.3.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/lL2MtE37/versions/Vg54emzq/bwncr-neoforge-1.21.1-3.20.3.jar"
hash-format = "sha512"
hash = "db8419504e6ede1553b2e2d060fe4937c53898154c66475b726381155233a55daa0f52a530562dfc84202692e52614f8c585e89b1f8621f6bf799ce6d97c91ab"
[update]
[update.modrinth]
mod-id = "lL2MtE37"
version = "Vg54emzq"

View file

@ -1,13 +0,0 @@
name = "BadOptimizations"
filename = "BadOptimizations-2.3.0-1.21.1.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/g96Z4WVZ/versions/AQrpXs3f/BadOptimizations-2.3.0-1.21.1.jar"
hash-format = "sha512"
hash = "a9d78ab8d84d0ba3ebfd77eb3c4f585b9c3f0cad8e59b0ecef8c3ba107067342d7549f275c8fe45a5d4f35f7d848a15c184ad6e369044e659d17ce08afe91a77"
[update]
[update.modrinth]
mod-id = "g96Z4WVZ"
version = "AQrpXs3f"

View file

@ -1,13 +0,0 @@
name = "Balm"
filename = "balm-neoforge-1.21.1-21.0.49.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/MBAkmtvl/versions/EBGKvOX4/balm-neoforge-1.21.1-21.0.49.jar"
hash-format = "sha512"
hash = "d2b24857515d035b971ec2617d89b490c750bf0763f42c0eb2bc2b3172abc52dd16309bc460a55e4c57023843c5027b91913dcefcddf72cce17393b18537cf5c"
[update]
[update.modrinth]
mod-id = "MBAkmtvl"
version = "EBGKvOX4"

View file

@ -1,13 +0,0 @@
name = "Barbeque's Delight [Forge/NeoForge]"
filename = "barbequesdelight-1.2.2.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/rtu7uERF/versions/eS5ZBlEc/barbequesdelight-1.2.2.jar"
hash-format = "sha512"
hash = "5d0dfcf4892526043b0098ffff57beed2a21f06b7705ee95c470ec41f7f74bdde9111838675af2b51d4531d54cda54378fdb3fe654aef87ec705478555e13171"
[update]
[update.modrinth]
mod-id = "rtu7uERF"
version = "eS5ZBlEc"

View file

@ -1,13 +0,0 @@
name = "Berry Good"
filename = "berry_good-1.21.1-8.0.0.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/2WZWaKCl/versions/dJvvZvQk/berry_good-1.21.1-8.0.0.jar"
hash-format = "sha512"
hash = "0fea9a289d39e0d722a323d5777bede66d4850655377c923cd4cae2f864b0e994fc87b0e75df7bd50683f40f08f509a77f3107c9956169326a0c184d7f092970"
[update]
[update.modrinth]
mod-id = "2WZWaKCl"
version = "dJvvZvQk"

View file

@ -1,13 +0,0 @@
name = "Better Advancements"
filename = "BetterAdvancements-NeoForge-1.21.1-0.4.3.21.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/Q2OqKxDG/versions/FjTYILOi/BetterAdvancements-NeoForge-1.21.1-0.4.3.21.jar"
hash-format = "sha512"
hash = "b3deafd146dc3ea6e879fbc92f00de5797319a1aa620fb3b39be64c19271dab2efa07f824cda7139f8c22f2524ea413949357f5469bb6de584d1386de069c6b7"
[update]
[update.modrinth]
mod-id = "Q2OqKxDG"
version = "FjTYILOi"

View file

@ -1,13 +0,0 @@
name = "Better Animations Collection"
filename = "BetterAnimationsCollection-v21.1.0-1.21.1-NeoForge.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/OoOVj3J3/versions/tlFY9zBb/BetterAnimationsCollection-v21.1.0-1.21.1-NeoForge.jar"
hash-format = "sha512"
hash = "bd99f6040b076ff878e4aae3832e6c494fbaba5d0cb0b26d80c1874c7bd59c11e725705aaeb777d9f1bb821b516e15866600c665a4fba1eafa7a75cbf66307bf"
[update]
[update.modrinth]
mod-id = "OoOVj3J3"
version = "tlFY9zBb"

View file

@ -1,13 +0,0 @@
name = "Better Third Person"
filename = "BetterThirdPerson-neoforge-1.9.0.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/G1s2WpNo/versions/aG5y4JUQ/BetterThirdPerson-neoforge-1.9.0.jar"
hash-format = "sha512"
hash = "053acb813940aac69578810558056942966f2f0a24006a3a5de545c82888f8190b3bdc27a2401d0f70200dc4c927c0f84a45bf17821fb94fb9b886d633644619"
[update]
[update.modrinth]
mod-id = "G1s2WpNo"
version = "aG5y4JUQ"

View file

@ -1,13 +0,0 @@
name = "BetterF3"
filename = "BetterF3-11.0.3-NeoForge-1.21.1.jar"
side = "client"
[download]
url = "https://cdn.modrinth.com/data/8shC1gFX/versions/maXNB1dn/BetterF3-11.0.3-NeoForge-1.21.1.jar"
hash-format = "sha512"
hash = "59c36a882669399dce2110db42df05e8fe935a2e1194c4bac49abffc3129b2e19373dfe3c4a3d2f8b22f21b1d66ad8cbd653944ce0a71ae05d3d65528d1b7514"
[update]
[update.modrinth]
mod-id = "8shC1gFX"
version = "maXNB1dn"

View file

@ -1,13 +0,0 @@
name = "Blueprint"
filename = "blueprint-1.21.1-8.0.5.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/VsM5EDoI/versions/l2AucG5l/blueprint-1.21.1-8.0.5.jar"
hash-format = "sha512"
hash = "6a0801d078b233e4e909c9f9644236def1b29dd135bdf64a98c2688edebc0eef266a7b419911035726c565eee616090047e59601ece77033e38108a3e1bfd525"
[update]
[update.modrinth]
mod-id = "VsM5EDoI"
version = "l2AucG5l"

View file

@ -1,13 +0,0 @@
name = "Bookshelf"
filename = "bookshelf-neoforge-1.21.1-21.1.67.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/uy4Cnpcm/versions/9LiRySL2/bookshelf-neoforge-1.21.1-21.1.67.jar"
hash-format = "sha512"
hash = "98225af4ba0a8e10e30219b9a2bef39d0aa49425f5f09b6b2c7f627992458e39e30b686d03a8b8c4563eae44505ce94a7d21363700d77b21c1c4771409c34c63"
[update]
[update.modrinth]
mod-id = "uy4Cnpcm"
version = "9LiRySL2"

View file

@ -1,13 +0,0 @@
name = "Can't Sleep Clowns Will Eat Me"
filename = "cant_sleep_clowns_will_eat_me-1.21-2.0.0.0.jar"
side = "both"
[download]
hash-format = "sha1"
hash = "da6f8415bb3be1f1855b1bb2c617db27683d3dab"
mode = "metadata:curseforge"
[update]
[update.curseforge]
file-id = 5530236
project-id = 430957

View file

@ -1,13 +0,0 @@
name = "Cloth Config API"
filename = "cloth-config-15.0.140-neoforge.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/9s6osm5g/versions/izKINKFg/cloth-config-15.0.140-neoforge.jar"
hash-format = "sha512"
hash = "aaf9b010955b8cd294e5a92f069985b18729fd5e2cf22d351f1dff9680f15488688803ec41e77e941cbde130ceb535014ca4c868047d80ab69c2d508e216654d"
[update]
[update.modrinth]
mod-id = "9s6osm5g"
version = "izKINKFg"

View file

@ -1,13 +0,0 @@
name = "Clumps"
filename = "Clumps-neoforge-1.21.1-19.0.0.1.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/Wnxd13zP/versions/jo7lDoK4/Clumps-neoforge-1.21.1-19.0.0.1.jar"
hash-format = "sha512"
hash = "314d8d8e640d73041f27e0f3f2cad7aad8b4c77dbd7fb31700ef7760362261f77085eed5289555c725d99c3f47a114e7290cd608f39c9f0f12ef74958463bdcc"
[update]
[update.modrinth]
mod-id = "Wnxd13zP"
version = "jo7lDoK4"

View file

@ -1,13 +0,0 @@
name = "Cobblemon additions"
filename = "BCA-Datapack-3.8_CE_norm_M1.21.1_C1.6.1.zip"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/W2pr9jyL/versions/J1wqmyOI/BCA-Datapack-3.8_CE_norm_M1.21.1_C1.6.1.zip"
hash-format = "sha512"
hash = "65d97aeb98572b264badf9c19b7994853472910dc2b9da2c962525e6f635deae812b9bf7a7e1886fcdf5e72764271fa0543d9066bb43f71a60028507e6e4e35c"
[update]
[update.modrinth]
mod-id = "W2pr9jyL"
version = "J1wqmyOI"

View file

@ -1,13 +0,0 @@
name = "Cobblemon Counter"
filename = "cobblemon-counter-1.6-neoforge-1.5.1.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/rj8uLYP4/versions/4NXvB2jm/cobblemon-counter-1.6-neoforge-1.5.1.jar"
hash-format = "sha512"
hash = "ff49a9e9939ef3c84e941c4070ca923dfc439f5ffed0aad3530db5fc5b70b1c4be5751310cc61e7d61853e25ff2c8a039de5e5cf38827e24aadfd659e0d1b511"
[update]
[update.modrinth]
mod-id = "rj8uLYP4"
version = "4NXvB2jm"

View file

@ -1,13 +0,0 @@
name = "Cobblemon Fight or Flight Reborn"
filename = "fightorflight-neoforge-0.8.2.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/cTdIg5HZ/versions/9JSwVvPR/fightorflight-neoforge-0.8.2.jar"
hash-format = "sha512"
hash = "3024acdc80eb3e07a914d46ecfc85c9addf2775c6469a5f4d88922d9d0f2fe6599aa8b6c4137bad10bc38dc88c0351eb5d7173bdcbf29a5b238610a3fbf2f80c"
[update]
[update.modrinth]
mod-id = "cTdIg5HZ"
version = "9JSwVvPR"

View file

@ -1,13 +0,0 @@
name = "Cobblemon Integrations"
filename = "cobblemonintegrations-neoforge-1.21.1-1.1.3.jar"
side = "both"
[download]
url = "https://cdn.modrinth.com/data/NPCfuUI4/versions/slzdgomG/cobblemonintegrations-neoforge-1.21.1-1.1.3.jar"
hash-format = "sha512"
hash = "74a30fdd644638d2fc95bccb8b4c20cb6e5dd33309ccca8c155e30e8e77e8bf3670f6a79a86c4f9404b04bc3ce7b78428cf69a3fb15ba46e4a71a4ddd2f1f471"
[update]
[update.modrinth]
mod-id = "NPCfuUI4"
version = "slzdgomG"

Some files were not shown because too many files have changed in this diff Show more