fft/flake.nix
2025-01-15 20:45:09 +01:00

489 lines
18 KiB
Nix

{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
whakaahua.url = "github:maix0/whakaahua";
};
outputs = {
self,
nixpkgs,
flake-utils,
...
} @ inputs:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
devShell = pkgs.mkShellNoCC {
packages = [
(with pkgs.python3.pkgs;
python.withPackages (pypkgs:
with pypkgs; [
redis
flask
requests
arrow
sentry-sdk
]))
pkgs.black
pkgs.ruff
];
};
packages = rec {
inherit (inputs.whakaahua.packages.${system}) whakaahua;
default = fft;
fft = with pkgs.python3.pkgs;
pkgs.stdenvNoCC.mkDerivation {
name = "fft";
src = ./.;
installPhase = ''
mkdir -p $out/opt
cp -farT . $out/opt
mkdir -p $out/bin
cat <<EOF >$out/bin/fft
#!/bin/sh
cd $out/opt
exec ${python.withPackages (pypkgs:
with pypkgs; [
redis
flask
requests
arrow
sentry-sdk
])}/bin/python ./app.py
EOF
chmod +x $out/bin/fft
'';
};
updater = with pkgs.python3.pkgs;
pkgs.stdenvNoCC.mkDerivation {
name = "updater";
src = ./.;
installPhase = ''
mkdir -p $out/opt
cp -farT . $out/opt
mkdir -p $out/bin
cat <<EOF >$out/bin/updater
#!/bin/sh
cd $out/opt
exec ${python.withPackages (pypkgs:
with pypkgs; [
redis
flask
requests
arrow
sentry-sdk
])}/bin/python ./updater.py
EOF
chmod +x $out/bin/updater
'';
};
};
})
// {
nixosModules = {
default = {
pkgs,
config,
lib,
...
}:
with lib; let
cfg = config.services.fft;
in {
options.services.fft = {
extraBindMounts = mkOption {
type = types.attrs;
default = {};
description = "Extra bind mounts for the containers";
};
package = mkOption {
type = types.package;
default = self.packages.${pkgs.system}.fft;
};
updaterPackage = mkOption {
type = types.package;
default = self.packages.${pkgs.system}.updater;
};
enable = mkEnableOption "fft";
port = mkOption {
type = types.port;
default = 10000;
description = "The port the load balencer will be listening on";
};
redisPort = mkOption {
type = types.port;
default = 9999;
description = "The port the load balencer will be listening on";
};
instanceCount = mkOption {
type = types.ints.positive;
default = 1;
description = "The number of instance that will be running";
};
envFile = mkOption {
type = types.path;
description = "Environment file";
};
bocalToken = mkOption {
type = types.strMatching "[a-zA-Z0-9_\-]+";
description = "Bocal Token";
};
updateToken = mkOption {
type = types.strMatching "[a-zA-Z0-9_\-]+";
description = "Update Token";
};
dbPath = mkOption {
type = types.path;
description = "database Path";
default = "/var/lib/fft/database.db";
};
domain = mkOption {
type = types.str;
description = "domain to be used";
default = "localhost";
};
backup = {
enable = mkEnableOption "Backup of database";
timer = mkOption {
type = types.attrsOf types.str;
description = "will be merged into a systemd.timers.<name>.timerConfig";
default = {
"OnUnitActiveSec" = "1d";
"OnBootSec" = "1d";
};
};
backupDir = mkOption {
type = types.path;
description = "Where the backup will be stored";
default = "/var/lib/fft-backup/";
};
backupCount = mkOption {
type = types.ints.positive;
default = 5;
description = "Number of stored backup";
};
};
description = "Backup the database";
};
config = mkIf cfg.enable (let
mainSystemdUnit = idx: {
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target"];
enable = true;
environment = {
F42_PORT = toString (10000 + idx);
F42_REDIS_PORT = toString cfg.redisPort;
F42_REDIS_HOST = "localhost";
F42_BOCAL_KEY = cfg.bocalToken;
F42_UPDATE_KEY = cfg.updateToken;
F42_DB = cfg.dbPath;
F42_DOMAIN = cfg.domain;
};
serviceConfig = {
User = "fft";
Group = "nobody";
EnvironmentFile = "/env";
ExecStart = "${cfg.package}/bin/fft";
};
};
in {
systemd = mkIf cfg.backup.enable {
services.fft-backup = {
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target"];
enable = true;
script = ''
TEMP_FILE=$(mktemp)
${
concatStringsSep "\n" (lib.reverseList
(map
(idx: ''
if [ -f ${lib.escapeShellArg "${cfg.backup.backupDir}/backup-${toString idx}.sq3"} ]; then
${pkgs.coreutils}/bin/touch -r ${lib.escapeShellArg "${cfg.backup.backupDir}/backup-${toString idx}.sq3"} "$TEMP_FILE"
mv ${lib.escapeShellArg "${cfg.backup.backupDir}/backup-${toString idx}.sq3"} ${lib.escapeShellArg "${cfg.backup.backupDir}/backup-${toString (idx + 1)}.sq3"};
${pkgs.coreutils}/bin/touch -r "$TEMP_FILE" ${lib.escapeShellArg "${cfg.backup.backupDir}/backup-${toString idx}.sq3"}
fi
'')
(lib.range 0 (cfg.backup.backupCount - 1))))
}
until [ -f "/var/lib/nixos-containers/fft/${cfg.dbPath}" ]; do
echo "Unable to find the database, sleeping for 30s...";
sleep 30;
done;
${pkgs.sqlite}/bin/sqlite3 /var/lib/nixos-containers/fft/var/lib/fft/database.db ".backup "${lib.escapeShellArg "${cfg.backup.backupDir}"}/backup-0.sq3
'';
};
timers.fft-backup = {
enable = true;
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target"];
timerConfig = cfg.backup.timer;
};
};
system.activationScripts.fft = mkIf cfg.backup.enable (lib.stringAfter ["var"] ''
mkdir -p ${lib.escapeShellArg cfg.backup.backupDir}
'');
containers.fft = {
privateNetwork = false; # TODO: maybe change it ?
bindMounts =
{
"/env" = {
hostPath = "${cfg.envFile}";
isReadOnly = true;
};
"/etc/resolv.conf" = {
hostPath = "/etc/resolv.conf";
isReadOnly = true;
};
}
// cfg.extraBindMounts;
autoStart = true;
hostAddress = "192.168.100.2";
config = {
boot.tmp.cleanOnBoot = true;
system.stateVersion = "24.11";
networking.firewall.allowedTCPPorts = [cfg.port];
system.activationScripts.fft = lib.stringAfter ["var"] ''
mkdir -p /var/lib/fft
chown fft /var/lib/fft
'';
users.users.fft = {
isSystemUser = true;
group = "fft";
};
users.groups.fft = {};
services.redis.servers.fft = {
user = "fft";
port = cfg.redisPort;
enable = true;
};
services.nginx = {
upstreams."${cfg.domain}" = {
extraConfig = ''
'';
servers = builtins.foldl' (a: b: a // b) {} (map (idx: {
"127.0.0.1:${toString (10000 + idx)}" = {};
}) (lib.range 1 cfg.instanceCount));
};
virtualHosts."${cfg.domain}" = {
locations = {
"/" = {
proxyPass = "http://${cfg.domain}";
};
"/proxy" = {
proxyPass = "http://localhost:9990";
};
};
listen = [
{
addr = "0.0.0.0";
port = cfg.port;
}
];
default = true;
};
enable = true;
};
systemd = {
services =
{
fft-updater = {
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target"];
enable = true;
environment = {
F42_PORT = toString cfg.port;
F42_REDIS_PORT = toString cfg.redisPort;
F42_REDIS_HOST = "localhost";
F42_BOCAL_KEY = cfg.bocalToken;
F42_UPDATE_KEY = cfg.updateToken;
F42_DB = cfg.dbPath;
F42_DOMAIN = cfg.domain;
};
serviceConfig = {
User = "fft";
Group = "nobody";
EnvironmentFile = "/env";
ExecStart = "${getBin cfg.updaterPackage}/bin/updater";
};
};
fft-proxy = {
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target"];
enable = true;
environment = {
P42_PORT = toString 9990;
P42_DATA_DIR = "/tmp/fft-proxy-dir";
};
serviceConfig = {
User = "fft";
Group = "nobody";
ExecStart = "${self.packages.${pkgs.system}.whakaahua}/bin/whakaahua";
};
};
fft-update-tutors = {
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target"];
enable = false;
script = ''
${pkgs.curl}/bin/curl -L http://localhost:${toString cfg.port}/admin/update/tutors/${lib.escapeShellArg cfg.updateToken}
'';
environment = {
F42_DOMAIN = cfg.domain;
};
};
fft-proxy-cleanup = {
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target"];
enable = false;
script = ''
rm -rf /tmp/fft-proxy-dir
'';
environment = {
F42_DOMAIN = cfg.domain;
};
};
}
// (listToAttrs (map
(idx: {
name = "fft-${toString idx}";
value = mainSystemdUnit idx;
}) (lib.range 1 cfg.instanceCount)));
timers = {
fft-update-tutors = {
enable = true;
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target" "fft-1.service"];
timerConfig = {
"OnUnitActiveSec" = "1d";
"OnBootSec" = "10m";
};
};
fft-proxy-cleanup = {
enable = true;
wantedBy = ["multi-user.target"];
requires = ["network.target"];
after = ["network.target"];
timerConfig = {
"OnUnitActiveSec" = "1d";
"OnBootSec" = "10m";
};
};
};
};
};
};
});
};
};
nixosConfigurations.test-vm = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = [
./create-envfile.nix
self.nixosModules.default
({
pkgs,
lib,
...
}: {
networking.firewall.extraCommands = ''
iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o eth0 -j MASQUERADE
'';
services.fft = {
enable = true;
bocalToken = "bocal";
updateToken = "update";
envFile = "/etc/envfile";
domain = "fft.maix.me";
port = 80;
backup = {
enable = true;
timer = {
"OnUnitActiveSec" = "1m";
"OnBootSec" = "1m";
};
};
};
})
({
pkgs,
lib,
...
}: {
virtualisation.vmVariant = {
services.openssh.enable = true;
services.openssh.settings.PermitRootLogin = "yes";
users.users.root = {
password = "root";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDQ+u6AcgPYW+4qOQWd5pQec/f+ukOnpVECPiPrYzM/D maix@XeMaix"
];
};
virtualisation.qemu.networkingOptions = [
"-device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}"
];
users.users.maiboyer = {
autoSubUidGidRange = true;
createHome = true;
isNormalUser = true;
initialHashedPassword = lib.mkForce null;
group = "maiboyer";
home = "/home/maiboyer";
openssh.authorizedKeys.keys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDQ+u6AcgPYW+4qOQWd5pQec/f+ukOnpVECPiPrYzM/D maix@XeMaix"];
password = "1234";
extraGroups = ["wheel" "docker"];
};
environment = {
systemPackages = with pkgs; [
docker
firefox
git
bat
gnumake
kitty
];
};
users.groups.maiboyer = {};
security.sudo.wheelNeedsPassword = false;
users.users.nixos = {
password = "nixos";
isNormalUser = true;
home = "/home/nixos";
};
networking.hosts = {"127.0.0.1" = ["fft.maix.me"];};
services.xserver = {
enable = true;
displayManager.gdm.enable = true;
desktopManager.gnome.enable = true;
};
virtualisation = {
memorySize = 8096;
cores = 8;
graphics = true;
diskSize = 16000; # 32 Gb
};
};
})
];
};
};
}