add zammad to fw vm, add web-arm machine
This commit is contained in:
281
hosts/web-arm/modules/authelia/default.nix
Normal file
281
hosts/web-arm/modules/authelia/default.nix
Normal file
@@ -0,0 +1,281 @@
|
||||
{ config, ... }:
|
||||
|
||||
{
|
||||
sops.secrets.authelia-jwt-secret = {
|
||||
owner = "authelia-main";
|
||||
sopsFile = ./secrets.yaml;
|
||||
};
|
||||
sops.secrets.authelia-backend-ldap-password = {
|
||||
owner = "authelia-main";
|
||||
sopsFile = ./secrets.yaml;
|
||||
};
|
||||
sops.secrets.authelia-storage-encryption-key = {
|
||||
owner = "authelia-main";
|
||||
sopsFile = ./secrets.yaml;
|
||||
};
|
||||
sops.secrets.authelia-session-secret = {
|
||||
owner = "authelia-main";
|
||||
sopsFile = ./secrets.yaml;
|
||||
};
|
||||
sops.secrets.authelia-identity-providers-oidc-hmac-secret = {
|
||||
owner = "authelia-main";
|
||||
sopsFile = ./secrets.yaml;
|
||||
};
|
||||
sops.secrets.authelia-identity-providers-oidc-issuer-certificate-chain = {
|
||||
owner = "authelia-main";
|
||||
sopsFile = ./secrets.yaml;
|
||||
};
|
||||
sops.secrets.authelia-identity-providers-oidc-issuer-private-key = {
|
||||
owner = "authelia-main";
|
||||
sopsFile = ./secrets.yaml;
|
||||
};
|
||||
|
||||
services.authelia.instances.main = {
|
||||
enable = true;
|
||||
secrets = {
|
||||
jwtSecretFile = config.sops.secrets.authelia-jwt-secret.path;
|
||||
storageEncryptionKeyFile = config.sops.secrets.authelia-storage-encryption-key.path;
|
||||
sessionSecretFile = config.sops.secrets.authelia-session-secret.path;
|
||||
oidcHmacSecretFile = config.sops.secrets.authelia-identity-providers-oidc-hmac-secret.path;
|
||||
oidcIssuerPrivateKeyFile = config.sops.secrets.authelia-identity-providers-oidc-issuer-private-key.path;
|
||||
};
|
||||
environmentVariables = {
|
||||
"AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE" = config.sops.secrets.authelia-backend-ldap-password.path;
|
||||
"AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE" = config.sops.secrets.authelia-backend-ldap-password.path;
|
||||
};
|
||||
settings = {
|
||||
theme = "dark";
|
||||
default_redirection_url = "https://cloonar.com";
|
||||
|
||||
server = {
|
||||
host = "127.0.0.1";
|
||||
port = 9091;
|
||||
};
|
||||
|
||||
# log = {
|
||||
# level = "debug";
|
||||
# format = "text";
|
||||
# };
|
||||
|
||||
authentication_backend = {
|
||||
ldap = {
|
||||
url = "ldaps://ldap.cloonar.com";
|
||||
base_dn = "DC=cloonar,DC=com";
|
||||
additional_users_dn = "OU=users";
|
||||
users_filter = "(&({username_attribute}={input})(objectClass=person))";
|
||||
username_attribute = "mail";
|
||||
mail_attribute = "mail";
|
||||
display_name_attribute = "cn";
|
||||
additional_groups_dn = "OU=groups";
|
||||
groups_filter = "(&(member={dn})(objectClass=groupOfNames))";
|
||||
group_name_attribute = "cn";
|
||||
permit_referrals = false;
|
||||
permit_unauthenticated_bind = false;
|
||||
user = "cn=authelia,ou=system,ou=users,dc=cloonar,dc=com";
|
||||
};
|
||||
};
|
||||
|
||||
webauthn = {
|
||||
disable = false;
|
||||
display_name = "Authelia";
|
||||
attestation_conveyance_preference = "indirect";
|
||||
user_verification = "preferred";
|
||||
timeout = "60s";
|
||||
};
|
||||
|
||||
totp = {
|
||||
disable = false;
|
||||
issuer = "auth.cloonar.com";
|
||||
algorithm = "sha1";
|
||||
digits = 6;
|
||||
period = 30;
|
||||
skew = 1;
|
||||
secret_size = 32;
|
||||
};
|
||||
|
||||
access_control = {
|
||||
default_policy = "deny";
|
||||
rules = [
|
||||
{
|
||||
domain = ["auth.cloonar.com"];
|
||||
policy = "bypass";
|
||||
}
|
||||
{
|
||||
domain = ["*.cloonar.com"];
|
||||
policy = "two_factor";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
session = {
|
||||
name = "authelia_session";
|
||||
expiration = "12h";
|
||||
inactivity = "45m";
|
||||
remember_me_duration = "1M";
|
||||
domain = "cloonar.com";
|
||||
# todo: enable with 4.38
|
||||
# cookies = [
|
||||
# {
|
||||
# domain = "cloonar.com";
|
||||
# }
|
||||
# {
|
||||
# domain = "cloonar.dev";
|
||||
# }
|
||||
# {
|
||||
# domain = "gbv-aktuell.at";
|
||||
# same_site = "strict";
|
||||
# }
|
||||
# ];
|
||||
};
|
||||
|
||||
regulation = {
|
||||
max_retries = 3;
|
||||
find_time = "5m";
|
||||
ban_time = "15m";
|
||||
};
|
||||
|
||||
storage = {
|
||||
# mysql = {
|
||||
# host = "/run/mysqld/mysqld.sock'";
|
||||
# port = 3306;
|
||||
# database = "authelia_main";
|
||||
# username = "authelia_main";
|
||||
# password = "socket_auth";
|
||||
# timeout = "5s";
|
||||
# };
|
||||
local = {
|
||||
path = "/var/lib/authelia-main/db.sqlite3";
|
||||
};
|
||||
};
|
||||
|
||||
notifier = {
|
||||
disable_startup_check = false;
|
||||
# filesystem = {
|
||||
# filename = "/var/lib/authelia-main/notification.txt";
|
||||
# };
|
||||
smtp = {
|
||||
host = "mail.cloonar.com";
|
||||
port = 25;
|
||||
username = "authelia@cloonar.com";
|
||||
sender = "Authelia <authelia@cloonar.com>";
|
||||
};
|
||||
};
|
||||
identity_providers = {
|
||||
oidc = {
|
||||
## The other portions of the mandatory OpenID Connect 1.0 configuration go here.
|
||||
## See: https://www.authelia.com/c/oidc
|
||||
clients = [
|
||||
{
|
||||
id = "gitea";
|
||||
description = "Gitea";
|
||||
secret = "$pbkdf2-sha512$310000$ngFGgCoDClB0xPLxxMJ.Qw$hFuXXizjiC73gZtwi2bPBHzpX8/1GmR8ux1aAz9esVhPEgB58d/vB2jLFKyc13mFJx7qc0ErIdla4/K0CsvM.A";
|
||||
public = false;
|
||||
authorization_policy = "one_factor";
|
||||
redirect_uris = [ "https://git.cloonar.com/user/oauth2/authelia/callback" ];
|
||||
pre_configured_consent_duration = "1y";
|
||||
scopes = [
|
||||
"openid"
|
||||
"profile"
|
||||
"email"
|
||||
];
|
||||
userinfo_signing_algorithm = "none";
|
||||
}
|
||||
{
|
||||
id = "nextcloud";
|
||||
description = "Nextcloud";
|
||||
secret = "$pbkdf2-sha512$310000$UqX35Fh.7uTZLQqD.mk5wg$e139D4g9SGUFc.ZdKt3RAZljC8A7C9nixUQd7rQoHFMKop643SuwfazjNn0ehdyAjydM2zV.KzKnMLgSajo.xw";
|
||||
public = false;
|
||||
authorization_policy = "one_factor";
|
||||
redirect_uris = [
|
||||
"https://nextcloud.cloonar.com/apps/oidc_login/oidc"
|
||||
"https://cloud.cloonar.com/apps/user_oidc/code"
|
||||
];
|
||||
pre_configured_consent_duration = "1y";
|
||||
scopes = [
|
||||
"openid"
|
||||
"profile"
|
||||
"email"
|
||||
"groups"
|
||||
];
|
||||
userinfo_signing_algorithm = "none";
|
||||
}
|
||||
{
|
||||
id = "grafana";
|
||||
description = "Grafana";
|
||||
secret = "$pbkdf2-sha512$310000$TP7.qfcevrHJFGcIMdZgGw$mLQ.AC5M28ETouxyiCeRkenQuKPvH0.oF1exp6LXBpleV56PI6sWrwmBgD7sMsHrMbkvCX4lNPx0vMf0urVpYA";
|
||||
public = false;
|
||||
authorization_policy = "one_factor";
|
||||
redirect_uris = [ "https://grafana.cloonar.com/login/generic_oauth" ];
|
||||
pre_configured_consent_duration = "1y";
|
||||
scopes = [
|
||||
"openid"
|
||||
"profile"
|
||||
"email"
|
||||
"groups"
|
||||
];
|
||||
userinfo_signing_algorithm = "none";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
services.nginx.virtualHosts."auth.cloonar.com" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
acmeRoot = null;
|
||||
|
||||
locations."/api/verify" = {
|
||||
proxyPass = "http://127.0.0.1:9091";
|
||||
proxyWebsockets = true;
|
||||
|
||||
extraConfig = ''
|
||||
allow 127.0.0.1;
|
||||
allow 49.12.244.139;
|
||||
allow 77.119.230.30;
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:9091";
|
||||
proxyWebsockets = true;
|
||||
|
||||
extraConfig = ''
|
||||
client_body_buffer_size 128k;
|
||||
|
||||
#Timeout if the real server is dead
|
||||
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
|
||||
|
||||
# Advanced Proxy Config
|
||||
send_timeout 5m;
|
||||
proxy_read_timeout 360;
|
||||
proxy_send_timeout 360;
|
||||
proxy_connect_timeout 360;
|
||||
|
||||
# Basic Proxy Config
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Uri $request_uri;
|
||||
proxy_set_header X-Forwarded-Ssl on;
|
||||
proxy_redirect http:// $scheme://;
|
||||
proxy_set_header Connection "";
|
||||
proxy_cache_bypass $cookie_session;
|
||||
proxy_no_cache $cookie_session;
|
||||
proxy_buffers 64 256k;
|
||||
|
||||
# If behind reverse proxy, forwards the correct IP
|
||||
set_real_ip_from 10.0.0.0/8;
|
||||
set_real_ip_from 172.0.0.0/8;
|
||||
set_real_ip_from 192.168.0.0/16;
|
||||
set_real_ip_from fc00::/7;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
45
hosts/web-arm/modules/authelia/secrets.yaml
Normal file
45
hosts/web-arm/modules/authelia/secrets.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
authelia-jwt-secret: ENC[AES256_GCM,data:+4mCRAbPYeuxZwPxIWdzym9M0soVRJGZOHpBLFp1dsienOes6PcF6DhkzLwx1g/2KYQBrWq5QtNyysLkl32mNg==,iv:3354Ww7D1fQAVZh8xlJo3W9VaLTC6sUxXpNzwFYGZPg=,tag:NjPuHi4R+I3CJ09ZbV1Cbw==,type:str]
|
||||
authelia-backend-ldap-password: ENC[AES256_GCM,data:AJ5/lQxxQ0PjPpja4Lm7Qbn4rrZ/fapFeTO9nXsXpYC7cSgPDmGL4LG6QTFrgHpJU4FGEyFhWUYf/BZvHFLA2A==,iv:/w3SlYC74vSV/hkOdp2wb50beSTaokQC9C1ogs82nxo=,tag:b5M78WOUgHcydoJTKiAAOQ==,type:str]
|
||||
authelia-storage-encryption-key: ENC[AES256_GCM,data:I3ek+p0faJUUjS3ULeeLzsrsl03MKlHwrC+R3IqrJ2P9AbJmMBvvXnqLx2H2THkjGiqN3kLgrhnmInn+BnCgYg==,iv:EiZpXbkyC3tbdzcp20hV6ctAJdB9tlgxT3gI7wiqSZc=,tag:qqG02RJAizr2jlGV0JnStA==,type:str]
|
||||
authelia-session-secret: ENC[AES256_GCM,data:+hljRSv4nABWg+vEOhYM27h9Gu1FCqcWWa51VqlN1r8AE79S78Uq2txWL7bZKql/fxmaguTLwk18xkHIAvIEsA==,iv:RoytV5jWIUDq6olp8rWAc0NRC4f1FLL43EpTzcXZ3eg=,tag:vIvDVRSqlVt/W/52vuDDZA==,type:str]
|
||||
authelia-identity-providers-oidc-hmac-secret: ENC[AES256_GCM,data:yyqauvp+/8ufhCaZ1o0DWn4Nx1rdTW8C1HRVAtyCRuBaQA/yFVmZkwFVbnIDC3TrmuEMc2MXzVCREbdDsEqkGm6LJAB4Eq31NyhhbAtKufeqKHhMgEF4d41K71V//FJn2/ZBY6CaR1Ke0rX3p/Rpwk0rwddikkUmdJ7i7w9ayP8=,iv:ONBU0uWEUeQxQCGmHtGOySuLmTnJlAx//lQcK32i1Gs=,tag:Tk2BbYZSqbJRc/2cj8yxHQ==,type:str]
|
||||
authelia-identity-providers-oidc-issuer-certificate-chain: ENC[AES256_GCM,data:oQwBKE0VjTIKYWOGKFtLwkOkjTh16gf5lJvMEEVs3Sy/+gmyGGmnDHm+xv9aT7Mmq9wSM7SVBe39yT5K9bUd0vGXO2Ze5V55B+B+9bAPKUL4rPNQAeSy3QCJPh6EoG3urDD/HUklV8QCprgTlokdgVgY3fv3be2Y1oOdiZDvbacol6OlcRXSi8ZqMro+f15e44j8NGhzsSahhzOLtmiRGLr5zWnzk8b221HZWtjSdG4rLrtcCZ1UjvvUX5pf8J5PI/9X4S6J7pglG+IlI0WGSHvQ9BXGQqWgmWky/3+hnC/B3ZPm3bz5CqMHzsdx/QmiCtQQf08GOoan/3rgp3pAu5J5TPDldnzEQkWPjciOMp4ewlu4nC1AViat7DH8wFtV9IpixEZm3fMidpPBpkTTRZMCy6AstNlPMvvvRDN/6nJypN++gvkBw3OJac2xBdtbdF5uC9nIrZqWENLnOn4623/C8yJJ8a2l1W1FF95hHiZDQKua+kB4CfFJSFxhtcWj3vcCzv7QIGHZPTIVn+aCozb4CdOegLswCuY9g5ncHfOnqIhSCY3Bc2xbd5GO7kVRvqT79abwHsAdArdDJAE4Fq3mNJG9/fy0N31GW4qKMTb3W5EgEt/2OtfsUn8MwHJV9BGPMeZhpn9hdzkXo9vmakVMKNoK4SEgZmFiKCj/uwhwdvJfYMRvl/n1DSpy8mxzKWt1IO9FD5HRUhkKeas6spOSyzbi4FTJJmJb9NQ5gzAcfTXs8C49S/DSocRwUHvQMvRIRZzBejxFKdnwGxwIJiVDY/04FWAjMR4HgxkCBvo9x+CxajnCw/S9g02uY85vxW1ZURi9wUK9Q9nbEyMu1IGWadhVO6fKvqWr9rVZ6tqqJ7FP81LKca80nkY+6Elec6l6u01Lzb4pLA6MFLyJbCE97+Vmoh056N73RNapWP6G3Txs2CvtzqWdup0J4xpwAxoEqVlnkBQ61abucZH9veMoq4gvxM90S+bBX7c6A+FYRha/PXovRL/SZWEfuKlVDeLQyb2IwQ==,iv:jhnNkcLXN3pHx6S8g78+R6X+ckhOF35QK615zcH2gqI=,tag:JSHDo9nbBbhpiQFSrLuDdg==,type:str]
|
||||
authelia-identity-providers-oidc-issuer-private-key: ENC[AES256_GCM,data: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,iv:PWdVLhu0BPx7sXMzow9wl+cqDXD2Y5J5lfVSX3tNCMg=,tag:P4vHogedMdAUeIh4XHlmdw==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHWkRuWXdaQ1RUbkF1d2p0
|
||||
elZkbnFVSW9tVjdqSHFvbjFiL202cW1tWjJ3ClpDUEFIMDFteFA1QTdTVmtVWHI0
|
||||
OFRuU1Fockh4aTBwa3l3ZjdiMFFYSm8KLS0tIGdCZjZNVXNVZWV3ZlJzY3ZyZXhr
|
||||
WFp1eVZna1VWUUZuTVY4Q2h2c0Y2ZDAKcglSV3UBoZ65+SsM+zRFJmjIH61jXbT0
|
||||
rpeJ8/0i4THmVpbZY+NOIh2zECmzBkAA06jv0jMoftL40h2wsdgncg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBna282T2hYcDl4UWFISDVL
|
||||
eE42MjVxZndUVEU5bjJwUzdHU2xHNXVNRW13CmZwUmdCWDFNVmdDbktwOXBIbzNZ
|
||||
eGgrZHQwMEdRSG11aWpoSllrcjBBY2cKLS0tIFBZRUdYVUhsbFZYV0w5T3RYc0Ez
|
||||
RDJZcjA4VFNadEZCUmpOVWRBdGNKMzQKhhQCbeRxDvhFVsF3G+OoXo4i+koqqgrV
|
||||
o/esYoxA1ZNsS9mhFbfMw1C2YO43iPtaWChAO5zUABDALD6dJ1Rf1A==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5ZUJuMnNwTGpSdVA4UXV5
|
||||
bkdGTWJsRjliMGJWcXBKekc3WDZiN0FWV0MwCmZIVld4M0xaWWhmUDVqSGcwbGpz
|
||||
S0kzQy9scDRObS82WkMzYUw2dVBaWXMKLS0tIGpkeFZqdXIrY0lFdUgwekNJeDN4
|
||||
eFhnWGdoTzdyZmtjZDJBc3FveTRaN0EKBj2hSr6qDxwW+k5hox47P5uyoHQAzCjH
|
||||
+TplhMUd5p8/ud3U4lixLezGu1qftVSKtz/4SAXrSC5DYZJF1w7tDQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2023-08-17T01:43:14Z"
|
||||
mac: ENC[AES256_GCM,data:zcCKk+VAddbb4vZltdC6hKPAnoo4rvcLcmIsKATQekbVo9OUk5Q5JnxglgAxXyj/YMZ7tIY/IXoWdSW4Kw673vthVnWpGLnuHtXJFGslkQ+GEkIt0z/oepr33gXErsEolZ3rIx02CVsIK5tb38ol0DhAe+6dUihsi23HruMJNog=,iv:2RVGRBTgqR9YLrRpoxuN72NOcXvRlZVTaPNiU7l75w0=,tag:lr4/sBBE9F27II289OWUNQ==,type:str]
|
||||
pgp: []
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.7.3
|
||||
114
hosts/web-arm/modules/bitwarden/default.nix
Normal file
114
hosts/web-arm/modules/bitwarden/default.nix
Normal file
@@ -0,0 +1,114 @@
|
||||
{
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
ldapConfig = {
|
||||
vaultwarden_url = "https://bitwarden.cloonar.com";
|
||||
vaultwarden_admin_token = "@ADMIN_TOKEN@";
|
||||
ldap_host = "ldap.cloonar.com";
|
||||
ldap_ssl = true;
|
||||
ldap_bind_dn = "cn=bitwarden,ou=system,ou=users,dc=cloonar,dc=com";
|
||||
ldap_bind_password = "@LDAP_PASSWORD@";
|
||||
ldap_search_base_dn = "ou=users,dc=cloonar,dc=com";
|
||||
ldap_search_filter = "(&(objectClass=cloonarUser))";
|
||||
ldap_sync_interval_seconds = 3600;
|
||||
};
|
||||
|
||||
ldapConfigFile =
|
||||
pkgs.runCommand "config.toml"
|
||||
{
|
||||
buildInputs = [pkgs.remarshal];
|
||||
preferLocalBuild = true;
|
||||
} ''
|
||||
remarshal -if json -of toml \
|
||||
< ${pkgs.writeText "config.json" (builtins.toJSON ldapConfig)} \
|
||||
> $out
|
||||
'';
|
||||
in {
|
||||
imports = [
|
||||
../../utils/modules/nur.nix
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
nur.repos.mic92.vaultwarden_ldap
|
||||
];
|
||||
|
||||
services.vaultwarden = {
|
||||
enable = true;
|
||||
dbBackend = "mysql";
|
||||
config = {
|
||||
domain = "https://bitwarden.cloonar.com";
|
||||
signupsAllowed = false;
|
||||
rocketPort = 3011;
|
||||
enableDbWal = "false";
|
||||
websocketEnabled = true;
|
||||
smtpHost = "mail.cloonar.com";
|
||||
smtpFrom = "bitwarden@cloonar.com";
|
||||
smtpUsername = "bitwarden@cloonar.com";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.vaultwarden.serviceConfig = {
|
||||
EnvironmentFile = [config.sops.secrets.bitwarden-smtp-password.path];
|
||||
};
|
||||
|
||||
systemd.services.vaultwarden_ldap = {
|
||||
wantedBy = ["multi-user.target"];
|
||||
|
||||
preStart = ''
|
||||
sed \
|
||||
-e "s=@LDAP_PASSWORD@=$(<${config.sops.secrets.bitwarden-ldap-password.path})=" \
|
||||
-e "s=@ADMIN_TOKEN@=$(<${config.sops.secrets.bitwarden-admin-token.path})=" \
|
||||
${ldapConfigFile} \
|
||||
> /run/vaultwarden_ldap/config.toml
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "2s";
|
||||
ExecStart = "${pkgs.nur.repos.mic92.vaultwarden_ldap}/bin/vaultwarden_ldap";
|
||||
Environment = "CONFIG_PATH=/run/vaultwarden_ldap/config.toml";
|
||||
|
||||
RuntimeDirectory = ["vaultwarden_ldap"];
|
||||
User = "vaultwarden_ldap";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."bitwarden.cloonar.com" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
extraConfig = ''
|
||||
client_max_body_size 128M;
|
||||
'';
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:3011";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
locations."/notifications/hub" = {
|
||||
proxyPass = "http://localhost:3012";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
locations."/notifications/hub/negotiate" = {
|
||||
proxyPass = "http://localhost:3011";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets = {
|
||||
bitwarden-admin-token.owner = "vaultwarden_ldap";
|
||||
bitwarden-ldap-password.owner = "vaultwarden_ldap";
|
||||
bitwarden-db-password.owner = "vaultwarden";
|
||||
bitwarden-smtp-password.owner = "vaultwarden";
|
||||
};
|
||||
|
||||
users.users.vaultwarden_ldap = {
|
||||
isSystemUser = true;
|
||||
group = "vaultwarden_ldap";
|
||||
};
|
||||
|
||||
users.groups.vaultwarden_ldap = {};
|
||||
|
||||
services.mysqlBackup.databases = [ "bitwarden" ];
|
||||
}
|
||||
42
hosts/web-arm/modules/bitwarden/secrets.yaml
Normal file
42
hosts/web-arm/modules/bitwarden/secrets.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
bitwarden-admin-token: ENC[AES256_GCM,data:nCj7kwQHTwezG3hh5J+c2MmUXwlGpdNjeh4A4SK/wgdBroAAghMSTuT6B7sjPgX5PmyBpzspdI3XqVUoBHzL6g==,iv:11C/ScaTqI1VlBSd71TA2cZNAu/wSbOs6rnDTlKlPsI=,tag:8eD0VkJn/KZ49yMe4D/MrA==,type:str]
|
||||
bitwarden-db-password: ENC[AES256_GCM,data:4l3ntOHX4pdiUzfSqOwzObgMRp9eS5fjze6rJu1h3kKr/g/lsESLWiIHUoguixaNmoPU2zy42jEDvhXII6R+1g==,iv:mEMGGGyWerJaAvo7ymNfkR1YgTG1ieB3n40BB6L+UM4=,tag:iRd88BjFMMht9Ku9K34SXQ==,type:str]
|
||||
bitwarden-ldap-password: ENC[AES256_GCM,data:g6tp0NzXk3ZJTGKHSzFxVZs4DhauzPS6SGW99WFX/CO0Wprgp9lh/evI6T56g2YhIv/3jqNSmi+p1FwdOzValw==,iv:mHMlhJx2aKLLkrPy+Z+/6plS/uMiK+xhYk/PF5m7+wQ=,tag:BgRNstiVnN95/pSX0DYfSw==,type:str]
|
||||
bitwarden-smtp-password: ENC[AES256_GCM,data:4ruP8yMeTG5A19Oyvv2MBTj2LwecwwYc8BBU1xDT2i757orCNrQHJd0VLtzynluS9ge4vAU7G8islKwR/IIDGsEq74//CxJIyXyH9XLBfc5Jb2Rs1uz/Nz2uCWOCqm1AZ2/8uxXOPPNVhKcs3wxOLbLnA3Yzh+VFKsKIO753FkKllpFbeZanhfD2/N4fAGU4C5F+0HcrLBLBGC3X/CfQyPUSio1uwWPxRJR94DlRdPq+ir4YXHW48Mw/33lJZ+HqApk1Nf+gmTff7XTib1d44ac4JR8m20D8qOQ2Y9vfqJOxD7/PdgeqRLXN3K1PaSDE7JkWoiE0dM3vJ0q+Pqf47tm/xT4qaJvqI0jLXMwqmUg=,iv:TiZrLMPx9UbUf/4zKmRWTERM8phtyTX7Q3dCFqn+Ew4=,tag:55tuxMBWu6WpT4BllKV+pA==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTVzQvK0VkUzh2MDhzSm5Z
|
||||
TWlYVHNQQk9sbTkxT2JtUVFTQ01xam1FSFJBCjh3QUN1VGhCakJlR3QrZCtkdWpk
|
||||
RGtGbEM0c2xUTlJiWktrczA0eVlFMm8KLS0tIFNnM0JpcHNrdFBadkpLZTZaY3VQ
|
||||
ckYzWldIN01TZ3dKYmhIU1ZqK3NGWE0KvVTpNRg7RN0jKBDEDf0U+52I17+A3Gkl
|
||||
1VGxCmO87cBPcxmfnxoAdpabqCV9l784YHkQsW3Z0gicr0392m78Rw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSdURKWGg1dFk2MEFzVS9q
|
||||
NkNReXU3RkNHaUUvZ0RMTXNVbkI5bDBwbHdzCjY2Rm1PMitteVBZQW1xMGxYMlFH
|
||||
djJLSGtFUElsaTBETk5EZzgzMGh2TmMKLS0tIENJUUlWTmhMT1dlVWRpdmYwQnFi
|
||||
cW02R1F0M2djcExEeVRUalp4cnRzY28KoFN3BS4C/xqoHeD3Is0AfRJlWRJQ/i5z
|
||||
rFV9USYsD23M+tdirbVgCfaSBl5RZXB4SpNFiG3QjhmQ04JuIxuHQg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0b1pReWNGenpEZ1RtVkZz
|
||||
dGIrQ1NYdzdlNTNacXFkNkY4eUVSUzJ4NjNnCmYxdlFYRm9VYlRnRS9GU28xSita
|
||||
cVNadTBBNmF0TjkwZnhPdHVvUWVhdXMKLS0tIGJ0MS9qOXJhVEtoSUd2TWtCUmFq
|
||||
dGxUQ1RmVkhXZDVRMGx5dUFDZUlTMkEKHwwCPamlcJoiJGIOVtLdcftMm3D5DgN/
|
||||
yijIfsBySzUfU1dfFp6GMpazL+81L4+8AEp3ZW7z2BBwwE7tm1yVzg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2023-01-28T21:53:06Z"
|
||||
mac: ENC[AES256_GCM,data:jZq4UzkxyX/UhrmeKO7sFQpTlMB13lyi5/duXA0s2XX3W0U9g+TSZm21WiRGPjKmteJg0w2OhFsNk/y0uvD/oPE1ttLz/YRgiinuCoyufoX51AgQqS0KFxNBkTaDzoaKk3z1j8nEhAY2U0YS4fpOCNAkMsKdVZeTVOitcp/UeIE=,iv:5EzYCqUZri1VmD9wqQGxpypZe4F2h8W3D8a7mYbBBrg=,tag:iEFJBFmRJVw4YP5/V+21dQ==,type:str]
|
||||
pgp: []
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.7.3
|
||||
66
hosts/web-arm/modules/collabora.nix
Normal file
66
hosts/web-arm/modules/collabora.nix
Normal file
@@ -0,0 +1,66 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
#Collabora Containers
|
||||
virtualisation.oci-containers.containers.collabora = {
|
||||
image = "docker.io/collabora/code:latest";
|
||||
ports = [ "9980:9980/tcp" ];
|
||||
environment = {
|
||||
server_name = "code.cloonar.com";
|
||||
aliasgroup1 = "https://cloud.cloonar.com:443";
|
||||
dictionaries = "en_US";
|
||||
extra_params = "--o:ssl.enable=false --o:ssl.termination=true";
|
||||
};
|
||||
extraOptions = [
|
||||
"--pull=newer"
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts.${config.virtualisation.oci-containers.containers.collabora.environment.server_name} = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
extraConfig = ''
|
||||
# static files
|
||||
location ^~ /browser {
|
||||
proxy_pass http://127.0.0.1:9980;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# WOPI discovery URL
|
||||
location ^~ /hosting/discovery {
|
||||
proxy_pass http://127.0.0.1:9980;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# Capabilities
|
||||
location ^~ /hosting/capabilities {
|
||||
proxy_pass http://127.0.0.1:9980;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# main websocket
|
||||
location ~ ^/cool/(.*)/ws$ {
|
||||
proxy_pass http://127.0.0.1:9980;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 36000s;
|
||||
}
|
||||
|
||||
# download, presentation and image upload
|
||||
location ~ ^/(c|l)ool {
|
||||
proxy_pass http://127.0.0.1:9980;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
# Admin Console websocket
|
||||
location ^~ /cool/adminws {
|
||||
proxy_pass http://127.0.0.1:9980;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 36000s;
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
107
hosts/web-arm/modules/grafana.nix
Normal file
107
hosts/web-arm/modules/grafana.nix
Normal file
@@ -0,0 +1,107 @@
|
||||
{ lib, pkgs, config, ...}:
|
||||
let
|
||||
ldap = pkgs.writeTextFile {
|
||||
name = "ldap.toml";
|
||||
text = ''
|
||||
[[servers]]
|
||||
host = "ldap.cloonar.com"
|
||||
port = 636
|
||||
use_ssl = true
|
||||
bind_dn = "cn=grafana,ou=system,ou=users,dc=cloonar,dc=com"
|
||||
bind_password = "$__file{/run/secrets/grafana-ldap-password}"
|
||||
search_filter = "(&(objectClass=cloonarUser)(mail=%s))"
|
||||
search_base_dns = ["ou=users,dc=cloonar,dc=com"]
|
||||
|
||||
[servers.attributes]
|
||||
name = "givenName"
|
||||
surname = "sn"
|
||||
username = "uid"
|
||||
email = "mail"
|
||||
member_of = "memberOf"
|
||||
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=Administrators,ou=groups,dc=cloonar,dc=com"
|
||||
org_role = "Admin"
|
||||
grafana_admin = true # Available in Grafana v5.3 and above
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
systemd.services.grafana.script = lib.mkBefore "export GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=$(cat /run/secrets/grafana-oauth-secret)";
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings = {
|
||||
analytics.reporting_enabled = false;
|
||||
# "auth.ldap".enabled = true;
|
||||
# "auth.ldap".config_file = toString ldap;
|
||||
|
||||
"auth.generic_oauth" = {
|
||||
enabled = true;
|
||||
name = "Authelia";
|
||||
icon = "signin";
|
||||
client_id = "grafana";
|
||||
scopes = "openid profile email groups";
|
||||
empty_scopes = false;
|
||||
auth_url = "https://auth.cloonar.com/api/oidc/authorization";
|
||||
token_url = "https://auth.cloonar.com/api/oidc/token";
|
||||
api_url = "https://auth.cloonar.com/api/oidc/userinfo";
|
||||
login_attribute_path = "preferred_username";
|
||||
groups_attribute_path = "groups";
|
||||
name_attribute_path = "name";
|
||||
use_pkce = true;
|
||||
};
|
||||
|
||||
"auth.anonymous".enabled = true;
|
||||
"auth.anonymous".org_name = "Cloonar e.U.";
|
||||
"auth.anonymous".org_role = "Viewer";
|
||||
|
||||
server = {
|
||||
root_url = "https://grafana.cloonar.com";
|
||||
domain = "grafana.cloonar.com";
|
||||
enforce_domain = true;
|
||||
enable_gzip = true;
|
||||
http_addr = "0.0.0.0";
|
||||
http_port = 3001;
|
||||
};
|
||||
|
||||
smtp = {
|
||||
enabled = true;
|
||||
host = "mail.cloonar.com:587";
|
||||
user = "grafana@cloonar.com";
|
||||
password = "$__file{${config.sops.secrets.grafana-ldap-password.path}}";
|
||||
fromAddress = "grafana@cloonar.com";
|
||||
};
|
||||
|
||||
database = {
|
||||
type = "postgres";
|
||||
name = "grafana";
|
||||
host = "/run/postgresql";
|
||||
user = "grafana";
|
||||
};
|
||||
|
||||
security.admin_password = "$__file{${config.sops.secrets.grafana-admin-password.path}}";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."grafana.cloonar.com" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
locations."/".extraConfig = "proxy_pass http://localhost:3001;";
|
||||
};
|
||||
|
||||
services.postgresql.ensureUsers = [
|
||||
{
|
||||
name = "grafana";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
services.postgresql.ensureDatabases = [ "grafana" ];
|
||||
services.postgresqlBackup.databases = [ "grafana" ];
|
||||
|
||||
sops.secrets = {
|
||||
grafana-admin-password.owner = "grafana";
|
||||
grafana-ldap-password.owner = "grafana";
|
||||
grafana-oauth-secret.owner = "grafana";
|
||||
};
|
||||
}
|
||||
151
hosts/web-arm/modules/loki.nix
Normal file
151
hosts/web-arm/modules/loki.nix
Normal file
@@ -0,0 +1,151 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
rulerConfig = {
|
||||
groups = [
|
||||
{
|
||||
name = "general";
|
||||
rules = [
|
||||
{
|
||||
alert = "Coredumps";
|
||||
# filter out failed build gitlab CI runner, users or nix build sandboxes
|
||||
expr = ''sum by (host) (count_over_time({unit=~"systemd-coredump.*"} !~ "(/runner/_work|/home|/build|/scratch)" |~ "core dumped"[10m])) > 0'';
|
||||
for = "10s";
|
||||
annotations.description = ''{{ $labels.instance }} {{ $labels.coredump_unit }} core dumped in last 10min.'';
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
rulerDir = pkgs.writeTextDir "ruler/ruler.yml" (builtins.toJSON rulerConfig);
|
||||
in
|
||||
{
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/loki 0700 loki loki - -"
|
||||
"d /var/lib/loki/ruler 0700 loki loki - -"
|
||||
];
|
||||
services.loki = {
|
||||
enable = true;
|
||||
configuration = {
|
||||
# Basic stuff
|
||||
auth_enabled = false;
|
||||
server = {
|
||||
http_listen_port = 3100;
|
||||
log_level = "warn";
|
||||
};
|
||||
|
||||
# Distributor
|
||||
distributor.ring.kvstore.store = "inmemory";
|
||||
|
||||
# Ingester
|
||||
ingester = {
|
||||
lifecycler.address = "0.0.0.0";
|
||||
lifecycler.ring = {
|
||||
kvstore.store = "inmemory";
|
||||
replication_factor = 1;
|
||||
};
|
||||
chunk_encoding = "snappy";
|
||||
# Disable block transfers on shutdown
|
||||
};
|
||||
|
||||
# Storage
|
||||
storage_config = {
|
||||
boltdb.directory = "/var/lib/loki/boltdb";
|
||||
boltdb_shipper = {
|
||||
active_index_directory = "/var/lib/loki/index";
|
||||
cache_location = "/var/lib/loki/boltdb-cache";
|
||||
};
|
||||
tsdb_shipper = {
|
||||
active_index_directory = "/var/lib/loki/tsdb-index";
|
||||
cache_location = "/var/lib/loki/tsdb-cache";
|
||||
|
||||
};
|
||||
filesystem.directory = "/var/lib/loki/storage";
|
||||
|
||||
};
|
||||
|
||||
limits_config.retention_period = "48h";
|
||||
|
||||
# Table manager
|
||||
table_manager = {
|
||||
retention_deletes_enabled = true;
|
||||
retention_period = "48h";
|
||||
};
|
||||
|
||||
compactor = {
|
||||
retention_enabled = true;
|
||||
compaction_interval = "10m";
|
||||
working_directory = "/var/lib/loki/compactor";
|
||||
retention_delete_delay = "2h";
|
||||
retention_delete_worker_count = 150;
|
||||
delete_request_store = "filesystem";
|
||||
};
|
||||
|
||||
# Schema
|
||||
schema_config.configs = [
|
||||
{
|
||||
from = "2020-11-08";
|
||||
store = "boltdb-shipper";
|
||||
object_store = "filesystem";
|
||||
schema = "v13";
|
||||
index.prefix = "index_";
|
||||
index.period = "24h";
|
||||
}
|
||||
{
|
||||
from = "2024-04-01";
|
||||
store = "tsdb";
|
||||
object_store = "filesystem";
|
||||
schema = "v13";
|
||||
index.prefix = "index_";
|
||||
index.period = "24h";
|
||||
}
|
||||
];
|
||||
|
||||
limits_config.ingestion_burst_size_mb = 16;
|
||||
|
||||
# ruler = {
|
||||
# storage = {
|
||||
# type = "local";
|
||||
# local.directory = rulerDir;
|
||||
# };
|
||||
# rule_path = "/var/lib/loki/ruler";
|
||||
# alertmanager_url = "http://alertmanager.cloonar.com";
|
||||
# ring.kvstore.store = "inmemory";
|
||||
# };
|
||||
|
||||
query_range.cache_results = true;
|
||||
query_range.parallelise_shardable_queries = false;
|
||||
limits_config.split_queries_by_interval = "24h";
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets.promtail-nginx-password.owner = "nginx";
|
||||
|
||||
services.nginx.virtualHosts."loki.cloonar.com" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
locations."/" = {
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
auth_basic "Loki password";
|
||||
auth_basic_user_file ${config.sops.secrets.promtail-nginx-password.path};
|
||||
|
||||
proxy_read_timeout 1800s;
|
||||
proxy_redirect off;
|
||||
proxy_connect_timeout 1600s;
|
||||
|
||||
access_log off;
|
||||
proxy_pass http://127.0.0.1:3100;
|
||||
'';
|
||||
};
|
||||
locations."/ready" = {
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
auth_basic off;
|
||||
access_log off;
|
||||
proxy_pass http://127.0.0.1:3100;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
78
hosts/web-arm/modules/mysql.nix
Normal file
78
hosts/web-arm/modules/mysql.nix
Normal file
@@ -0,0 +1,78 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
let
|
||||
mysqlCreateDatabase = pkgs.writeShellScriptBin "mysql-create-database" ''
|
||||
#!/usr/bin/env bash
|
||||
if [ $# -lt 2 ]
|
||||
then
|
||||
echo "Usage: $0 <database> <host>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ $EUID -eq 0 ]
|
||||
then
|
||||
echo "Must be root!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB="$1"
|
||||
HOST="$2"
|
||||
PASSWORD="$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64 | xargs)"
|
||||
|
||||
cat <<EOF | mysql --host localhost --user root
|
||||
create database $DB;
|
||||
grant usage on $DB.* to '$DB'@'$HOST' identified by '$PASSWORD';
|
||||
grant all privileges on $DB.* to '$DB'@'$HOST';
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo "Password for user $DB is:"
|
||||
echo
|
||||
echo $PASSWORD
|
||||
echo
|
||||
'';
|
||||
mysqlDeleteDatabase = pkgs.writeShellScriptBin "mysql-delete-database" ''
|
||||
#!/usr/bin/env bash
|
||||
if [ $# -lt 1 ]
|
||||
then
|
||||
echo "Usage: $0 <database>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ $EUID -eq 0 ]
|
||||
then
|
||||
echo "Must be root!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB="$1"
|
||||
PASSWORD="$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64 | xargs)"
|
||||
|
||||
cat <<EOF | mysql --host localhost --user root
|
||||
drop database $DB;
|
||||
drop user '$DB';
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo "Dropped database $DB!"
|
||||
echo
|
||||
'';
|
||||
in {
|
||||
environment.systemPackages = [
|
||||
mysqlCreateDatabase
|
||||
mysqlDeleteDatabase
|
||||
];
|
||||
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
package = pkgs.mariadb;
|
||||
settings = {
|
||||
mysqld = {
|
||||
max_allowed_packet = "64M";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.mysqlBackup.enable = true;
|
||||
services.mysqlBackup.databases = [ "mysql" ];
|
||||
}
|
||||
37
hosts/web-arm/modules/nextcloud/default.nix
Normal file
37
hosts/web-arm/modules/nextcloud/default.nix
Normal file
@@ -0,0 +1,37 @@
|
||||
{ pkgs, config, ... }:
|
||||
{
|
||||
sops.secrets.nextcloud-adminpass.owner = "nextcloud";
|
||||
|
||||
services.nextcloud = {
|
||||
enable = true;
|
||||
hostName = "nextcloud.cloonar.com";
|
||||
https = true;
|
||||
package = pkgs.nextcloud27;
|
||||
# Instead of using pkgs.nextcloud27Packages.apps,
|
||||
# we'll reference the package version specified above
|
||||
extraApps = with config.services.nextcloud.package.packages.apps; {
|
||||
inherit contacts calendar tasks deck;
|
||||
oidc_login = pkgs.fetchNextcloudApp rec {
|
||||
url = "https://github.com/pulsejet/nextcloud-oidc-login/releases/download/v2.6.0/oidc_login.tar.gz";
|
||||
sha256 = "sha256-MZ/Pgqrb8Y9aH1vd3BfuPhfLOmYyZQO2xVasdj+rCo4=";
|
||||
};
|
||||
};
|
||||
extraAppsEnable = true;
|
||||
|
||||
database.createLocally = true;
|
||||
enableBrokenCiphersForSSE = false;
|
||||
|
||||
config = {
|
||||
adminpassFile = config.sops.secrets.nextcloud-adminpass.path;
|
||||
dbtype = "mysql";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts.${config.services.nextcloud.hostName} = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
};
|
||||
|
||||
services.mysqlBackup.databases = [ "nextcloud" ];
|
||||
}
|
||||
39
hosts/web-arm/modules/nextcloud/secrets.yaml
Normal file
39
hosts/web-arm/modules/nextcloud/secrets.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
nextcloud-adminpass: ENC[AES256_GCM,data:WJA7+5XqLK2eYefCviHqvHwqYPy9yfN+/3j5RTF0edrw41oB/wC5JWYejK2FzMkjkXZM0BUQ6waE3PCal3Ebqvzt/ZyC8Pwm8Z+PuMuXFx/6fQLJDxHALXH03GWAzNhUZpcZUYoNtu+uwaROg/4ZVNRu3IXxw+b2DWN65EaMO48=,iv:arkUgibmZQuaiCwYg6NBrMHZXUCLY2y/XiuVjB450ag=,tag:RH6r8nJPU24qq/EUC3jQ/A==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0VmR4THNkUGpvVHB6WWtw
|
||||
WkQ1dlc3R0FWaXpVZ29Sd2g1ZWJzYUFQWHdFCndkUWxqZEdIQlBnSDluN2NEWmZG
|
||||
VndCbXlqV3p0ZnYwcFhjeGZVa09xcW8KLS0tIHVnc2RPWTF1b2NvWVp3OEFwVDZk
|
||||
V0FWOXhSbXQyd0JmVEVpdG9IeXlsQ1UKFxGluq+uOgkA7UUa6/4ZErEPRgQQ5cXS
|
||||
PdB5Et5f02RWBRAUtGEE0UrLiINlIFvFAIr3PKctNVc8/Ovf/jGojg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0RnRPK0Y4ekRiYS9xdGs0
|
||||
ZE5oT1FIWmlySERMbDAyQXlHNDJnQ2Q2dkVvCjNQSGlyQXlzUXAzV0wrNHppUFY4
|
||||
a3k4Y2VtQ1Z4UjVqcnQ4MXhjSzJoM0UKLS0tIHBORnVoSHlJVnpjcmdZVTA1NHhF
|
||||
dHVTWnpXTnNNc0l1M3J6enFBdUwwNWcK80nKzyIrrKaEa0naFsnuie+732hMZQUg
|
||||
IAU9V7/bZiDItTUVdATDjjNBiXnMgDB73SqHhuyIDD+VhDkVUBhjWw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVdDduRUZOS2VEUldmRFRS
|
||||
QUVxeUVWRERSQ2ZkdnV1ekw4SVVFSzZvUFN3CkQrRnBQQzlnL2xtcFpVd0xiQmda
|
||||
NFZnQmhxcm1xUnVZY3l2eHp6Sjl4a0UKLS0tIG1maDNiRW44VmJDSlk2eWRQcHB2
|
||||
ZHpwQURoNGhuOWJPUkFpc0RSaHFBM0UKW4lMlcxC5+Hpm6DO3wwco41kJsfuWP33
|
||||
+2qhmnwt8mXWxAVxNreQQ0YQDliBnQR3uUny7hWyfrIkeQzOBLBrOw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2023-08-18T17:47:34Z"
|
||||
mac: ENC[AES256_GCM,data:bm/lHsobqvZSzk9crPmf8vc2idN3h/HOpQab7n7N6vtEY0QpMTv+6K7YERBD7T9oIxSNtcLNOcw6Rr2w9Cd1cq+W0azPA2dxd6/crq6rbhAgld/MipemP+YfdENxRrdyastk7P3FWyHZzhKlhem/ft0lpeiJg5NWRjA8IkLSDZc=,iv:W4cYC/e1CO5nsLx5yOaH0vGJ7fAx5bAH9acJShciHcI=,tag:whYqwogQMPPklHqoyhuL8g==,type:str]
|
||||
pgp: []
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.7.3
|
||||
9
hosts/web-arm/modules/postgresql.nix
Normal file
9
hosts/web-arm/modules/postgresql.nix
Normal file
@@ -0,0 +1,9 @@
|
||||
{ pkgs, ... }: {
|
||||
services.postgresql.enable = true;
|
||||
services.postgresql.package = pkgs.postgresql_14;
|
||||
services.postgresql.settings = {
|
||||
max_connections = "300";
|
||||
shared_buffers = "80MB";
|
||||
};
|
||||
services.postgresqlBackup.enable = true;
|
||||
}
|
||||
306
hosts/web-arm/modules/prometheus.nix
Normal file
306
hosts/web-arm/modules/prometheus.nix
Normal file
@@ -0,0 +1,306 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
sops.secrets.alertmanager = { };
|
||||
sops.secrets.hass-token.owner = "prometheus";
|
||||
|
||||
# imports = [
|
||||
# ./matrix-alertmanager.nix
|
||||
# ./irc-alertmanager.nix
|
||||
# ./rules.nix
|
||||
# ];
|
||||
|
||||
services.prometheus = {
|
||||
webExternalUrl = "https://prometheus.cloonar.com";
|
||||
alertmanagers = [
|
||||
{
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "localhost:9093" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
rules = [
|
||||
''
|
||||
ALERT node_down
|
||||
IF up == 0
|
||||
FOR 5m
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary = "{{$labels.alias}}: Node is down.",
|
||||
description = "{{$labels.alias}} has been down for more than 5 minutes."
|
||||
}
|
||||
ALERT node_systemd_service_failed
|
||||
IF node_systemd_unit_state{state="failed"} == 1
|
||||
FOR 4m
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary = "{{$labels.alias}}: Service {{$labels.name}} failed to start.",
|
||||
description = "{{$labels.alias}} failed to (re)start service {{$labels.name}}."
|
||||
}
|
||||
ALERT node_filesystem_full_90percent
|
||||
IF sort(node_filesystem_free{device!="ramfs"} < node_filesystem_size{device!="ramfs"} * 0.1) / 1024^3
|
||||
FOR 5m
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary = "{{$labels.alias}}: Filesystem is running out of space soon.",
|
||||
description = "{{$labels.alias}} device {{$labels.device}} on {{$labels.mountpoint}} got less than 10% space left on its filesystem."
|
||||
}
|
||||
ALERT node_filesystem_full_in_4h
|
||||
IF predict_linear(node_filesystem_free{device!="ramfs"}[1h], 4*3600) <= 0
|
||||
FOR 5m
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary = "{{$labels.alias}}: Filesystem is running out of space in 4 hours.",
|
||||
description = "{{$labels.alias}} device {{$labels.device}} on {{$labels.mountpoint}} is running out of space of in approx. 4 hours"
|
||||
}
|
||||
ALERT node_filedescriptors_full_in_3h
|
||||
IF predict_linear(node_filefd_allocated[1h], 3*3600) >= node_filefd_maximum
|
||||
FOR 20m
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary = "{{$labels.alias}} is running out of available file descriptors in 3 hours.",
|
||||
description = "{{$labels.alias}} is running out of available file descriptors in approx. 3 hours"
|
||||
}
|
||||
ALERT node_load1_90percent
|
||||
IF node_load1 / on(alias) count(node_cpu{mode="system"}) by (alias) >= 0.9
|
||||
FOR 1h
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary = "{{$labels.alias}}: Running on high load.",
|
||||
description = "{{$labels.alias}} is running with > 90% total load for at least 1h."
|
||||
}
|
||||
ALERT node_cpu_util_90percent
|
||||
IF 100 - (avg by (alias) (irate(node_cpu{mode="idle"}[5m])) * 100) >= 90
|
||||
FOR 1h
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary = "{{$labels.alias}}: High CPU utilization.",
|
||||
description = "{{$labels.alias}} has total CPU utilization over 90% for at least 1h."
|
||||
}
|
||||
ALERT node_ram_using_90percent
|
||||
IF node_memory_MemFree + node_memory_Buffers + node_memory_Cached < node_memory_MemTotal * 0.1
|
||||
FOR 30m
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary="{{$labels.alias}}: Using lots of RAM.",
|
||||
description="{{$labels.alias}} is using at least 90% of its RAM for at least 30 minutes now.",
|
||||
}
|
||||
ALERT node_swap_using_80percent
|
||||
IF node_memory_SwapTotal - (node_memory_SwapFree + node_memory_SwapCached) > node_memory_SwapTotal * 0.8
|
||||
FOR 10m
|
||||
LABELS {
|
||||
severity="page"
|
||||
}
|
||||
ANNOTATIONS {
|
||||
summary="{{$labels.alias}}: Running out of swap soon.",
|
||||
description="{{$labels.alias}} is using 80% of its swap space for at least 10 minutes now."
|
||||
}
|
||||
ALERT homeassistant = {
|
||||
IF homeassistant_entity_available{domain="persistent_notification", entity!~"persistent_notification.http_login|persistent_notification.recorder_database_migration"} >= 0
|
||||
ANNOTATIONS {
|
||||
description="homeassistant notification {{$labels.entity}} ({{$labels.friendly_name}}): {{$value}}"
|
||||
}
|
||||
|
||||
ALERT gitea
|
||||
IF rate(promhttp_metric_handler_requests_total{job="gitea", code="500"}[5m]) > 3
|
||||
ANNOTATIONS {
|
||||
description="{{$labels.instance}}: gitea instances error rate went up: {{$value}} errors in 5 minutes"
|
||||
}
|
||||
''
|
||||
];
|
||||
scrapeConfigs = [
|
||||
{
|
||||
job_name = "telegraf";
|
||||
scrape_interval = "60s";
|
||||
metrics_path = "/metrics";
|
||||
static_configs = [
|
||||
{
|
||||
targets = [
|
||||
"web-01.cloonar.com:9273"
|
||||
];
|
||||
labels.host = "web-01.cloonar.com";
|
||||
}
|
||||
{
|
||||
targets = [
|
||||
"mail.cloonar.com:9273"
|
||||
];
|
||||
labels.host = "mail.cloonar.com";
|
||||
}
|
||||
{
|
||||
targets = [
|
||||
"git.cloonar.com:9273"
|
||||
];
|
||||
labels.host = "git.cloonar.com";
|
||||
}
|
||||
{
|
||||
targets = [
|
||||
"home-assistant.cloonar.com:9273"
|
||||
];
|
||||
labels.host = "home-assistant.cloonar.com";
|
||||
}
|
||||
{
|
||||
targets = map (host: "${host}.cloonar.com:9273") [
|
||||
"web-01"
|
||||
"mail"
|
||||
"git"
|
||||
"home-assistant"
|
||||
];
|
||||
|
||||
labels.org = "cloonar";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
job_name = "homeassistant";
|
||||
scrape_interval = "60s";
|
||||
metrics_path = "/api/prometheus";
|
||||
|
||||
authorization.credentials_file = config.sops.secrets.hass-token.path;
|
||||
|
||||
scheme = "https";
|
||||
static_configs = [
|
||||
{
|
||||
targets = [
|
||||
"home-assistant.cloonar.com:443"
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
job_name = "gitea";
|
||||
scrape_interval = "60s";
|
||||
metrics_path = "/metrics";
|
||||
|
||||
scheme = "https";
|
||||
static_configs = [
|
||||
{
|
||||
targets = [
|
||||
"git.cloonar.com:443"
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
# services.prometheus.alertmanager = {
|
||||
# enable = true;
|
||||
# environmentFile = config.sops.secrets.alertmanager.path;
|
||||
# webExternalUrl = "https://alertmanager.cloonar.com";
|
||||
# listenAddress = "[::1]";
|
||||
# configuration = {
|
||||
# global = {
|
||||
# # The smarthost and SMTP sender used for mail notifications.
|
||||
# smtp_smarthost = "mail.cloonar.com:587";
|
||||
# smtp_from = "alertmanager@cloonar.com";
|
||||
# smtp_auth_username = "alertmanager@cloonar.com";
|
||||
# smtp_auth_password = "$SMTP_PASSWORD";
|
||||
# };
|
||||
# route = {
|
||||
# receiver = "default";
|
||||
# routes = [
|
||||
# {
|
||||
# group_by = [ "host" ];
|
||||
# match_re.org = "krebs";
|
||||
# group_wait = "5m";
|
||||
# group_interval = "5m";
|
||||
# repeat_interval = "4h";
|
||||
# receiver = "krebs";
|
||||
# }
|
||||
# {
|
||||
# group_by = [ "host" ];
|
||||
# match_re.org = "nix-community";
|
||||
# group_wait = "5m";
|
||||
# group_interval = "5m";
|
||||
# repeat_interval = "4h";
|
||||
# receiver = "nix-community";
|
||||
# }
|
||||
# {
|
||||
# group_by = [ "host" ];
|
||||
# match_re.org = "clan-lol";
|
||||
# group_wait = "5m";
|
||||
# group_interval = "5m";
|
||||
# repeat_interval = "4h";
|
||||
# receiver = "clan-lol";
|
||||
# }
|
||||
# {
|
||||
# group_by = [ "host" ];
|
||||
# group_wait = "30s";
|
||||
# group_interval = "2m";
|
||||
# repeat_interval = "2h";
|
||||
# receiver = "all";
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
# receivers = [
|
||||
# {
|
||||
# name = "krebs";
|
||||
# webhook_configs = [
|
||||
# {
|
||||
# url = "http://127.0.0.1:9223/";
|
||||
# max_alerts = 5;
|
||||
# }
|
||||
# ];
|
||||
# }
|
||||
# #{
|
||||
# # name = "numtide";
|
||||
# # slack_configs = [
|
||||
# # {
|
||||
# # token = "$SLACK_TOKEN";
|
||||
# # api_url = "https://";
|
||||
# # }
|
||||
# # ];
|
||||
# #}
|
||||
# {
|
||||
# name = "nix-community";
|
||||
# webhook_configs = [
|
||||
# {
|
||||
# url = "http://localhost:9088/alert";
|
||||
# max_alerts = 5;
|
||||
# }
|
||||
# ];
|
||||
# }
|
||||
# {
|
||||
# name = "clan-lol";
|
||||
# webhook_configs = [
|
||||
# # TODO
|
||||
# #{
|
||||
# # url = "http://localhost:4050/services/hooks/YWxlcnRtYW5hZ2VyX3NlcnZpY2U";
|
||||
# # max_alerts = 5;
|
||||
# #}
|
||||
# ];
|
||||
# }
|
||||
# {
|
||||
# name = "all";
|
||||
# pushover_configs = [
|
||||
# {
|
||||
# user_key = "$PUSHOVER_USER_KEY";
|
||||
# token = "$PUSHOVER_TOKEN";
|
||||
# priority = "0";
|
||||
# }
|
||||
# ];
|
||||
# }
|
||||
# {
|
||||
# name = "default";
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
# };
|
||||
|
||||
}
|
||||
39
hosts/web-arm/modules/rustdesk.nix
Normal file
39
hosts/web-arm/modules/rustdesk.nix
Normal file
@@ -0,0 +1,39 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
virtualisation = {
|
||||
podman.enable = true;
|
||||
oci-containers.containers = {
|
||||
rustdesk-server = {
|
||||
image = "rustdesk/rustdesk-server-s6:1";
|
||||
volumes = [ "/var/lib/rustdesk-server:/data" ];
|
||||
environment = {
|
||||
RELAY = "rustdesk.cloonar.com:21117";
|
||||
};
|
||||
ports = [
|
||||
"21115:21115"
|
||||
"21116:21116"
|
||||
"21116:21116/udp"
|
||||
"21118:21118"
|
||||
"21117:21117"
|
||||
"21119:21119"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users.users.rustdesk-server = {
|
||||
isSystemUser = true;
|
||||
group = "rustdesk-server";
|
||||
home = "/var/lib/rustdesk-server";
|
||||
createHome = true;
|
||||
};
|
||||
users.groups.rustdesk-server = { };
|
||||
users.groups.docker.members = [ "rustdesk-server" ];
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 5000 21115 21116 21117 21118 21119 ];
|
||||
allowedUDPPorts = [ 21116 ];
|
||||
};
|
||||
}
|
||||
43
hosts/web-arm/modules/victoriametrics.nix
Normal file
43
hosts/web-arm/modules/victoriametrics.nix
Normal file
@@ -0,0 +1,43 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
configure_prom = builtins.toFile "prometheus.yml" ''
|
||||
scrape_configs:
|
||||
- job_name: '${config.networking.hostName}'
|
||||
stream_parse: true
|
||||
static_configs:
|
||||
- targets:
|
||||
- 127.0.0.1:9100
|
||||
'';
|
||||
in {
|
||||
services.prometheus.exporters.node.enable = true;
|
||||
|
||||
sops.secrets.victoria-nginx-password.owner = "nginx";
|
||||
|
||||
services.victoriametrics = {
|
||||
enable = true;
|
||||
extraOptions = [
|
||||
"-promscrape.config=${configure_prom}"
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."victoria-server.cloonar.com" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
locations."/" = {
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
auth_basic "Victoria password";
|
||||
auth_basic_user_file ${config.sops.secrets.victoria-nginx-password.path};
|
||||
|
||||
proxy_read_timeout 1800s;
|
||||
proxy_redirect off;
|
||||
proxy_connect_timeout 1600s;
|
||||
|
||||
access_log off;
|
||||
proxy_pass http://127.0.0.1:8428;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
328
hosts/web-arm/modules/web/stack.nix
Normal file
328
hosts/web-arm/modules/web/stack.nix
Normal file
@@ -0,0 +1,328 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.webstack;
|
||||
|
||||
instanceOpts = { name, ... }:
|
||||
{
|
||||
options = {
|
||||
user = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
User of the typo3 instance. Defaults to attribute name in instances.
|
||||
'';
|
||||
example = "example.org";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
Domain of the typo3 instance. Defaults to attribute name in instances.
|
||||
'';
|
||||
example = "example.org";
|
||||
};
|
||||
|
||||
domainAliases = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "www.example.org" "example.org" ];
|
||||
description = lib.mdDoc ''
|
||||
Additional domains served by this typo3 instance.
|
||||
'';
|
||||
};
|
||||
|
||||
phpPackage = mkOption {
|
||||
type = types.package;
|
||||
example = literalExpression "pkgs.php";
|
||||
description = lib.mdDoc ''
|
||||
Which PHP package to use in this typo3 instance.
|
||||
'';
|
||||
};
|
||||
|
||||
phpOptions = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
"Options appended to the PHP configuration file {file}`php.ini` used for this PHP-FPM pool."
|
||||
'';
|
||||
};
|
||||
|
||||
enableMysql = mkEnableOption (lib.mdDoc "MySQL Database");
|
||||
enableDefaultLocations = mkEnableOption (lib.mdDoc "Create default nginx location directives") // { default = true; };
|
||||
|
||||
authorizedKeys = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
Authorized keys for the typo3 instance ssh user.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = ''
|
||||
if (!-e $request_filename) {
|
||||
rewrite ^/(.+)\.(\d+)\.(php|js|css|png|jpg|gif|gzip)$ /$1.$3 last;
|
||||
}
|
||||
'';
|
||||
description = lib.mdDoc ''
|
||||
These lines go to the end of the vhost verbatim.
|
||||
'';
|
||||
};
|
||||
|
||||
locations = mkOption {
|
||||
type = types.attrsOf (types.submodule (import <nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix> {
|
||||
inherit lib config;
|
||||
}));
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
"/" = {
|
||||
proxyPass = "http://localhost:3000";
|
||||
};
|
||||
};
|
||||
'';
|
||||
description = lib.mdDoc "Declarative location config";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
options.services.webstack = {
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/www";
|
||||
description = lib.mdDoc ''
|
||||
The data directory for MySQL.
|
||||
|
||||
::: {.note}
|
||||
If left as the default value of `/var/www` this directory will automatically be created before the web
|
||||
server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
instances = mkOption {
|
||||
type = types.attrsOf (types.submodule instanceOpts);
|
||||
default = {};
|
||||
description = lib.mdDoc "Create vhosts for typo3";
|
||||
example = literalExpression ''
|
||||
{
|
||||
"typo3.example.com" = {
|
||||
domain = "example.com";
|
||||
domainAliases = [ "www.example.com" ];
|
||||
phpPackage = pkgs.php81;
|
||||
authorizedKeys = [
|
||||
"ssh-rsa AZA=="
|
||||
];
|
||||
};
|
||||
};
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.services = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
in
|
||||
nameValuePair "phpfpm-${domain}" {
|
||||
serviceConfig = {
|
||||
ProtectHome = lib.mkForce "tmpfs";
|
||||
BindPaths = "BindPaths=/var/www/${domain}:/var/www/${domain}";
|
||||
};
|
||||
}
|
||||
) cfg.instances;
|
||||
|
||||
services.phpfpm.pools = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
nameValuePair domain {
|
||||
user = user;
|
||||
settings = {
|
||||
"listen.owner" = config.services.nginx.user;
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 32;
|
||||
"pm.max_requests" = 500;
|
||||
"pm.start_servers" = 2;
|
||||
"pm.min_spare_servers" = 2;
|
||||
"pm.max_spare_servers" = 5;
|
||||
"php_admin_value[error_log]" = "syslog";
|
||||
"php_admin_value[max_execution_time]" = 240;
|
||||
"php_admin_value[max_input_vars]" = 1500;
|
||||
"access.log" = "/var/log/$pool.access.log";
|
||||
};
|
||||
phpOptions = instanceOpts.phpOptions;
|
||||
phpPackage = instanceOpts.phpPackage;
|
||||
phpEnv."PATH" = pkgs.lib.makeBinPath [ instanceOpts.phpPackage ];
|
||||
}
|
||||
) cfg.instances;
|
||||
|
||||
};
|
||||
|
||||
|
||||
config.services.nginx.virtualHosts = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
nameValuePair domain {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
root = cfg.dataDir + "/" + domain + "/public";
|
||||
|
||||
locations = lib.mkMerge [
|
||||
instanceOpts.locations
|
||||
(mkIf instanceOpts.enableDefaultLocations {
|
||||
"/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# Cache.appcache, your document html and data
|
||||
"~* \\.(?:manifest|appcache|html?|xml|json)$".extraConfig = ''
|
||||
expires -1;
|
||||
# access_log logs/static.log; # I don't usually include a static log
|
||||
'';
|
||||
|
||||
"~* \\.(jpe?g|png)$".extraConfig = ''
|
||||
set $red Z;
|
||||
|
||||
if ($http_accept ~* "webp") {
|
||||
set $red A;
|
||||
}
|
||||
|
||||
if (-f $document_root/webp/$request_uri.webp) {
|
||||
set $red "''${red}B";
|
||||
}
|
||||
|
||||
if ($red = "AB") {
|
||||
add_header Vary Accept;
|
||||
rewrite ^ /webp/$request_uri.webp;
|
||||
}
|
||||
'';
|
||||
|
||||
# Cache Media: images, icons, video, audio, HTC
|
||||
"~* \\.(?:jpg|jpeg|gif|png|webp|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Feed
|
||||
"~* \\.(?:rss|atom)$".extraConfig = ''
|
||||
expires 1h;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Cache CSS, Javascript, Images, Icons, Video, Audio, HTC, Fonts
|
||||
"~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
"/".extraConfig = ''
|
||||
index index.php index.html;
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
'';
|
||||
})
|
||||
{
|
||||
"~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
return 404;
|
||||
}
|
||||
include ${pkgs.nginx}/conf/fastcgi_params;
|
||||
include ${pkgs.nginx}/conf/fastcgi.conf;
|
||||
fastcgi_buffer_size 32k;
|
||||
fastcgi_buffers 8 16k;
|
||||
fastcgi_connect_timeout 240s;
|
||||
fastcgi_read_timeout 240s;
|
||||
fastcgi_send_timeout 240s;
|
||||
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
|
||||
fastcgi_index index.php;
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
extraConfig = instanceOpts.extraConfig;
|
||||
|
||||
|
||||
# locations = mapAttrs' (location: locationOpts:
|
||||
# nameValuePair location locationOpts) instanceOpts.locations;
|
||||
|
||||
}
|
||||
) cfg.instances;
|
||||
|
||||
config.users.users = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
nameValuePair user {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = "/var/www/" + domain;
|
||||
homeMode= "770";
|
||||
group = config.services.nginx.group;
|
||||
openssh.authorizedKeys.keys = instanceOpts.authorizedKeys;
|
||||
}
|
||||
) cfg.instances;
|
||||
config.users.groups = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in nameValuePair user {}) cfg.instances;
|
||||
|
||||
config.services.mysql.ensureUsers = mapAttrsToList (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
mkIf instanceOpts.enableMysql {
|
||||
name = user;
|
||||
ensurePermissions = {
|
||||
"${user}.*" = "ALL PRIVILEGES";
|
||||
};
|
||||
}) cfg.instances;
|
||||
|
||||
config.services.mysql.ensureDatabases = mapAttrsToList (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
mkIf instanceOpts.enableMysql user
|
||||
) cfg.instances;
|
||||
config.services.mysqlBackup.databases = mapAttrsToList (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
mkIf instanceOpts.enableMysql user
|
||||
) cfg.instances;
|
||||
}
|
||||
|
||||
445
hosts/web-arm/modules/web/typo3.nix
Normal file
445
hosts/web-arm/modules/web/typo3.nix
Normal file
@@ -0,0 +1,445 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.typo3;
|
||||
|
||||
instanceOpts = { name, ... }:
|
||||
{
|
||||
options = {
|
||||
user = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
User of the typo3 instance. Defaults to attribute name in instances.
|
||||
'';
|
||||
example = "example.org";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
Domain of the typo3 instance. Defaults to attribute name in instances.
|
||||
'';
|
||||
example = "example.org";
|
||||
};
|
||||
|
||||
domainAliases = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "www.example.org" "example.org" ];
|
||||
description = lib.mdDoc ''
|
||||
Additional domains served by this typo3 instance.
|
||||
'';
|
||||
};
|
||||
|
||||
phpPackage = mkOption {
|
||||
type = types.package;
|
||||
example = literalExpression "pkgs.php";
|
||||
description = lib.mdDoc ''
|
||||
Which PHP package to use in this typo3 instance.
|
||||
'';
|
||||
};
|
||||
|
||||
authorizedKeys = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
Authorized keys for the typo3 instance ssh user.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
options.services.typo3 = {
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/www";
|
||||
description = lib.mdDoc ''
|
||||
The data directory for MySQL.
|
||||
|
||||
::: {.note}
|
||||
If left as the default value of `/var/www` this directory will automatically be created before the web
|
||||
server starts, otherwise you are responsible for ensuring the directory exists with appropriate ownership and permissions.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
instances = mkOption {
|
||||
type = types.attrsOf (types.submodule instanceOpts);
|
||||
default = {};
|
||||
description = lib.mdDoc "Create vhosts for typo3";
|
||||
example = literalExpression ''
|
||||
{
|
||||
"typo3.example.com" = {
|
||||
domain = "example.com";
|
||||
domainAliases = [ "www.example.com" ];
|
||||
phpPackage = pkgs.php82;
|
||||
authorizedKeys = [
|
||||
"ssh-rsa AZA=="
|
||||
];
|
||||
};
|
||||
};
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
# systemd.services = mapAttrs' (instance: instanceOpts:
|
||||
# let
|
||||
# domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
# in
|
||||
# nameValuePair "phpfpm-${domain}" {
|
||||
# serviceConfig = {
|
||||
# ProtectHome = lib.mkForce "tmpfs";
|
||||
# BindPaths = "BindPaths=/var/www/${domain}:/var/www/${domain}";
|
||||
# };
|
||||
# }
|
||||
# ) cfg.instances;
|
||||
|
||||
systemd.timers = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
nameValuePair ("typo3-cron-" + domain) {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "05:00";
|
||||
Unit = "typo3-cron-" + domain + ".service";
|
||||
};
|
||||
}
|
||||
) cfg.instances;
|
||||
systemd.services = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
nameValuePair ("typo3-cron-" + domain) {
|
||||
script = ''
|
||||
set -eu
|
||||
${instanceOpts.phpPackage}/bin/php /var/www/${domain}/.Build/bin/typo3 scheduler:run
|
||||
${instanceOpts.phpPackage}/bin/php /var/www/${domain}/.Build/bin/typo3 ke_search:indexing
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = user;
|
||||
};
|
||||
}
|
||||
) cfg.instances;
|
||||
|
||||
services.phpfpm.pools = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
nameValuePair domain {
|
||||
user = user;
|
||||
settings = {
|
||||
"listen.owner" = config.services.nginx.user;
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 32;
|
||||
"pm.max_requests" = 500;
|
||||
"pm.start_servers" = 2;
|
||||
"pm.min_spare_servers" = 2;
|
||||
"pm.max_spare_servers" = 5;
|
||||
"php_admin_value[error_log]" = "syslog";
|
||||
"php_admin_value[max_execution_time]" = 240;
|
||||
"php_admin_value[max_input_vars]" = 1500;
|
||||
"php_admin_value[upload_max_filesize]" = "256M";
|
||||
"php_admin_value[post_max_size]" = "256M";
|
||||
"access.log" = "/var/log/$pool.access.log";
|
||||
};
|
||||
phpOptions = ''
|
||||
opcache.enable=1
|
||||
opcache.memory_consumption=128
|
||||
opcache.validate_timestamps=0
|
||||
opcache.revalidate_path=0
|
||||
'';
|
||||
phpPackage = instanceOpts.phpPackage;
|
||||
phpEnv."PATH" = pkgs.lib.makeBinPath [ instanceOpts.phpPackage ];
|
||||
}
|
||||
) cfg.instances;
|
||||
|
||||
};
|
||||
|
||||
|
||||
config.services.nginx.virtualHosts = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
nameValuePair domain {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
root = cfg.dataDir + "/" + domain + "/public";
|
||||
serverAliases = instanceOpts.domainAliases;
|
||||
|
||||
extraConfig = ''
|
||||
if (!-e $request_filename) {
|
||||
rewrite ^/(.+)\.(\d+)\.(php|js|css|png|jpg|gif|gzip)$ /$1.$3 last;
|
||||
}
|
||||
|
||||
# Virtual endpoint created by nginx to forward auth requests.
|
||||
location /authelia {
|
||||
internal;
|
||||
set $upstream_authelia http://127.0.0.1:9091/api/verify;
|
||||
proxy_pass_request_body off;
|
||||
proxy_pass $upstream_authelia;
|
||||
proxy_set_header Content-Length "";
|
||||
|
||||
# Timeout if the real server is dead
|
||||
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
|
||||
|
||||
# [REQUIRED] Needed by Authelia to check authorizations of the resource.
|
||||
# Provide either X-Original-URL and X-Forwarded-Proto or
|
||||
# X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-Uri or both.
|
||||
# Those headers will be used by Authelia to deduce the target url of the user.
|
||||
# Basic Proxy Config
|
||||
client_body_buffer_size 128k;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Uri $request_uri;
|
||||
proxy_set_header X-Forwarded-Ssl on;
|
||||
proxy_redirect http:// $scheme://;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_cache_bypass $cookie_session;
|
||||
proxy_no_cache $cookie_session;
|
||||
proxy_buffers 4 32k;
|
||||
|
||||
# Advanced Proxy Config
|
||||
send_timeout 5m;
|
||||
proxy_read_timeout 240;
|
||||
proxy_send_timeout 240;
|
||||
proxy_connect_timeout 240;
|
||||
}
|
||||
'';
|
||||
|
||||
# locations."/typo3/login" = {
|
||||
# extraConfig = ''
|
||||
# # Basic Authelia Config
|
||||
# # Send a subsequent request to Authelia to verify if the user is authenticated
|
||||
# # and has the right permissions to access the resource.
|
||||
# auth_request /authelia;
|
||||
# # Set the `target_url` variable based on the request. It will be used to build the portal
|
||||
# # URL with the correct redirection parameter.
|
||||
# auth_request_set $target_url $scheme://$http_host$request_uri;
|
||||
# # Set the X-Forwarded-User and X-Forwarded-Groups with the headers
|
||||
# # returned by Authelia for the backends which can consume them.
|
||||
# # This is not safe, as the backend must make sure that they come from the
|
||||
# # proxy. In the future, it's gonna be safe to just use OAuth.
|
||||
# auth_request_set $user $upstream_http_remote_user;
|
||||
# auth_request_set $groups $upstream_http_remote_groups;
|
||||
# auth_request_set $name $upstream_http_remote_name;
|
||||
# auth_request_set $email $upstream_http_remote_email;
|
||||
# proxy_set_header Remote-User $user;
|
||||
# proxy_set_header Remote-Groups $groups;
|
||||
# proxy_set_header Remote-Name $name;
|
||||
# proxy_set_header Remote-Email $email;
|
||||
# # If Authelia returns 401, then nginx redirects the user to the login portal.
|
||||
# # If it returns 200, then the request pass through to the backend.
|
||||
# # For other type of errors, nginx will handle them as usual.
|
||||
# error_page 401 =302 https://auth.cloonar.com/?rd=$target_url;
|
||||
#
|
||||
# fastcgi_param REMOTE_USER $user;
|
||||
#
|
||||
# include ${pkgs.nginx}/conf/fastcgi.conf;
|
||||
# fastcgi_buffer_size 32k;
|
||||
# fastcgi_buffers 8 16k;
|
||||
# fastcgi_connect_timeout 240s;
|
||||
# fastcgi_read_timeout 240s;
|
||||
# fastcgi_send_timeout 240s;
|
||||
# fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
|
||||
# fastcgi_param SCRIPT_FILENAME ${cfg.dataDir}/${domain}/public/typo3/index.php;
|
||||
# '';
|
||||
# };
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# TYPO3 - Block access to composer files
|
||||
locations."~* composer\\.(?:json|lock)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
|
||||
|
||||
# TYPO3 - Block access to flexform files
|
||||
locations."~* flexform[^.]*\\.xml".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
|
||||
# TYPO3 - Block access to language files
|
||||
locations."~* locallang[^.]*\\.(?:xml|xlf)$".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
|
||||
# TYPO3 - Block access to static typoscript files
|
||||
locations."~* ext_conf_template\\.txt|ext_typoscript_constants\\.txt|ext_typoscript_setup\\.txt".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
|
||||
# TYPO3 - Block access to miscellaneous protected files
|
||||
locations."~* /.*\\.(?:bak|co?nf|cfg|ya?ml|ts|typoscript|tsconfig|dist|fla|in[ci]|log|sh|sql|sqlite)$".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
# locations."~* /.*\.(?:bak|cfg|co?nf|ya?ml|ts)$".extraConfig = ''
|
||||
# deny all;
|
||||
# '';
|
||||
|
||||
# TYPO3 - Block access to recycler and temporary directories
|
||||
locations."~ _(?:recycler|temp)_/".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
|
||||
# TYPO3 - Block access to configuration files stored in fileadmin
|
||||
locations."~ fileadmin/(?:templates)/.*\\.(?:txt|ts|typoscript)$".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
|
||||
|
||||
# TYPO3 - Block access to libraries, source and temporary compiled data
|
||||
locations."~ ^(?:vendor|typo3_src|typo3temp/var)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
|
||||
|
||||
# TYPO3 - Block access to protected extension directories
|
||||
locations."~ (?:typo3conf/ext|typo3/sysext|typo3/ext)/[^/]+/(?:Configuration|Resources/Private|Tests?|Documentation|docs?)/".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
|
||||
# Cache.appcache, your document html and data
|
||||
locations."~* \\.(?:manifest|appcache|html?|xml|json)$".extraConfig = ''
|
||||
expires -1;
|
||||
# access_log logs/static.log; # I don't usually include a static log
|
||||
'';
|
||||
|
||||
# Cache Media: images, icons, video, audio, HTC
|
||||
locations."~* \\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Feed
|
||||
locations."~* \\.(?:rss|atom)$".extraConfig = ''
|
||||
expires 1h;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Cache CSS, Javascript, Images, Icons, Video, Audio, HTC, Fonts
|
||||
locations."~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
locations."/".extraConfig = ''
|
||||
index index.php index.html;
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
'';
|
||||
|
||||
# TYPO3 Backend URLs
|
||||
locations."/typo3$".extraConfig = ''
|
||||
rewrite ^ /typo3/;
|
||||
'';
|
||||
|
||||
locations."/typo3/".extraConfig = ''
|
||||
try_files $uri /typo3/index.php$is_args$args;
|
||||
'';
|
||||
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
include ${pkgs.nginx}/conf/fastcgi.conf;
|
||||
fastcgi_buffer_size 32k;
|
||||
fastcgi_buffers 8 16k;
|
||||
fastcgi_connect_timeout 240s;
|
||||
fastcgi_read_timeout 240s;
|
||||
fastcgi_send_timeout 240s;
|
||||
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
|
||||
fastcgi_index index.php;
|
||||
'';
|
||||
}
|
||||
) cfg.instances;
|
||||
|
||||
config.users.users = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
nameValuePair user {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = "/var/www/" + domain;
|
||||
homeMode= "770";
|
||||
group = config.services.nginx.group;
|
||||
openssh.authorizedKeys.keys = instanceOpts.authorizedKeys;
|
||||
}
|
||||
) cfg.instances;
|
||||
config.users.groups = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in nameValuePair user {}) cfg.instances;
|
||||
|
||||
config.services.mysql.ensureUsers = mapAttrsToList (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
{
|
||||
name = user;
|
||||
ensurePermissions = {
|
||||
"${user}.*" = "ALL PRIVILEGES";
|
||||
};
|
||||
}) cfg.instances;
|
||||
|
||||
config.services.mysql.ensureDatabases = mapAttrsToList (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
user
|
||||
) cfg.instances;
|
||||
config.services.mysqlBackup.databases = mapAttrsToList (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
user
|
||||
) cfg.instances;
|
||||
}
|
||||
117
hosts/web-arm/modules/zammad/default.nix
Normal file
117
hosts/web-arm/modules/zammad/default.nix
Normal file
@@ -0,0 +1,117 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
services.zammad = {
|
||||
enable = true;
|
||||
port = 3010;
|
||||
secretKeyBaseFile = config.sops.secrets.zammad-key-base.path;
|
||||
database = {
|
||||
createLocally = true;
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."support.cloonar.com" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
|
||||
extraConfig = ''
|
||||
# Virtual endpoint created by nginx to forward auth requests.
|
||||
location /authelia {
|
||||
internal;
|
||||
set $upstream_authelia http://127.0.0.1:9091/api/verify;
|
||||
proxy_pass_request_body off;
|
||||
proxy_pass $upstream_authelia;
|
||||
proxy_set_header Content-Length "";
|
||||
|
||||
# Timeout if the real server is dead
|
||||
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
|
||||
|
||||
# [REQUIRED] Needed by Authelia to check authorizations of the resource.
|
||||
# Provide either X-Original-URL and X-Forwarded-Proto or
|
||||
# X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-Uri or both.
|
||||
# Those headers will be used by Authelia to deduce the target url of the user.
|
||||
# Basic Proxy Config
|
||||
client_body_buffer_size 128k;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Uri $request_uri;
|
||||
proxy_set_header X-Forwarded-Ssl on;
|
||||
proxy_redirect http:// $scheme://;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
proxy_cache_bypass $cookie_session;
|
||||
proxy_no_cache $cookie_session;
|
||||
proxy_buffers 4 32k;
|
||||
|
||||
# Advanced Proxy Config
|
||||
send_timeout 5m;
|
||||
proxy_read_timeout 240;
|
||||
proxy_send_timeout 240;
|
||||
proxy_connect_timeout 240;
|
||||
}
|
||||
'';
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:3010";
|
||||
proxyWebsockets = true;
|
||||
extraConfig =
|
||||
"proxy_connect_timeout 300;" +
|
||||
"proxy_send_timeout 300;" +
|
||||
"proxy_read_timeout 300;" +
|
||||
"send_timeout 300;"
|
||||
;
|
||||
};
|
||||
locations."/auth/sso" = {
|
||||
proxyPass = "http://127.0.0.1:3010";
|
||||
proxyWebsockets = true;
|
||||
|
||||
extraConfig = ''
|
||||
# Basic Authelia Config
|
||||
# Send a subsequent request to Authelia to verify if the user is authenticated
|
||||
# and has the right permissions to access the resource.
|
||||
auth_request /authelia;
|
||||
# Set the `target_url` variable based on the request. It will be used to build the portal
|
||||
# URL with the correct redirection parameter.
|
||||
auth_request_set $target_url $scheme://$http_host$request_uri;
|
||||
# Set the X-Forwarded-User and X-Forwarded-Groups with the headers
|
||||
# returned by Authelia for the backends which can consume them.
|
||||
# This is not safe, as the backend must make sure that they come from the
|
||||
# proxy. In the future, it's gonna be safe to just use OAuth.
|
||||
auth_request_set $user $upstream_http_remote_user;
|
||||
auth_request_set $groups $upstream_http_remote_groups;
|
||||
auth_request_set $name $upstream_http_remote_name;
|
||||
auth_request_set $email $upstream_http_remote_email;
|
||||
proxy_set_header Remote-User $user;
|
||||
proxy_set_header Remote-Groups $groups;
|
||||
proxy_set_header Remote-Name $name;
|
||||
proxy_set_header Remote-Email $email;
|
||||
# If Authelia returns 401, then nginx redirects the user to the login portal.
|
||||
# If it returns 200, then the request pass through to the backend.
|
||||
# For other type of errors, nginx will handle them as usual.
|
||||
error_page 401 =302 https://auth.cloonar.com/?rd=$target_url;
|
||||
'';
|
||||
};
|
||||
locations."/ws" = {
|
||||
proxyPass = "http://127.0.0.1:6042";
|
||||
proxyWebsockets = true;
|
||||
extraConfig =
|
||||
"proxy_read_timeout 86400;" +
|
||||
"send_timeout 300;"
|
||||
;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets = {
|
||||
zammad-db-password.sopsFile = ./secrets.yaml;
|
||||
zammad-key-base.owner = "zammad";
|
||||
};
|
||||
|
||||
services.postgresqlBackup.enable = true;
|
||||
services.postgresqlBackup.databases = [ "zammad" ];
|
||||
}
|
||||
40
hosts/web-arm/modules/zammad/secrets.yaml
Normal file
40
hosts/web-arm/modules/zammad/secrets.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
zammad-db-password: ENC[AES256_GCM,data:FFsTnwQcL8V1ZWvZ9a15FWcHnsrC7nuDW155reSmfg/IRhRfrtnvbCDQ0N3AMh7TBiyG3x5za/6orV04CplUgQ==,iv:inQXkwlTbGaKgU3nfOtIYMcheBdGv8xa7dCad8WrGEc=,tag:fxjNRCUpS6RMipk4D08new==,type:str]
|
||||
zammad-key-base: ENC[AES256_GCM,data:z2v1GrjRFoaDY9tPaAsUJPVRHZhSOrXWCZhhm5E6rmH4s6QWU1EW7aY4PPgditdcathLRWkDlBT5c3SQ8Cd2DPLp/SVn9Xd8w8g/lrplhNC2sJXUyB+CUgdEnBBN0XPMsFWNx9EIrqGrF/A8js5eKtQON9fCNytaHMOsCCc0rNE=,iv:oHKiXE9U0h846XVpCrcD/dFJ1MAXCYrnM80CwaWgALc=,tag:W88DsRWvdudMscH+UBPy/Q==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUc0RlQUt4VHU1eWZrdlF5
|
||||
UFhjSU5TWFlGbTIwbzVlaStHaWRTdS92d0YwCkJQRlh0eWVNRW9SdUFXQUZzNFYw
|
||||
dktoSmFqbWxDbXR0dDNTNy8zTHYwQUEKLS0tIFFwQkdvK2QvSmFGaVRBaVFMeEFi
|
||||
YUZ6b1dzUGZkL2t4aU5tTjA4UC9KU3cKmhugvvIexQqpVtGp7aLKU7WSQNxk0cTO
|
||||
+8MWF1v0mztJlGbiWk5OOzT9L8TO7GDGXfi8GyMVgVBvaA7tFF709w==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFZGRWbnVxVUdHWndEanlk
|
||||
Wmp4WS8yUjdrVSsxTHFNcjFUWm5IZytaZVRzCmorZTJRSnBRTE5qK2xiZGtYNXZH
|
||||
RjBDdWE5NjE3ZWtXRU5Fc2FaVFkzNUEKLS0tIGwvUjVBL2NpdTFsY04zbktJRGxF
|
||||
QWo1Vm56dnZWQ2l1K3hzVlZDL3BaTHMKw9CjtbS9hyW42prUhlTIcmcb4Z6OaxRr
|
||||
T7RJZxXefEr4myJYI5B3pqbXlBpSLLwS4lgtoqHmmYuSNjL8/xoksw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBicStLZGZvdGJyMyszMkFo
|
||||
S2xTeUM5ZEIrbUxqbXBxQTUyeHhJVTAzUm40Ck5KbngvdWYvVk5VYTRCUWhZeFkw
|
||||
eFJKVEZ3VnpuL3BmOFVQdCs2K3hoTUUKLS0tIEhFRXZyRlpPZUpEanFMU1oweVJ2
|
||||
RVJjc0FUb0NFMHk2M3gxTmhMYjlrTDgKR0tfq1CWU8OdeeigOsKqNx2sszVtPWjH
|
||||
yXcqe/jLAnvS/Ut/afEyfGYEiyyzJXLp9TGjV1fAp9y2K2noD8/TwQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2022-11-29T10:54:56Z"
|
||||
mac: ENC[AES256_GCM,data:OX49RTucGWdH1RkbXfkiMLH2Lj65v554WSfJxkCkIu/dFagCH90QSRiX/15HTsI//ffwKVurDivC6H6OByK2eWdaeCYTEn2029GjdL4RhJhXy0RLXEq5D/KVRu73O9Xe6M36asc/OenzPcmbHAvddD14y9vaOsVTL0H15ydVrwg=,iv:+uBt1Mvj+WMM4CvAOwmOXhZJVZBXVDCXA8iSXpdjktU=,tag:AeipsBJ8PA22OfUxXA8bIA==,type:str]
|
||||
pgp: []
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.7.3
|
||||
Reference in New Issue
Block a user