Compare commits
22 Commits
087753eb1e
...
f498b82b07
Author | SHA1 | Date |
---|---|---|
h7x4 | f498b82b07 | |
h7x4 | 9034a71927 | |
h7x4 | f85d18769f | |
h7x4 | b47a626427 | |
h7x4 | 4d65b9fd1d | |
h7x4 | f3e094520e | |
h7x4 | 69f98933a4 | |
h7x4 | bf2959c68d | |
h7x4 | 17f0268d12 | |
h7x4 | ebce0eb67a | |
h7x4 | b48230e811 | |
Daniel Olsen | 914eb35c5a | |
h7x4 | 8610a59f35 | |
h7x4 | bd42412b94 | |
Daniel Olsen | ef3b146b58 | |
h7x4 | bb4662b345 | |
h7x4 | 5b1c04e4b8 | |
h7x4 | 3fa7f67027 | |
h7x4 | b0f555667c | |
h7x4 | ef418bf125 | |
h7x4 | 945d53cdb4 | |
h7x4 | cf3b62e01e |
150
base.nix
150
base.nix
|
@ -1,150 +0,0 @@
|
|||
{ config, lib, pkgs, inputs, values, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./users
|
||||
./modules/snakeoil-certs.nix
|
||||
];
|
||||
|
||||
networking.domain = "pvv.ntnu.no";
|
||||
networking.useDHCP = false;
|
||||
# networking.search = [ "pvv.ntnu.no" "pvv.org" ];
|
||||
# networking.nameservers = lib.mkDefault [ "129.241.0.200" "129.241.0.201" ];
|
||||
# networking.tempAddresses = lib.mkDefault "disabled";
|
||||
# networking.defaultGateway = values.hosts.gateway;
|
||||
|
||||
systemd.network.enable = true;
|
||||
|
||||
services.resolved = {
|
||||
enable = lib.mkDefault true;
|
||||
dnssec = "false"; # Supposdly this keeps breaking and the default is to allow downgrades anyways...
|
||||
};
|
||||
|
||||
time.timeZone = "Europe/Oslo";
|
||||
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
console = {
|
||||
font = "Lat2-Terminus16";
|
||||
keyMap = "no";
|
||||
};
|
||||
|
||||
system.autoUpgrade = {
|
||||
enable = true;
|
||||
flake = "git+https://git.pvv.ntnu.no/Drift/pvv-nixos-config.git";
|
||||
flags = [
|
||||
"--update-input" "nixpkgs"
|
||||
"--update-input" "nixpkgs-unstable"
|
||||
"--no-write-lock-file"
|
||||
];
|
||||
};
|
||||
nix.gc.automatic = true;
|
||||
nix.gc.options = "--delete-older-than 2d";
|
||||
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
|
||||
/* This makes commandline tools like
|
||||
** nix run nixpkgs#hello
|
||||
** and nix-shell -p hello
|
||||
** use the same channel the system
|
||||
** was built with
|
||||
*/
|
||||
nix.registry = {
|
||||
nixpkgs.flake = inputs.nixpkgs;
|
||||
};
|
||||
nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
file
|
||||
git
|
||||
gnupg
|
||||
htop
|
||||
nano
|
||||
ripgrep
|
||||
rsync
|
||||
screen
|
||||
tmux
|
||||
vim
|
||||
wget
|
||||
|
||||
kitty.terminfo
|
||||
];
|
||||
|
||||
programs.zsh.enable = true;
|
||||
|
||||
users.groups."drift".name = "drift";
|
||||
|
||||
# Trusted users on the nix builder machines
|
||||
users.groups."nix-builder-users".name = "nix-builder-users";
|
||||
|
||||
# Let's not thermal throttle
|
||||
services.thermald.enable = lib.mkIf (lib.all (x: x) [
|
||||
(config.nixpkgs.system == "x86_64-linux")
|
||||
(!config.boot.isContainer or false)
|
||||
]) true;
|
||||
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
PubkeyAcceptedAlgorithms=+ssh-rsa
|
||||
Match Group wheel
|
||||
PasswordAuthentication no
|
||||
Match All
|
||||
'';
|
||||
settings.PermitRootLogin = "yes";
|
||||
};
|
||||
|
||||
# nginx return 444 for all nonexistent virtualhosts
|
||||
|
||||
systemd.services.nginx.after = [ "generate-snakeoil-certs.service" ];
|
||||
|
||||
environment.snakeoil-certs = lib.mkIf config.services.nginx.enable {
|
||||
"/etc/certs/nginx" = {
|
||||
owner = "nginx";
|
||||
group = "nginx";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
recommendedTlsSettings = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedGzipSettings = true;
|
||||
|
||||
appendConfig = ''
|
||||
pcre_jit on;
|
||||
worker_processes auto;
|
||||
worker_rlimit_nofile 100000;
|
||||
'';
|
||||
eventsConfig = ''
|
||||
worker_connections 2048;
|
||||
use epoll;
|
||||
multi_accept on;
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.nginx.serviceConfig = lib.mkIf config.services.nginx.enable {
|
||||
LimitNOFILE = 65536;
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."_" = lib.mkIf config.services.nginx.enable {
|
||||
sslCertificate = "/etc/certs/nginx.crt";
|
||||
sslCertificateKey = "/etc/certs/nginx.key";
|
||||
addSSL = true;
|
||||
extraConfig = "return 444;";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf config.services.nginx.enable [ 80 443 ];
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "drift@pvv.ntnu.no";
|
||||
};
|
||||
# Let's not spam LetsEncrypt in `nixos-rebuild build-vm` mode:
|
||||
virtualisation.vmVariant = {
|
||||
security.acme.defaults.server = "https://127.0.0.1";
|
||||
security.acme.preliminarySelfsigned = true;
|
||||
|
||||
users.users.root.initialPassword = "root";
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
{ pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
../users
|
||||
../modules/snakeoil-certs.nix
|
||||
|
||||
./networking.nix
|
||||
./nix.nix
|
||||
|
||||
./services/acme.nix
|
||||
./services/auto-upgrade.nix
|
||||
./services/irqbalance.nix
|
||||
./services/logrotate.nix
|
||||
./services/nginx.nix
|
||||
./services/openssh.nix
|
||||
./services/postfix.nix
|
||||
./services/smartd.nix
|
||||
./services/thermald.nix
|
||||
];
|
||||
|
||||
boot.tmp.cleanOnBoot = lib.mkDefault true;
|
||||
|
||||
time.timeZone = "Europe/Oslo";
|
||||
|
||||
i18n.defaultLocale = "en_US.UTF-8";
|
||||
console = {
|
||||
font = "Lat2-Terminus16";
|
||||
keyMap = "no";
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
file
|
||||
git
|
||||
gnupg
|
||||
htop
|
||||
nano
|
||||
ripgrep
|
||||
rsync
|
||||
screen
|
||||
tmux
|
||||
vim
|
||||
wget
|
||||
|
||||
kitty.terminfo
|
||||
];
|
||||
|
||||
programs.zsh.enable = true;
|
||||
|
||||
security.sudo.execWheelOnly = true;
|
||||
security.sudo.extraConfig = ''
|
||||
Defaults lecture = never
|
||||
'';
|
||||
|
||||
users.groups."drift".name = "drift";
|
||||
|
||||
# Trusted users on the nix builder machines
|
||||
users.groups."nix-builder-users".name = "nix-builder-users";
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{ lib, values, ... }:
|
||||
{
|
||||
networking.domain = "pvv.ntnu.no";
|
||||
networking.useDHCP = false;
|
||||
# networking.search = [ "pvv.ntnu.no" "pvv.org" ];
|
||||
# networking.nameservers = lib.mkDefault [ "129.241.0.200" "129.241.0.201" ];
|
||||
# networking.tempAddresses = lib.mkDefault "disabled";
|
||||
# networking.defaultGateway = values.hosts.gateway;
|
||||
|
||||
systemd.network.enable = true;
|
||||
|
||||
services.resolved = {
|
||||
enable = lib.mkDefault true;
|
||||
dnssec = "false"; # Supposdly this keeps breaking and the default is to allow downgrades anyways...
|
||||
};
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{ inputs, ... }:
|
||||
{
|
||||
nix = {
|
||||
gc = {
|
||||
automatic = true;
|
||||
options = "--delete-older-than 2d";
|
||||
};
|
||||
|
||||
settings = {
|
||||
allow-dirty = true;
|
||||
auto-optimise-store = true;
|
||||
builders-use-substitutes = true;
|
||||
experimental-features = [ "nix-command" "flakes" ];
|
||||
log-lines = 50;
|
||||
use-xdg-base-directories = true;
|
||||
};
|
||||
|
||||
/* This makes commandline tools like
|
||||
** nix run nixpkgs#hello
|
||||
** and nix-shell -p hello
|
||||
** use the same channel the system
|
||||
** was built with
|
||||
*/
|
||||
registry = {
|
||||
"nixpkgs".flake = inputs.nixpkgs;
|
||||
"pvv-nix".flake = inputs.self;
|
||||
};
|
||||
nixPath = [ "nixpkgs=${inputs.nixpkgs}" ];
|
||||
};
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{ ... }:
|
||||
{
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "drift@pvv.ntnu.no";
|
||||
};
|
||||
|
||||
# Let's not spam LetsEncrypt in `nixos-rebuild build-vm` mode:
|
||||
virtualisation.vmVariant = {
|
||||
security.acme.defaults.server = "https://127.0.0.1";
|
||||
security.acme.preliminarySelfsigned = true;
|
||||
|
||||
users.users.root.initialPassword = "root";
|
||||
};
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{ ... }:
|
||||
{
|
||||
system.autoUpgrade = {
|
||||
enable = true;
|
||||
flake = "git+https://git.pvv.ntnu.no/Drift/pvv-nixos-config.git";
|
||||
flags = [
|
||||
"--update-input" "nixpkgs"
|
||||
"--update-input" "nixpkgs-unstable"
|
||||
"--no-write-lock-file"
|
||||
];
|
||||
};
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{ ... }:
|
||||
{
|
||||
services.irqbalance.enable = true;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
{ ... }:
|
||||
{
|
||||
# source: https://github.com/logrotate/logrotate/blob/main/examples/logrotate.service
|
||||
systemd.services.logrotate = {
|
||||
documentation = [ "man:logrotate(8)" "man:logrotate.conf(5)" ];
|
||||
unitConfig.RequiresMountsFor = "/var/log";
|
||||
serviceConfig = {
|
||||
Nice = 19;
|
||||
IOSchedulingClass = "best-effort";
|
||||
IOSchedulingPriority = 7;
|
||||
|
||||
ReadWritePaths = [ "/var/log" ];
|
||||
|
||||
AmbientCapabilities = [ "" ];
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
DeviceAllow = [ "" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true; # disable for third party rotate scripts
|
||||
PrivateDevices = true;
|
||||
PrivateNetwork = true; # disable for mail delivery
|
||||
PrivateTmp = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true; # disable for userdir logs
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "full";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true; # disable for creating setgid directories
|
||||
SocketBindDeny = [ "any" ];
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
{ config, lib, ... }:
|
||||
{
|
||||
# nginx return 444 for all nonexistent virtualhosts
|
||||
|
||||
systemd.services.nginx.after = [ "generate-snakeoil-certs.service" ];
|
||||
|
||||
environment.snakeoil-certs = lib.mkIf config.services.nginx.enable {
|
||||
"/etc/certs/nginx" = {
|
||||
owner = "nginx";
|
||||
group = "nginx";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf config.services.nginx.enable [ 80 443 ];
|
||||
|
||||
services.nginx = {
|
||||
recommendedTlsSettings = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedGzipSettings = true;
|
||||
|
||||
appendConfig = ''
|
||||
pcre_jit on;
|
||||
worker_processes auto;
|
||||
worker_rlimit_nofile 100000;
|
||||
'';
|
||||
eventsConfig = ''
|
||||
worker_connections 2048;
|
||||
use epoll;
|
||||
multi_accept on;
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.nginx.serviceConfig = lib.mkIf config.services.nginx.enable {
|
||||
LimitNOFILE = 65536;
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."_" = lib.mkIf config.services.nginx.enable {
|
||||
sslCertificate = "/etc/certs/nginx.crt";
|
||||
sslCertificateKey = "/etc/certs/nginx.key";
|
||||
addSSL = true;
|
||||
extraConfig = "return 444;";
|
||||
};
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{ ... }:
|
||||
{
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
startWhenNeeded = true;
|
||||
extraConfig = ''
|
||||
PubkeyAcceptedAlgorithms=+ssh-rsa
|
||||
Match Group wheel
|
||||
PasswordAuthentication no
|
||||
Match All
|
||||
'';
|
||||
settings.PermitRootLogin = "yes";
|
||||
};
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
cfg = config.services.postfix;
|
||||
in
|
||||
{
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
|
||||
hostname = "${config.networking.hostName}.pvv.ntnu.no";
|
||||
domain = "pvv.ntnu.no";
|
||||
|
||||
relayHost = "smtp.pvv.ntnu.no";
|
||||
relayPort = 465;
|
||||
|
||||
config = {
|
||||
smtp_tls_wrappermode = "yes";
|
||||
smtp_tls_security_level = "encrypt";
|
||||
};
|
||||
|
||||
# Nothing should be delivered to this machine
|
||||
destination = [ ];
|
||||
};
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
{
|
||||
services.smartd.enable = lib.mkDefault true;
|
||||
|
||||
environment.systemPackages = lib.optionals config.services.smartd.enable (with pkgs; [
|
||||
smartmontools
|
||||
]);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{ config, lib, ... }:
|
||||
{
|
||||
# Let's not thermal throttle
|
||||
services.thermald.enable = lib.mkIf (lib.all (x: x) [
|
||||
(config.nixpkgs.system == "x86_64-linux")
|
||||
(!config.boot.isContainer or false)
|
||||
]) true;
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
imports = [
|
||||
./hardware-configuration.nix
|
||||
|
||||
../../base.nix
|
||||
../../base
|
||||
../../misc/metrics-exporters.nix
|
||||
|
||||
./services/gitea/default.nix
|
||||
|
@ -11,6 +11,7 @@
|
|||
./services/kerberos
|
||||
./services/mediawiki
|
||||
./services/nginx.nix
|
||||
./services/phpfpm.nix
|
||||
./services/vaultwarden.nix
|
||||
./services/webmail
|
||||
./services/website
|
||||
|
@ -31,6 +32,8 @@
|
|||
address = with values.hosts.bekkalokk; [ (ipv4 + "/25") (ipv6 + "/64") ];
|
||||
};
|
||||
|
||||
services.btrfs.autoScrub.enable = true;
|
||||
|
||||
# Do not change, even during upgrades.
|
||||
# See https://search.nixos.org/options?show=system.stateVersion
|
||||
system.stateVersion = "22.11";
|
||||
|
|
|
@ -6,7 +6,8 @@ let
|
|||
in {
|
||||
imports = [
|
||||
./ci.nix
|
||||
./import-users.nix
|
||||
./import-users
|
||||
./web-secret-provider
|
||||
];
|
||||
|
||||
sops.secrets = {
|
||||
|
@ -58,6 +59,7 @@ in {
|
|||
service = {
|
||||
DISABLE_REGISTRATION = true;
|
||||
ENABLE_NOTIFY_MAIL = true;
|
||||
AUTO_WATCH_NEW_REPOS = false;
|
||||
};
|
||||
admin.DEFAULT_EMAIL_NOTIFICATIONS = "onmention";
|
||||
session.COOKIE_SECURE = true;
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
import requests
|
||||
import secrets
|
||||
import os
|
||||
|
||||
EMAIL_DOMAIN = os.getenv('EMAIL_DOMAIN')
|
||||
if EMAIL_DOMAIN is None:
|
||||
EMAIL_DOMAIN = 'pvv.ntnu.no'
|
||||
|
||||
API_TOKEN = os.getenv('API_TOKEN')
|
||||
if API_TOKEN is None:
|
||||
raise Exception('API_TOKEN not set')
|
||||
|
||||
GITEA_API_URL = os.getenv('GITEA_API_URL')
|
||||
if GITEA_API_URL is None:
|
||||
GITEA_API_URL = 'https://git.pvv.ntnu.no/api/v1'
|
||||
|
||||
BANNED_SHELLS = [
|
||||
"/usr/bin/nologin",
|
||||
"/usr/sbin/nologin",
|
||||
"/sbin/nologin",
|
||||
"/bin/false",
|
||||
"/bin/msgsh",
|
||||
]
|
||||
|
||||
existing_users = {}
|
||||
|
||||
|
||||
# This function should only ever be called when adding users
|
||||
# from the passwd file
|
||||
def add_user(username, name):
|
||||
user = {
|
||||
"full_name": name,
|
||||
"username": username,
|
||||
"login_name": username,
|
||||
"source_id": 1, # 1 = SMTP
|
||||
}
|
||||
|
||||
if username not in existing_users:
|
||||
user["password"] = secrets.token_urlsafe(32)
|
||||
user["must_change_password"] = False
|
||||
user["visibility"] = "private"
|
||||
user["email"] = username + '@' + EMAIL_DOMAIN
|
||||
|
||||
r = requests.post(GITEA_API_URL + '/admin/users', json=user,
|
||||
headers={'Authorization': 'token ' + API_TOKEN})
|
||||
if r.status_code != 201:
|
||||
print('ERR: Failed to create user ' + username + ': ' + r.text)
|
||||
return
|
||||
|
||||
print('Created user ' + username)
|
||||
existing_users[username] = user
|
||||
|
||||
else:
|
||||
user["visibility"] = existing_users[username]["visibility"]
|
||||
r = requests.patch(GITEA_API_URL + f'/admin/users/{username}',
|
||||
json=user,
|
||||
headers={'Authorization': 'token ' + API_TOKEN})
|
||||
if r.status_code != 200:
|
||||
print('ERR: Failed to update user ' + username + ': ' + r.text)
|
||||
return
|
||||
|
||||
print('Updated user ' + username)
|
||||
|
||||
|
||||
def main():
|
||||
# Fetch existing users
|
||||
r = requests.get(GITEA_API_URL + '/admin/users',
|
||||
headers={'Authorization': 'token ' + API_TOKEN})
|
||||
|
||||
if r.status_code != 200:
|
||||
raise Exception('Failed to get users: ' + r.text)
|
||||
|
||||
for user in r.json():
|
||||
existing_users[user['login']] = user
|
||||
|
||||
# Read the file, add each user
|
||||
with open("/tmp/passwd-import", 'r') as f:
|
||||
for line in f.readlines():
|
||||
uid = int(line.split(':')[2])
|
||||
if uid < 1000:
|
||||
continue
|
||||
|
||||
shell = line.split(':')[-1]
|
||||
if shell in BANNED_SHELLS:
|
||||
continue
|
||||
|
||||
username = line.split(':')[0]
|
||||
name = line.split(':')[4].split(',')[0]
|
||||
|
||||
add_user(username, name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -14,6 +14,9 @@ in
|
|||
preStart=''${pkgs.rsync}/bin/rsync -e "${pkgs.openssh}/bin/ssh -o UserKnownHostsFile=$CREDENTIALS_DIRECTORY/ssh-known-hosts -i $CREDENTIALS_DIRECTORY/sshkey" -a pvv@smtp.pvv.ntnu.no:/etc/passwd /tmp/passwd-import'';
|
||||
serviceConfig = {
|
||||
ExecStart = pkgs.writers.writePython3 "gitea-import-users" {
|
||||
flakeIgnore = [
|
||||
"E501" # Line over 80 chars lol
|
||||
];
|
||||
libraries = with pkgs.python3Packages; [ requests ];
|
||||
} (builtins.readFile ./gitea-import-users.py);
|
||||
LoadCredential=[
|
|
@ -0,0 +1,198 @@
|
|||
import requests
|
||||
import secrets
|
||||
import os
|
||||
|
||||
|
||||
EMAIL_DOMAIN = os.getenv('EMAIL_DOMAIN')
|
||||
if EMAIL_DOMAIN is None:
|
||||
EMAIL_DOMAIN = 'pvv.ntnu.no'
|
||||
|
||||
|
||||
API_TOKEN = os.getenv('API_TOKEN')
|
||||
if API_TOKEN is None:
|
||||
raise Exception('API_TOKEN not set')
|
||||
|
||||
|
||||
GITEA_API_URL = os.getenv('GITEA_API_URL')
|
||||
if GITEA_API_URL is None:
|
||||
GITEA_API_URL = 'https://git.pvv.ntnu.no/api/v1'
|
||||
|
||||
|
||||
def gitea_list_all_users() -> dict[str, dict[str, any]] | None:
|
||||
r = requests.get(
|
||||
GITEA_API_URL + '/admin/users',
|
||||
headers={'Authorization': 'token ' + API_TOKEN}
|
||||
)
|
||||
|
||||
if r.status_code != 200:
|
||||
print('Failed to get users:', r.text)
|
||||
return None
|
||||
|
||||
return {user['login']: user for user in r.json()}
|
||||
|
||||
|
||||
def gitea_create_user(username: str, userdata: dict[str, any]) -> bool:
|
||||
r = requests.post(
|
||||
GITEA_API_URL + '/admin/users',
|
||||
json=userdata,
|
||||
headers={'Authorization': 'token ' + API_TOKEN},
|
||||
)
|
||||
|
||||
if r.status_code != 201:
|
||||
print(f'ERR: Failed to create user {username}:', r.text)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def gitea_edit_user(username: str, userdata: dict[str, any]) -> bool:
|
||||
r = requests.patch(
|
||||
GITEA_API_URL + f'/admin/users/{username}',
|
||||
json=userdata,
|
||||
headers={'Authorization': 'token ' + API_TOKEN},
|
||||
)
|
||||
|
||||
if r.status_code != 200:
|
||||
print(f'ERR: Failed to update user {username}:', r.text)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def gitea_list_teams_for_organization(org: str) -> dict[str, any] | None:
|
||||
r = requests.get(
|
||||
GITEA_API_URL + f'/orgs/{org}/teams',
|
||||
headers={'Authorization': 'token ' + API_TOKEN},
|
||||
)
|
||||
|
||||
if r.status_code != 200:
|
||||
print(f"ERR: Failed to list teams for {org}:", r.text)
|
||||
return None
|
||||
|
||||
return {team['name']: team for team in r.json()}
|
||||
|
||||
|
||||
def gitea_add_user_to_organization_team(username: str, team_id: int) -> bool:
|
||||
r = requests.put(
|
||||
GITEA_API_URL + f'/teams/{team_id}/members/{username}',
|
||||
headers={'Authorization': 'token ' + API_TOKEN},
|
||||
)
|
||||
|
||||
if r.status_code != 204:
|
||||
print(f'ERR: Failed to add user {username} to org team {team_id}:', r.text)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# If a passwd user has one of the following shells,
|
||||
# it is most likely not a PVV user, but rather a system user.
|
||||
# Users with these shells should thus be ignored.
|
||||
BANNED_SHELLS = [
|
||||
"/usr/bin/nologin",
|
||||
"/usr/sbin/nologin",
|
||||
"/sbin/nologin",
|
||||
"/bin/false",
|
||||
"/bin/msgsh",
|
||||
]
|
||||
|
||||
|
||||
# Reads out a passwd-file line for line, and filters out
|
||||
# real PVV users (as opposed to system users meant for daemons and such)
|
||||
def passwd_file_parser(passwd_path):
|
||||
with open(passwd_path, 'r') as f:
|
||||
for line in f.readlines():
|
||||
uid = int(line.split(':')[2])
|
||||
if uid < 1000:
|
||||
continue
|
||||
|
||||
shell = line.split(':')[-1]
|
||||
if shell in BANNED_SHELLS:
|
||||
continue
|
||||
|
||||
username = line.split(':')[0]
|
||||
name = line.split(':')[4].split(',')[0]
|
||||
yield (username, name)
|
||||
|
||||
|
||||
# This function either creates a new user in gitea
|
||||
# and fills it out with some default information if
|
||||
# it does not exist, or ensures that the default information
|
||||
# is correct if the user already exists. All user information
|
||||
# (including non-default fields) is pulled from gitea and added
|
||||
# to the `existing_users` dict
|
||||
def add_or_patch_gitea_user(
|
||||
username: str,
|
||||
name: str,
|
||||
existing_users: dict[str, dict[str, any]],
|
||||
) -> None:
|
||||
user = {
|
||||
"full_name": name,
|
||||
"username": username,
|
||||
"login_name": username,
|
||||
"source_id": 1, # 1 = SMTP
|
||||
}
|
||||
|
||||
if username not in existing_users:
|
||||
user["password"] = secrets.token_urlsafe(32)
|
||||
user["must_change_password"] = False
|
||||
user["visibility"] = "private"
|
||||
user["email"] = username + '@' + EMAIL_DOMAIN
|
||||
|
||||
if not gitea_create_user(username, user):
|
||||
return
|
||||
|
||||
print('Created user', username)
|
||||
existing_users[username] = user
|
||||
|
||||
else:
|
||||
user["visibility"] = existing_users[username]["visibility"]
|
||||
|
||||
if not gitea_edit_user(username, user):
|
||||
return
|
||||
|
||||
print('Updated user', username)
|
||||
|
||||
|
||||
# This function adds a user to a gitea team (part of organization)
|
||||
# if the user is not already part of said team.
|
||||
def ensure_gitea_user_is_part_of_team(
|
||||
username: str,
|
||||
org: str,
|
||||
team_name: str,
|
||||
) -> None:
|
||||
teams = gitea_list_teams_for_organization(org)
|
||||
|
||||
if teams is None:
|
||||
return
|
||||
|
||||
if team_name not in teams:
|
||||
print(f'ERR: could not find team "{team_name}" in organization "{org}"')
|
||||
|
||||
gitea_add_user_to_organization_team(username, teams[team_name]['id'])
|
||||
|
||||
print(f'User {username} is now part of {org}/{team_name}')
|
||||
|
||||
|
||||
# List of teams that all users should be part of by default
|
||||
COMMON_USER_TEAMS = [
|
||||
("Projects", "Members"),
|
||||
("Kurs", "Members"),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
existing_users = gitea_list_all_users()
|
||||
if existing_users is None:
|
||||
exit(1)
|
||||
|
||||
for username, name in passwd_file_parser("/tmp/passwd-import"):
|
||||
print(f"Processing {username}")
|
||||
add_or_patch_gitea_user(username, name, existing_users)
|
||||
for org, team_name in COMMON_USER_TEAMS:
|
||||
ensure_gitea_user_is_part_of_team(username, org, team_name)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,114 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
organizations = [
|
||||
"Drift"
|
||||
"Projects"
|
||||
"Kurs"
|
||||
];
|
||||
|
||||
giteaCfg = config.services.gitea;
|
||||
|
||||
giteaWebSecretProviderScript = pkgs.writers.writePython3 "gitea-web-secret-provider" {
|
||||
libraries = with pkgs.python3Packages; [ requests ];
|
||||
flakeIgnore = [
|
||||
"E501" # Line over 80 chars lol
|
||||
"E201" # "whitespace after {"
|
||||
"E202" # "whitespace after }"
|
||||
"E251" # unexpected spaces around keyword / parameter equals
|
||||
"W391" # Newline at end of file
|
||||
];
|
||||
makeWrapperArgs = [
|
||||
"--prefix PATH : ${(lib.makeBinPath [ pkgs.openssh ])}"
|
||||
];
|
||||
} (builtins.readFile ./gitea-web-secret-provider.py);
|
||||
in
|
||||
{
|
||||
users.groups."gitea-web" = { };
|
||||
users.users."gitea-web" = {
|
||||
group = "gitea-web";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
sops.secrets."gitea/web-secret-provider/token" = {
|
||||
owner = "gitea-web";
|
||||
group = "gitea-web";
|
||||
restartUnits = [
|
||||
"gitea-web-secret-provider@"
|
||||
] ++ (map (org: "gitea-web-secret-provider@${org}") organizations);
|
||||
};
|
||||
|
||||
systemd.slices.system-giteaweb = {
|
||||
description = "Gitea web directories";
|
||||
};
|
||||
|
||||
# https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#Specifiers
|
||||
# %i - instance name (after the @)
|
||||
# %d - secrets directory
|
||||
systemd.services."gitea-web-secret-provider@" = {
|
||||
description = "Ensure all repos in %i has an SSH key to push web content";
|
||||
requires = [ "gitea.service" "network.target" ];
|
||||
serviceConfig = {
|
||||
Slice = "system-giteaweb.slice";
|
||||
Type = "oneshot";
|
||||
ExecStart = let
|
||||
args = lib.cli.toGNUCommandLineShell { } {
|
||||
org = "%i";
|
||||
token-path = "%d/token";
|
||||
api-url = "${giteaCfg.settings.server.ROOT_URL}api/v1";
|
||||
key-dir = "/var/lib/gitea-web/keys/%i";
|
||||
authorized-keys-path = "/var/lib/gitea-web/authorized_keys.d/%i";
|
||||
rrsync-script = pkgs.writeShellScript "rrsync-chown" ''
|
||||
${lib.getExe pkgs.rrsync} -wo "$1"
|
||||
${pkgs.coreutils}/bin/chown -R gitea-web:gitea-web "$1"
|
||||
'';
|
||||
web-dir = "/var/lib/gitea-web/web";
|
||||
};
|
||||
in "${giteaWebSecretProviderScript} ${args}";
|
||||
|
||||
User = "gitea-web";
|
||||
Group = "gitea-web";
|
||||
|
||||
StateDirectory = "gitea-web";
|
||||
StateDirectoryMode = "0750";
|
||||
LoadCredential = [
|
||||
"token:${config.sops.secrets."gitea/web-secret-provider/token".path}"
|
||||
];
|
||||
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectSystem = true;
|
||||
ProtectHome = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
LockPersonality = true;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers."gitea-web-secret-provider@" = {
|
||||
description = "Ensure all repos in %i has an SSH key to push web content";
|
||||
timerConfig = {
|
||||
RandomizedDelaySec = "1h";
|
||||
Persistent = true;
|
||||
Unit = "gitea-web-secret-provider@%i.service";
|
||||
OnCalendar = "daily";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.targets.timers.wants = map (org: "gitea-web-secret-provider@${org}.timer") organizations;
|
||||
|
||||
services.openssh.authorizedKeysFiles = map (org: "/var/lib/gitea-web/authorized_keys.d/${org}") organizations;
|
||||
|
||||
users.users.nginx.extraGroups = [ "gitea-web" ];
|
||||
services.nginx.virtualHosts."pages.pvv.ntnu.no" = {
|
||||
kTLS = true;
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
root = "/var/lib/gitea-web/web";
|
||||
};
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
import requests
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Generate SSH keys for Gitea repositories and add them as secrets")
|
||||
parser.add_argument("--org", required=True, type=str, help="The organization to generate keys for")
|
||||
parser.add_argument("--token-path", metavar='PATH', required=True, type=Path, help="Path to a file containing the Gitea API token")
|
||||
parser.add_argument("--api-url", metavar='URL', type=str, help="The URL of the Gitea API", default="https://git.pvv.ntnu.no/api/v1")
|
||||
parser.add_argument("--key-dir", metavar='PATH', type=Path, help="The directory to store the generated keys in", default="/run/gitea-web-secret-provider")
|
||||
parser.add_argument("--authorized-keys-path", metavar='PATH', type=Path, help="The path to the resulting authorized_keys file", default="/etc/ssh/authorized_keys.d/gitea-web-secret-provider")
|
||||
parser.add_argument("--rrsync-script", metavar='PATH', type=Path, help="The path to a rrsync script, taking the destination path as its single argument")
|
||||
parser.add_argument("--web-dir", metavar='PATH', type=Path, help="The directory to sync the repositories to", default="/var/www")
|
||||
parser.add_argument("--force", action="store_true", help="Overwrite existing keys")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def add_secret(args: argparse.Namespace, token: str, repo: str, name: str, secret: str):
|
||||
result = requests.put(
|
||||
f"{args.api_url}/repos/{args.org}/{repo}/actions/secrets/{name}",
|
||||
json = { 'data': secret },
|
||||
headers = { 'Authorization': 'token ' + token },
|
||||
)
|
||||
if result.status_code not in (201, 204):
|
||||
raise Exception(f"Failed to add secret: {result.json()}")
|
||||
|
||||
|
||||
def get_org_repo_list(args: argparse.Namespace, token: str):
|
||||
result = requests.get(
|
||||
f"{args.api_url}/orgs/{args.org}/repos",
|
||||
headers = { 'Authorization': 'token ' + token },
|
||||
)
|
||||
return [repo["name"] for repo in result.json()]
|
||||
|
||||
|
||||
def generate_ssh_key(args: argparse.Namespace, repository: str):
|
||||
keyname = hashlib.sha256(args.org.encode() + repository.encode()).hexdigest()
|
||||
key_path = args.key_dir / keyname
|
||||
if not key_path.is_file() or args.force:
|
||||
subprocess.run(
|
||||
[
|
||||
"ssh-keygen",
|
||||
*("-t", "ed25519"),
|
||||
*("-f", key_path),
|
||||
*("-N", ""),
|
||||
*("-C", f"{args.org}/{repository}"),
|
||||
],
|
||||
check=True,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
print(f"Generated SSH key for `{args.org}/{repository}`")
|
||||
|
||||
with open(key_path, "r") as f:
|
||||
private_key = f.read()
|
||||
|
||||
pub_key_path = args.key_dir / (keyname + '.pub')
|
||||
with open(pub_key_path, "r") as f:
|
||||
public_key = f.read()
|
||||
|
||||
return private_key, public_key
|
||||
|
||||
|
||||
SSH_OPTS = ",".join([
|
||||
"restrict",
|
||||
"no-agent-forwarding",
|
||||
"no-port-forwarding",
|
||||
"no-pty",
|
||||
"no-X11-forwarding",
|
||||
])
|
||||
|
||||
|
||||
def generate_authorized_keys(args: argparse.Namespace, repo_public_keys: list[tuple[str, str]]):
|
||||
lines = []
|
||||
for repo, public_key in repo_public_keys:
|
||||
command = f"{args.rrsync_script} {args.web_dir}/{args.org}/{repo}"
|
||||
lines.append(f'command="{command}",{SSH_OPTS} {public_key}')
|
||||
|
||||
with open(args.authorized_keys_path, "w") as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
with open(args.token_path, "r") as f:
|
||||
token = f.read().strip()
|
||||
|
||||
os.makedirs(args.key_dir, 0o700, exist_ok=True)
|
||||
os.makedirs(args.authorized_keys_path.parent, 0o700, exist_ok=True)
|
||||
|
||||
repos = get_org_repo_list(args, token)
|
||||
print(f'Found {len(repos)} repositories in `{args.org}`')
|
||||
|
||||
repo_public_keys = []
|
||||
for repo in repos:
|
||||
print(f"Locating key for `{args.org}/{repo}`")
|
||||
private_key, public_key = generate_ssh_key(args, repo)
|
||||
add_secret(args, token, repo, "WEB_SYNC_SSH_KEY", private_key)
|
||||
repo_public_keys.append((repo, public_key))
|
||||
|
||||
generate_authorized_keys(args, repo_public_keys)
|
||||
print(f"Wrote authorized_keys file to `{args.authorized_keys_path}`")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,51 @@
|
|||
{ lib, ... }:
|
||||
let
|
||||
pools = map (pool: "phpfpm-${pool}") [
|
||||
"idp"
|
||||
"mediawiki"
|
||||
"pvv-nettsiden"
|
||||
"roundcube"
|
||||
"snappymail"
|
||||
];
|
||||
in
|
||||
{
|
||||
# Source: https://www.pierreblazquez.com/2023/06/17/how-to-harden-apache-php-fpm-daemons-using-systemd/
|
||||
systemd.services = lib.genAttrs pools (_: {
|
||||
serviceConfig = let
|
||||
caps = [
|
||||
"CAP_NET_BIND_SERVICE"
|
||||
"CAP_SETGID"
|
||||
"CAP_SETUID"
|
||||
"CAP_CHOWN"
|
||||
"CAP_KILL"
|
||||
"CAP_IPC_LOCK"
|
||||
"CAP_DAC_OVERRIDE"
|
||||
];
|
||||
in {
|
||||
AmbientCapabilities = caps;
|
||||
CapabilityBoundingSet = caps;
|
||||
DeviceAllow = [ "" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = false;
|
||||
NoNewPrivileges = true;
|
||||
PrivateMounts = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
RemoveIPC = true;
|
||||
UMask = "0077";
|
||||
RestrictNamespaces = "~mnt";
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
KeyringMode = "private";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
|
@ -65,4 +65,40 @@ in {
|
|||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.vaultwarden = lib.mkIf cfg.enable {
|
||||
serviceConfig = {
|
||||
AmbientCapabilities = [ "" ];
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
DeviceAllow = [ "" ];
|
||||
LockPersonality = true;
|
||||
NoNewPrivileges = true;
|
||||
# MemoryDenyWriteExecute = true;
|
||||
PrivateMounts = true;
|
||||
PrivateUsers = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
];
|
||||
RemoveIPC = true;
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
];
|
||||
UMask = "0007";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
imports = [
|
||||
./hardware-configuration.nix
|
||||
|
||||
../../base.nix
|
||||
../../base
|
||||
../../misc/metrics-exporters.nix
|
||||
./services/nginx
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
services.mjolnir = {
|
||||
enable = true;
|
||||
pantalaimon.enable = false;
|
||||
homeserverUrl = "http://127.0.0.1:8008";
|
||||
homeserverUrl = "https://matrix.pvv.ntnu.no";
|
||||
accessTokenFile = config.sops.secrets."matrix/mjolnir/access_token".path;
|
||||
managementRoom = "!gsdeCoWjvYRBrzuiRq:pvv.ntnu.no";
|
||||
protectedRooms = map (a: "https://matrix.to/#/${a}") [
|
||||
|
|
|
@ -157,6 +157,18 @@ in {
|
|||
'';
|
||||
};
|
||||
}
|
||||
{
|
||||
locations."/_synapse/admin" = {
|
||||
proxyPass = "http://$synapse_backend";
|
||||
extraConfig = ''
|
||||
allow 127.0.0.1;
|
||||
allow ::1;
|
||||
allow ${values.hosts.bicep.ipv4};
|
||||
allow ${values.hosts.bicep.ipv6};
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
}
|
||||
{
|
||||
locations = let
|
||||
connectionInfo = w: matrix-lib.workerConnectionResource "metrics" w;
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{ pkgs, lib, config, values, ... }:
|
||||
let
|
||||
backupDir = "/var/lib/mysql/backups";
|
||||
in
|
||||
{
|
||||
sops.secrets."mysql/password" = {
|
||||
owner = "mysql";
|
||||
|
@ -36,11 +39,6 @@
|
|||
}];
|
||||
};
|
||||
|
||||
services.mysqlBackup = {
|
||||
enable = true;
|
||||
location = "/var/lib/mysql/backups";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 3306 ];
|
||||
|
||||
systemd.services.mysql.serviceConfig = {
|
||||
|
@ -50,4 +48,51 @@
|
|||
values.ipv6-space
|
||||
];
|
||||
};
|
||||
|
||||
# NOTE: instead of having the upstream nixpkgs postgres backup unit trigger
|
||||
# another unit, it was easier to just make one ourselves
|
||||
systemd.services."backup-mysql" = {
|
||||
description = "Backup MySQL data";
|
||||
requires = [ "mysql.service" ];
|
||||
|
||||
path = [
|
||||
pkgs.coreutils
|
||||
pkgs.rsync
|
||||
pkgs.gzip
|
||||
config.services.mysql.package
|
||||
];
|
||||
|
||||
script = let
|
||||
rotations = 10;
|
||||
sshTarget1 = "root@isvegg.pvv.ntnu.no:/mnt/backup1/bicep/mysql";
|
||||
sshTarget2 = "root@isvegg.pvv.ntnu.no:/mnt/backup2/bicep/mysql";
|
||||
in ''
|
||||
set -eo pipefail
|
||||
|
||||
mysqldump | gzip -c -9 --rsyncable > "${backupDir}/$(date --iso-8601)-dump.sql.gz"
|
||||
|
||||
while [ $(ls -1 "${backupDir}" | wc -l) -gt ${toString rotations} ]; do
|
||||
rm $(find "${backupDir}" -type f -printf '%T+ %p\n' | sort | head -n 1 | cut -d' ' -f2)
|
||||
done
|
||||
|
||||
rsync -avz --delete "${backupDir}" '${sshTarget1}'
|
||||
rsync -avz --delete "${backupDir}" '${sshTarget2}'
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "mysql";
|
||||
Group = "mysql";
|
||||
UMask = "0077";
|
||||
ReadWritePaths = [ backupDir ];
|
||||
};
|
||||
|
||||
startAt = "*-*-* 02:15:00";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."10-mysql-backup".${backupDir}.d = {
|
||||
user = "mysql";
|
||||
group = "mysql";
|
||||
mode = "700";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ config, pkgs, ... }:
|
||||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
sslCert = config.security.acme.certs."postgres.pvv.ntnu.no";
|
||||
backupDir = "/var/lib/postgresql/backups";
|
||||
in
|
||||
{
|
||||
services.postgresql = {
|
||||
|
@ -79,19 +79,64 @@ in
|
|||
|
||||
systemd.services.postgresql.serviceConfig = {
|
||||
LoadCredential = [
|
||||
"cert:${sslCert.directory}/cert.pem"
|
||||
"key:${sslCert.directory}/key.pem"
|
||||
"cert:/etc/certs/postgres.crt"
|
||||
"key:/etc/certs/postgres.key"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.acme.members = [ "postgres" ];
|
||||
environment.snakeoil-certs."/etc/certs/postgres" = {
|
||||
owner = "postgres";
|
||||
group = "postgres";
|
||||
subject = "/C=NO/O=Programvareverkstedet/CN=postgres.pvv.ntnu.no/emailAddress=drift@pvv.ntnu.no";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 5432 ];
|
||||
networking.firewall.allowedUDPPorts = [ 5432 ];
|
||||
|
||||
services.postgresqlBackup = {
|
||||
enable = true;
|
||||
location = "/var/lib/postgres/backups";
|
||||
backupAll = true;
|
||||
# NOTE: instead of having the upstream nixpkgs postgres backup unit trigger
|
||||
# another unit, it was easier to just make one ourselves
|
||||
systemd.services."backup-postgresql" = {
|
||||
description = "Backup PostgreSQL data";
|
||||
requires = [ "postgresql.service" ];
|
||||
|
||||
path = [
|
||||
pkgs.coreutils
|
||||
pkgs.rsync
|
||||
pkgs.gzip
|
||||
config.services.postgresql.package
|
||||
];
|
||||
|
||||
script = let
|
||||
rotations = 10;
|
||||
sshTarget1 = "root@isvegg.pvv.ntnu.no:/mnt/backup1/bicep/postgresql";
|
||||
sshTarget2 = "root@isvegg.pvv.ntnu.no:/mnt/backup2/bicep/postgresql";
|
||||
in ''
|
||||
set -eo pipefail
|
||||
|
||||
pg_dumpall -U postgres | gzip -c -9 --rsyncable > "${backupDir}/$(date --iso-8601)-dump.sql.gz"
|
||||
|
||||
while [ $(ls -1 "${backupDir}" | wc -l) -gt ${toString rotations} ]; do
|
||||
rm $(find "${backupDir}" -type f -printf '%T+ %p\n' | sort | head -n 1 | cut -d' ' -f2)
|
||||
done
|
||||
|
||||
rsync -avz --delete "${backupDir}" '${sshTarget1}'
|
||||
rsync -avz --delete "${backupDir}" '${sshTarget2}'
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "postgres";
|
||||
Group = "postgres";
|
||||
UMask = "0077";
|
||||
ReadWritePaths = [ backupDir ];
|
||||
};
|
||||
|
||||
startAt = "*-*-* 01:15:00";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."10-postgresql-backup".${backupDir}.d = {
|
||||
user = "postgres";
|
||||
group = "postgres";
|
||||
mode = "700";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
../../base.nix
|
||||
../../base
|
||||
../../misc/metrics-exporters.nix
|
||||
./disks.nix
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
../../base.nix
|
||||
../../base
|
||||
../../misc/metrics-exporters.nix
|
||||
|
||||
./services/grzegorz.nix
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
../../base.nix
|
||||
../../base
|
||||
../../misc/metrics-exporters.nix
|
||||
|
||||
./services/libvirt.nix
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
../../base.nix
|
||||
../../base
|
||||
../../misc/metrics-exporters.nix
|
||||
|
||||
../../modules/grzegorz.nix
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
../../base.nix
|
||||
../../base
|
||||
../../misc/metrics-exporters.nix
|
||||
|
||||
./services/monitoring
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
imports = [
|
||||
# Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
../../base.nix
|
||||
../../base
|
||||
../../misc/metrics-exporters.nix
|
||||
];
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ in
|
|||
serviceConfig.Type = "oneshot";
|
||||
script = let
|
||||
openssl = lib.getExe pkgs.openssl;
|
||||
in lib.concatMapStringsSep "\n----------------\n" ({ name, value }: ''
|
||||
in lib.concatMapStringsSep "\n" ({ name, value }: ''
|
||||
mkdir -p $(dirname "${value.certificate}") $(dirname "${value.certificateKey}")
|
||||
if ! ${openssl} x509 -checkend 86400 -noout -in ${value.certificate}
|
||||
then
|
||||
|
@ -69,6 +69,8 @@ in
|
|||
chown "${value.owner}:${value.group}" "${value.certificateKey}"
|
||||
chmod "${value.mode}" "${value.certificate}"
|
||||
chmod "${value.mode}" "${value.certificateKey}"
|
||||
|
||||
echo "\n-----------------\n"
|
||||
'') (lib.attrsToList cfg);
|
||||
};
|
||||
systemd.timers."generate-snakeoil-certs" = {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
gitea:
|
||||
web-secret-provider:
|
||||
token: ENC[AES256_GCM,data:pHmBKxrNcLifl4sjR44AGEElfdachja35Tl/InsqvBWturaeTv4R0w==,iv:emBWfXQs2VNqtpDp5iA5swNC+24AWDYYXo6nvN+Fwx4=,tag:lkhSVSs6IqhHpfDPOX0wQA==,type:str]
|
||||
password: ENC[AES256_GCM,data:hlNzdU1ope0t50/3aztyLeXjMHd2vFPpwURX+Iu8f49DOqgSnEMtV+KtLA==,iv:qljRnSnchL5cFmaUAfCH9GQYQxcy5cyWejgk1x6bFgI=,tag:tIhboFU5kZsj5oAQR3hLbw==,type:str]
|
||||
database: ENC[AES256_GCM,data:UlS33IdCEyeSvT6ngpmnkBWHuSEqsB//DT+3b7C+UwbD8UXWJlsLf1X8/w==,iv:mPRW5ldyZaHP+y/0vC2JGSLZmlkhgmkvXPk4LazkSDs=,tag:gGk6Z/nbPvzE1zG+tJC8Sw==,type:str]
|
||||
email-password: ENC[AES256_GCM,data:KRwC+aL1aPvJuXt91Oq1ttATMnFTnuUy,iv:ats8TygB/2pORkaTZzPOLufZ9UmvVAKoRcWNvYF1z6w=,tag:Do0fA+4cZ3+l7JJyu8hjBg==,type:str]
|
||||
passwd-ssh-key: ENC[AES256_GCM,data:L0lF0wvpayss1NU9m3A45cH0bCMQzODTFVrq6EPd1JHx54wIcoaRBYLmxXKXASzBlCg9zlwXMUIk3OQcS3kdzMKL0iqcSL2iicAcKjFIHyrWLqXgwV5pRSP/tRPcVw8KW8gz0bh33EgESs5ReddZ3VZ0Cy1s2YupMRQvBXr89k1+Hv70OWB6P06hvxhv/zKcMGI1N/dWLroMgrQuT9imw4+/Q1RqwzTYeEU+eUn24AM9GjcBg4qf3OI+6g0nXUat/upIYE28iF5J3lbUSmDSmirBLc8xgHLdOyyJPTObWYWYxlSL78T7IqiMm9lI3rtBlpJDDcn/YxZpVqN5bg2154GISNK+uR0TVSLdJ+drdGHIfIX3G78XSxf2L9rbJyRn8MQlgStfdBIQicLavQKVMrmj+XQfvEMez23WbPLjH4oViBQFI+GrOHOGy/f16cz8Sn4n+69OcsOeTxs3tKYdfq6r1XLYSJ/fe/zvxBpaZiyGXljsuyEdIyBL2A8D6uSXe3Nd3/DAdBtceFfIdN1olCdutixzVWgxaJnrel161z5A/4w=,iv:Uy46yY3jFYSvpxrgCHxRMUksnWfhf5DViLMvCXVMMl4=,tag:wFEJ5+icFrOKkc56gY0A5g==,type:str]
|
||||
ssh-known-hosts: ENC[AES256_GCM,data:zlRLoelQeumMxGqPmgMTB69X1RVWXIs2jWwc67lk0wrdNOHUs5UzV5TUA1JnQ43RslBU92+js7DkyvE5enGzw7zZE5F1ZYdGv/eCgvkTMC9BoLfzHzP6OzayPLYEt3xJ5PRocN8JUAD55cuu4LgsuebuydHPi2oWOfpbSUBKSeCh6dvk5Pp1XRDprPS5SzGLW8Xjq98QlzmfGv50meI9CDJZVF9Wq/72gkyfgtb3YVdr,iv:AF06TBitHegfWk6w07CdkHklh4ripQCmA45vswDQgss=,tag:zKh7WVXMJN2o9ZIwIkby3Q==,type:str]
|
||||
import-user-env: ENC[AES256_GCM,data:vfaqjGEnUM9VtOPvBurz7nFwzGZt3L2EqijrQej4wiOcGCrRA4tN6kBV6NmhHqlFPsw=,iv:viPGkyOOacCWcgTu25da4qH7DC4wz2qdeC1W2WcMUdI=,tag:BllNqGQoaxqUo3lTz9LGnw==,type:str]
|
||||
import-user-env: ENC[AES256_GCM,data:wArFwTd0ZoB4VXHPpichfnmykxGxN8y2EQsMgOPHv7zsm6A+m2rG9BWDGskQPr5Ns9o=,iv:gPUzYFSNoALJb1N0dsbNlgHIb7+xG7E9ANpmVNZURQ0=,tag:JghfRy2OcDFWKS9zX1XJ9A==,type:str]
|
||||
runners:
|
||||
alpha: ENC[AES256_GCM,data:gARxCufePz+EMVwEwRsL2iZUfh9HUowWqtb7Juz3fImeeAdbt+k3DvL/Nwgegg==,iv:3fEaWd7v7uLGTy2J7EFQGfN0ztI0uCOJRz5Mw8V5UOU=,tag:Aa6LwWeW2hfDz1SqEhUJpA==,type:str]
|
||||
beta: ENC[AES256_GCM,data:DVjS78IKWiWgf+PuijCZKx4ZaEJGhQr7vl+lc7QOg1JlA4p9Kux/tOD8+f2+jA==,iv:tk3Xk7lKWNdZ035+QVIhxXy2iJbHwunI4jRFM4It46E=,tag:9Mr6o//svYEyYhSvzkOXMg==,type:str]
|
||||
|
@ -90,8 +92,8 @@ sops:
|
|||
UHpLRkdQTnhkeGlWVG9VS1hkWktyckEKAdwnA9URLYZ50lMtXrU9Q09d0L3Zfsyr
|
||||
4UsvjjdnFtsXwEZ9ZzOQrpiN0Oz24s3csw5KckDni6kslaloJZsLGg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2024-05-26T02:07:41Z"
|
||||
mac: ENC[AES256_GCM,data:CRaJefV1zcJc6eyzyjTLgd0+Wv46VT8o4iz2YAGU+c2b/Cr97Tj290LoEO6UXTI3uFwVfzii2yZ2l+4FK3nVVriD4Cx1O/9qWcnLa5gfK30U0zof6AsJx8qtGu1t6oiPlGUCF7sT0BW9Wp8cPumrY6cZp9QbhmIDV0o0aJNUNN4=,iv:8OSYV1eG6kYlJD4ovZZhcD1GaYnmy7vHPa/+7egM1nE=,tag:OPI13rpDh2l1ViFj8TBFWg==,type:str]
|
||||
lastmodified: "2024-08-26T19:38:58Z"
|
||||
mac: ENC[AES256_GCM,data:3FyfZPmJ7znQEul+IwqN1ZaM53n6os3grquJwJ9vfyDSc2h8UZBhqYG+2uW9Znp9DSIjuhCUI8iqGKRJE0M/6IDICeXms/5+ynVFOS9bA2cdzPvWaj0FFAd2x3g4Vhs47+vRlsnIe/tMiKU3IOvzOfI6KAUHc9L2ySrzH7z2+fo=,iv:1iZSR9qOIEtf+fNbtWSwJBIUEQGKadfHSVOnkFzOwq8=,tag:Sk6JEU1B6Rd1GXLYC6rQtQ==,type:str]
|
||||
pgp:
|
||||
- created_at: "2024-08-04T00:03:28Z"
|
||||
enc: |-
|
||||
|
@ -114,4 +116,4 @@ sops:
|
|||
-----END PGP MESSAGE-----
|
||||
fp: F7D37890228A907440E1FD4846B9228E814A2AAC
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.8.1
|
||||
version: 3.9.0
|
||||
|
|
Loading…
Reference in New Issue