remove _disnix folder
This commit is contained in:
parent
9bf187766a
commit
21e4f837e9
57 changed files with 0 additions and 5491 deletions
|
@ -1,39 +0,0 @@
|
||||||
{ distribution ? null
|
|
||||||
, services ? null
|
|
||||||
, system ? builtins.currentSystem
|
|
||||||
, pkgs ? import <nixpkgs> { inherit system; }
|
|
||||||
, utils ? null
|
|
||||||
, secret ? null
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
callPackage = pkgs.lib.callPackageWith (pkgs // customPkgs);
|
|
||||||
|
|
||||||
customPkgs = rec {
|
|
||||||
mkPostgresDB = callPackage ./postgresdb {};
|
|
||||||
|
|
||||||
mkHaproxyService = callPackage ./haproxy/unit.nix {inherit utils;};
|
|
||||||
|
|
||||||
CaddyConfig = callPackage ./caddy/config.nix {inherit utils;};
|
|
||||||
CaddyService = callPackage ./caddy/unit.nix {inherit utils;};
|
|
||||||
CaddySiteConfig = callPackage ./caddy/siteconfig.nix {inherit utils;};
|
|
||||||
mkCaddySiteConfig = callPackage ./caddy/mksiteconfig.nix {inherit CaddySiteConfig;};
|
|
||||||
|
|
||||||
mkNginxService = callPackage ./nginx/unit.nix {inherit utils;};
|
|
||||||
|
|
||||||
mkPHPFPMService = callPackage ./php-fpm/unit.nix {inherit utils;};
|
|
||||||
|
|
||||||
mkKeycloakService = callPackage ./keycloak/unit.nix {inherit utils;};
|
|
||||||
mkOauth2Proxy = callPackage ./oauth2-proxy/unit.nix {inherit utils;};
|
|
||||||
|
|
||||||
mkKeycloakHaproxyService = callPackage ./keycloak-haproxy/unit.nix {inherit utils;};
|
|
||||||
|
|
||||||
mkKeycloakCliService = callPackage ./keycloak-cli-config/unit.nix {inherit utils;};
|
|
||||||
|
|
||||||
keycloak = callPackage ./keycloak {inherit utils customPkgs;};
|
|
||||||
|
|
||||||
ttrss = callPackage ./ttrss {inherit utils customPkgs;};
|
|
||||||
vaultwarden = callPackage ./vaultwarden {inherit utils customPkgs secret;};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
customPkgs
|
|
|
@ -1,24 +0,0 @@
|
||||||
{ CaddySiteConfig
|
|
||||||
}:
|
|
||||||
{ CaddyConfig
|
|
||||||
, CaddyService
|
|
||||||
, name
|
|
||||||
, port
|
|
||||||
, siteName
|
|
||||||
, siteRoot
|
|
||||||
, phpFpmSiteSocket ? ""
|
|
||||||
}:
|
|
||||||
rec {
|
|
||||||
inherit name;
|
|
||||||
caddySocket = "${CaddyService.runtimeDirectory}/${siteName}.sock";
|
|
||||||
pkg = CaddySiteConfig rec {
|
|
||||||
inherit (CaddyConfig) siteConfigDir;
|
|
||||||
inherit phpFpmSiteSocket;
|
|
||||||
|
|
||||||
portBinding = port;
|
|
||||||
bindService = siteName;
|
|
||||||
siteSocket = caddySocket;
|
|
||||||
serviceRoot = siteRoot;
|
|
||||||
};
|
|
||||||
type = "fileset";
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ siteConfigDir
|
|
||||||
, portBinding
|
|
||||||
, bindService
|
|
||||||
, serviceRoot ? "/usr/share/webapps/${bindService}"
|
|
||||||
, siteSocket ? null
|
|
||||||
, phpFpmSiteSocket ? null
|
|
||||||
, logLevel ? "WARN"
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
content =
|
|
||||||
[
|
|
||||||
"root * ${serviceRoot}"
|
|
||||||
"file_server"
|
|
||||||
]
|
|
||||||
++ (
|
|
||||||
if siteSocket != ""
|
|
||||||
then [
|
|
||||||
"bind unix/${siteSocket}"
|
|
||||||
]
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
++ (
|
|
||||||
if phpFpmSiteSocket != ""
|
|
||||||
then [
|
|
||||||
"php_fastcgi unix/${phpFpmSiteSocket}"
|
|
||||||
]
|
|
||||||
else []
|
|
||||||
);
|
|
||||||
in
|
|
||||||
|
|
||||||
utils.mkConfigFile {
|
|
||||||
name = "${bindService}.config";
|
|
||||||
dir = siteConfigDir;
|
|
||||||
content = ''
|
|
||||||
:${builtins.toString portBinding} {
|
|
||||||
${builtins.concatStringsSep "\n " content}
|
|
||||||
|
|
||||||
log {
|
|
||||||
output stderr
|
|
||||||
level ${logLevel}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ user ? "http"
|
|
||||||
, group ? "http"
|
|
||||||
, siteConfigDir
|
|
||||||
}:
|
|
||||||
{...}:
|
|
||||||
|
|
||||||
let
|
|
||||||
config = pkgs.writeTextDir "Caddyfile" ''
|
|
||||||
{
|
|
||||||
# Disable auto https
|
|
||||||
http_port 10001
|
|
||||||
https_port 10002
|
|
||||||
}
|
|
||||||
|
|
||||||
import ${siteConfigDir}/*
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
utils.systemd.mkService rec {
|
|
||||||
name = "caddy";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=Caddy webserver
|
|
||||||
Documentation=https://caddyserver.com/docs/
|
|
||||||
|
|
||||||
After=network.target network-online.target
|
|
||||||
Wants=network-online.target systemd-networkd-wait-online.target
|
|
||||||
|
|
||||||
StartLimitInterval=14400
|
|
||||||
StartLimitBurst=10
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=notify
|
|
||||||
User=${user}
|
|
||||||
Group=${group}
|
|
||||||
ExecStart=${pkgs.caddy}/bin/caddy run --environ --config ${config}
|
|
||||||
ExecReload=${pkgs.caddy}/bin/caddy reload --config ${config}
|
|
||||||
|
|
||||||
# Restart=on-abnormal
|
|
||||||
RuntimeDirectory=caddy
|
|
||||||
|
|
||||||
# KillMode=mixed
|
|
||||||
# KillSignal=SIGQUIT
|
|
||||||
TimeoutStopSec=5s
|
|
||||||
|
|
||||||
LimitNOFILE=1048576
|
|
||||||
LimitNPROC=512
|
|
||||||
|
|
||||||
# PrivateDevices=true
|
|
||||||
LockPersonality=true
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateDevices=true
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectClock=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectHome=true
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectKernelLogs=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectSystem=full
|
|
||||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
|
|
||||||
RestrictNamespaces=true
|
|
||||||
RestrictRealtime=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
|
|
||||||
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
||||||
|
|
||||||
# ProtectSystem=strict
|
|
||||||
# ReadWritePaths=/var/lib/caddy /var/log/caddy
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
'';
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
herculesCI = {...}: {
|
|
||||||
onPush.default = {
|
|
||||||
outputs = {...}: {
|
|
||||||
unit = (import ./default.nix {}).tests.unit;
|
|
||||||
integration = (import ./default.nix {}).tests.integration;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{ pkgs ? import (builtins.fetchGit {
|
|
||||||
# Descriptive name to make the store path easier to identify
|
|
||||||
name = "nixos-21.11-2023-03-15";
|
|
||||||
url = "https://github.com/nixos/nixpkgs/";
|
|
||||||
# Commit hash for nixos-unstable as of 2018-09-12
|
|
||||||
# `git ls-remote https://github.com/nixos/nixpkgs nixos-unstable`
|
|
||||||
ref = "refs/tags/21.11";
|
|
||||||
rev = "506445d88e183bce80e47fc612c710eb592045ed";
|
|
||||||
}) {}
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
utils = pkgs.callPackage ./utils.nix {};
|
|
||||||
in
|
|
||||||
with builtins;
|
|
||||||
with pkgs.lib.attrsets;
|
|
||||||
with pkgs.lib.lists;
|
|
||||||
with pkgs.lib.strings;
|
|
||||||
rec {
|
|
||||||
customPkgs = import ./all-packages.nix;
|
|
||||||
|
|
||||||
tests = pkgs.callPackage ./tests { inherit utils; };
|
|
||||||
|
|
||||||
runtests =
|
|
||||||
let
|
|
||||||
onlytests = filterAttrs (name: value: name != "override" && name != "overrideDerivation") tests;
|
|
||||||
failingtests = filterAttrs (name: value: length value > 0) onlytests;
|
|
||||||
formatFailure = failure: toString failure; # TODO: make this more pretty
|
|
||||||
formattedFailureGroups = mapAttrsToList (name: failures: "${name}:\n${concatMapStringsSep "\n" formatFailure failures}") failingtests;
|
|
||||||
in
|
|
||||||
if length formattedFailureGroups == 0 then
|
|
||||||
"no failing test"
|
|
||||||
else
|
|
||||||
concatStringsSep "\n" formattedFailureGroups;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
# This example uses YAML anchors which allows reuse of multiple keys
|
|
||||||
# without having to repeat yourself.
|
|
||||||
# Also see https://github.com/Mic92/dotfiles/blob/master/nixos/.sops.yaml
|
|
||||||
# for a more complex example.
|
|
||||||
keys:
|
|
||||||
- &me age1nj0ulq6863y9tdk0pkwjx4ltuyjpx6gftwy27mk3gkwja6k325esgaerlr
|
|
||||||
- &machine1 age16yraj9xdpjqazwakcy4fs9gcxu75el3yefpzudhv7zu9pn6jsvtqeee23r
|
|
||||||
|
|
||||||
creation_rules:
|
|
||||||
- path_regex: secrets/[^/]+\.yaml$
|
|
||||||
key_groups:
|
|
||||||
- age:
|
|
||||||
- *me
|
|
||||||
- path_regex: secrets/machine1/[^/]+\.yaml$
|
|
||||||
key_groups:
|
|
||||||
- age:
|
|
||||||
- *me
|
|
||||||
- *machine1
|
|
|
@ -1,36 +0,0 @@
|
||||||
# Vaultwarden setup
|
|
||||||
|
|
||||||
This folder contain an example configuration for setting up
|
|
||||||
Vaultwarden on Linode. But before deploying to linode, you can
|
|
||||||
actually test the deployment locally with VirtualBox.
|
|
||||||
|
|
||||||
First, [setup NixOS on a Linode instance](/docs/tutorials/linode.md).
|
|
||||||
|
|
||||||
When that's done, explore the files in this folder.
|
|
||||||
|
|
||||||
To try it out locally, follow [deploy to staging](/docs/tutorials/deploystaging.md).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nixops set-args --network dev \
|
|
||||||
--arg domain '"dev.mydomain.com"' \
|
|
||||||
--arg sopsKeyFile '"$HOME/.config/sops/age/keys.txt"'
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use the `info` subcommand to print the values of the arguments:
|
|
||||||
```bash
|
|
||||||
nixops info --network dev
|
|
||||||
```
|
|
||||||
|
|
||||||
The TL; DR version is, assuming you're where this file is located:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export NIXOPS_DEPLOYMENT=vaultwarden-staging
|
|
||||||
export DISNIXOS_USE_NIXOPS=1
|
|
||||||
|
|
||||||
nixops create ./network-virtualbox.nix -d vaultwarden-staging
|
|
||||||
|
|
||||||
nixops deploy --network dev
|
|
||||||
nixops reboot
|
|
||||||
|
|
||||||
disnixos-env -s services.nix -n dev/nixops.nix -d distribution.nix
|
|
||||||
```
|
|
|
@ -1,52 +0,0 @@
|
||||||
{
|
|
||||||
domain ? "dev.mydomain.com",
|
|
||||||
sopsKeyFile ? "",
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
network = {
|
|
||||||
storage.legacy = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
machine1 = { system, pkgs, lib, ... }:
|
|
||||||
with lib;
|
|
||||||
let
|
|
||||||
utils = pkgs.lib.callPackageWith pkgs ./../../../../utils.nix { };
|
|
||||||
|
|
||||||
base = ((import ./../network.nix).machine1 {
|
|
||||||
inherit system pkgs lib;
|
|
||||||
inherit domain utils;
|
|
||||||
secret = x: x;
|
|
||||||
});
|
|
||||||
|
|
||||||
vbox = (import ./../network.nix).virtualbox;
|
|
||||||
|
|
||||||
mkPortMapping = {name, host, guest, protocol ? "tcp"}:
|
|
||||||
["--natpf1" "${name},${protocol},,${toString host},,${toString guest}"];
|
|
||||||
in
|
|
||||||
recursiveUpdate base {
|
|
||||||
imports = [
|
|
||||||
<sops-nix/modules/sops>
|
|
||||||
];
|
|
||||||
deployment.targetEnv = "virtualbox";
|
|
||||||
deployment.virtualbox = {
|
|
||||||
memorySize = 1024;
|
|
||||||
vcpu = 2;
|
|
||||||
headless = true;
|
|
||||||
vmFlags = concatMap mkPortMapping vbox.portMappings;
|
|
||||||
};
|
|
||||||
|
|
||||||
# This will add secrets.yml to the nix store
|
|
||||||
# You can avoid this by adding a string to the full path instead, i.e.
|
|
||||||
# sops.defaultSopsFile = "/root/.sops/secrets/example.yaml";
|
|
||||||
sops.defaultSopsFile = ../secrets/linode.yaml;
|
|
||||||
# This will automatically import SSH keys as age keys
|
|
||||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
|
||||||
# This is using an age key that is expected to already be in the filesystem
|
|
||||||
sops.age.keyFile = /. + sopsKeyFile;
|
|
||||||
# This will generate a new key if the key specified above does not exist
|
|
||||||
sops.age.generateKey = true;
|
|
||||||
# This is the actual specification of the secrets.
|
|
||||||
sops.secrets.linode = {};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
{ infrastructure
|
|
||||||
, pkgs ? import <nixpkgs> {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
with infrastructure;
|
|
||||||
let
|
|
||||||
customPkgs = (pkgs.callPackage (./../../..) {}).customPkgs {
|
|
||||||
inherit pkgs;
|
|
||||||
};
|
|
||||||
|
|
||||||
keycloak = customPkgs.keycloak {};
|
|
||||||
vaultwarden = customPkgs.vaultwarden {};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
HaproxyService = [ machine1 ];
|
|
||||||
|
|
||||||
KeycloakService = [ machine1 ];
|
|
||||||
KeycloakCliService = [ machine1 ];
|
|
||||||
|
|
||||||
KeycloakHaproxyService = [ machine1 ];
|
|
||||||
}
|
|
||||||
// keycloak.distribute [ machine1 ]
|
|
||||||
// vaultwarden.distribute [ machine1 ]
|
|
|
@ -1,160 +0,0 @@
|
||||||
{ hostname
|
|
||||||
, userName
|
|
||||||
, userPackages
|
|
||||||
, systemPackages
|
|
||||||
, address
|
|
||||||
, gateway
|
|
||||||
, sshPublicKey
|
|
||||||
, allowedTCPPorts
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports =
|
|
||||||
[ # Include the results of the hardware scan.
|
|
||||||
./machine1-hardware-configuration.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
# Use the GRUB 2 boot loader.
|
|
||||||
boot.loader.grub.enable = true;
|
|
||||||
boot.loader.grub.version = 2;
|
|
||||||
# boot.loader.grub.efiSupport = true;
|
|
||||||
# boot.loader.grub.efiInstallAsRemovable = true;
|
|
||||||
# boot.loader.efi.efiSysMountPoint = "/boot/efi";
|
|
||||||
# Define on which hard drive you want to install Grub.
|
|
||||||
# boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
|
|
||||||
|
|
||||||
networking.hostName = hostname; # Define your hostname.
|
|
||||||
# Pick only one of the below networking options.
|
|
||||||
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
|
|
||||||
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
|
|
||||||
networking.usePredictableInterfaceNames = false;
|
|
||||||
networking.enableIPv6 = false;
|
|
||||||
|
|
||||||
# Set your time zone.
|
|
||||||
# time.timeZone = "Europe/Amsterdam";
|
|
||||||
|
|
||||||
# Configure network proxy if necessary
|
|
||||||
# networking.proxy.default = "http://user:password@proxy:port/";
|
|
||||||
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
|
|
||||||
|
|
||||||
# Select internationalisation properties.
|
|
||||||
# i18n.defaultLocale = "en_US.UTF-8";
|
|
||||||
# console = {
|
|
||||||
# font = "Lat2-Terminus16";
|
|
||||||
# keyMap = "us";
|
|
||||||
# useXkbConfig = true; # use xkbOptions in tty.
|
|
||||||
# };
|
|
||||||
|
|
||||||
# Enable the X11 windowing system.
|
|
||||||
# services.xserver.enable = true;
|
|
||||||
|
|
||||||
# Configure keymap in X11
|
|
||||||
# services.xserver.layout = "us";
|
|
||||||
# services.xserver.xkbOptions = {
|
|
||||||
# "eurosign:e";
|
|
||||||
# "caps:escape" # map caps to escape.
|
|
||||||
# };
|
|
||||||
|
|
||||||
# Enable CUPS to print documents.
|
|
||||||
# services.printing.enable = true;
|
|
||||||
|
|
||||||
# Enable sound.
|
|
||||||
# sound.enable = true;
|
|
||||||
# hardware.pulseaudio.enable = true;
|
|
||||||
|
|
||||||
# Enable touchpad support (enabled default in most desktopManager).
|
|
||||||
# services.xserver.libinput.enable = true;
|
|
||||||
|
|
||||||
# Define a user account. Don't forget to set a password with ‘passwd’.
|
|
||||||
users.users.${userName} = {
|
|
||||||
isNormalUser = true;
|
|
||||||
extraGroups = [ "wheel" "networkmanager" ]; # Enable ‘sudo’ for the user.
|
|
||||||
packages = userPackages;
|
|
||||||
openssh.authorizedKeys.keys = [ sshPublicKey ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# List packages installed in system profile. To search, run:
|
|
||||||
# $ nix search wget
|
|
||||||
environment.systemPackages = systemPackages;
|
|
||||||
|
|
||||||
# Some programs need SUID wrappers, can be configured further or are
|
|
||||||
# started in user sessions.
|
|
||||||
# programs.mtr.enable = true;
|
|
||||||
# programs.gnupg.agent = {
|
|
||||||
# enable = true;
|
|
||||||
# enableSSHSupport = true;
|
|
||||||
# };
|
|
||||||
|
|
||||||
# List services that you want to enable:
|
|
||||||
|
|
||||||
# Enable the OpenSSH daemon.
|
|
||||||
services.openssh = {
|
|
||||||
enable = true;
|
|
||||||
permitRootLogin = "yes";
|
|
||||||
passwordAuthentication = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
nix.trustedUsers = [
|
|
||||||
"deployer"
|
|
||||||
];
|
|
||||||
|
|
||||||
users.groups.deployer = {};
|
|
||||||
users.users.deployer = {
|
|
||||||
isSystemUser = true;
|
|
||||||
group = "deployer";
|
|
||||||
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
|
||||||
openssh.authorizedKeys.keys = [ sshPublicKey ];
|
|
||||||
};
|
|
||||||
users.users."root" = {
|
|
||||||
openssh.authorizedKeys.keys = [ sshPublicKey ];
|
|
||||||
};
|
|
||||||
|
|
||||||
security.sudo.wheelNeedsPassword = false;
|
|
||||||
|
|
||||||
services.longview = {
|
|
||||||
enable = true;
|
|
||||||
apiKeyFile = "/var/lib/longview/apiKeyFile";
|
|
||||||
|
|
||||||
apacheStatusUrl = "";
|
|
||||||
nginxStatusUrl = "";
|
|
||||||
|
|
||||||
mysqlUser = "";
|
|
||||||
mysqlPassword = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Open ports in the firewall.
|
|
||||||
networking.firewall.allowedTCPPorts = allowedTCPPorts;
|
|
||||||
# networking.firewall.allowedUDPPorts = [ ... ];
|
|
||||||
# Or disable the firewall altogether.
|
|
||||||
# networking.firewall.enable = false;
|
|
||||||
|
|
||||||
networking.domain = "members.linode.com";
|
|
||||||
networking.search = [ "members.linode.com" ];
|
|
||||||
networking.resolvconf.extraOptions = [ "rotate" ];
|
|
||||||
networking.nameservers = [
|
|
||||||
"173.230.145.5"
|
|
||||||
"173.230.147.5"
|
|
||||||
"173.230.155.5"
|
|
||||||
"173.255.212.5"
|
|
||||||
"173.255.219.5"
|
|
||||||
"173.255.241.5"
|
|
||||||
"173.255.243.5"
|
|
||||||
"173.255.244.5"
|
|
||||||
"74.207.241.5"
|
|
||||||
"74.207.242.5"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Copy the NixOS configuration file and link it from the resulting system
|
|
||||||
# (/run/current-system/configuration.nix). This is useful in case you
|
|
||||||
# accidentally delete configuration.nix.
|
|
||||||
# system.copySystemConfiguration = true;
|
|
||||||
|
|
||||||
# This value determines the NixOS release from which the default
|
|
||||||
# settings for stateful data, like file locations and database versions
|
|
||||||
# on your system were taken. It‘s perfectly fine and recommended to leave
|
|
||||||
# this value at the release version of the first install of this system.
|
|
||||||
# Before changing this value read the documentation for this option
|
|
||||||
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
|
|
||||||
system.stateVersion = "22.05"; # Did you read the comment?
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
|
||||||
# and may be overwritten by future invocations. Please make changes
|
|
||||||
# to /etc/nixos/configuration.nix instead.
|
|
||||||
{ config, lib, pkgs, modulesPath, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports =
|
|
||||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [ "virtio_pci" "virtio_scsi" "ahci" "sd_mod" ];
|
|
||||||
boot.initrd.kernelModules = [ ];
|
|
||||||
boot.kernelModules = [ ];
|
|
||||||
boot.extraModulePackages = [ ];
|
|
||||||
|
|
||||||
fileSystems."/" =
|
|
||||||
{ device = "/dev/disk/by-label/nixos";
|
|
||||||
fsType = "ext4";
|
|
||||||
};
|
|
||||||
|
|
||||||
swapDevices =
|
|
||||||
[ { device = "/dev/disk/by-label/swap"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
boot.kernelParams = [ "console=ttyS0,19200n8" ];
|
|
||||||
boot.loader.grub.extraConfig = ''
|
|
||||||
serial --speed=19200 --unit=0 --word=8 --parity=no --stop=1;
|
|
||||||
terminal_input serial;
|
|
||||||
terminal_output serial
|
|
||||||
'';
|
|
||||||
boot.loader.grub.forceInstall = true;
|
|
||||||
boot.loader.grub.device = "nodev";
|
|
||||||
boot.loader.timeout = 10;
|
|
||||||
|
|
||||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
|
||||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
|
||||||
# still possible to use this option, but it's recommended to use it in conjunction
|
|
||||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
|
||||||
networking.useDHCP = lib.mkDefault false;
|
|
||||||
# networking.interfaces.eth0.useDHCP = true;
|
|
||||||
networking.interfaces.eth0 = {
|
|
||||||
ipv4 = {
|
|
||||||
addresses = [
|
|
||||||
{
|
|
||||||
address = "45.79.76.142";
|
|
||||||
prefixLength = 24;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
networking.defaultGateway = {
|
|
||||||
address = "45.79.76.1";
|
|
||||||
interface = "eth0";
|
|
||||||
};
|
|
||||||
|
|
||||||
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
|
||||||
}
|
|
|
@ -1,221 +0,0 @@
|
||||||
rec {
|
|
||||||
machine1 = { system
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
, utils
|
|
||||||
, domain
|
|
||||||
, secret
|
|
||||||
, ... }:
|
|
||||||
let
|
|
||||||
customPkgs = (pkgs.callPackage (./../../..) {}).customPkgs {
|
|
||||||
inherit system pkgs utils secret;
|
|
||||||
};
|
|
||||||
|
|
||||||
vaultwarden = customPkgs.vaultwarden {};
|
|
||||||
keycloak = customPkgs.keycloak {};
|
|
||||||
|
|
||||||
httpUser = "http";
|
|
||||||
httpGroup = "http";
|
|
||||||
httpRoot = "/usr/share/webapps";
|
|
||||||
|
|
||||||
phpfpmUser = "phpfpm";
|
|
||||||
phpfpmGroup = "phpfpm";
|
|
||||||
phpfpmRoot = "/run/php-fpm";
|
|
||||||
|
|
||||||
keycloakUser = "keycloak";
|
|
||||||
keycloakGroup = "keycloak";
|
|
||||||
|
|
||||||
caddyHttpPort = 10001;
|
|
||||||
caddyHttpsPort = 10002;
|
|
||||||
|
|
||||||
keycloaksecretsdir = "/run/keys/keycloakcliconfig";
|
|
||||||
keycloakusers = [ "me" "friend" ];
|
|
||||||
in
|
|
||||||
rec {
|
|
||||||
users.groups = {
|
|
||||||
http = {
|
|
||||||
name = httpGroup;
|
|
||||||
};
|
|
||||||
phpfpm = {
|
|
||||||
name = phpfpmGroup;
|
|
||||||
};
|
|
||||||
keycloak = {
|
|
||||||
name = keycloakGroup;
|
|
||||||
};
|
|
||||||
keycloakcli = {
|
|
||||||
name = "keycloakcli";
|
|
||||||
};
|
|
||||||
"${vaultwarden.group}" = {
|
|
||||||
name = "${vaultwarden.group}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
users.users = {
|
|
||||||
http = {
|
|
||||||
name = httpUser;
|
|
||||||
group = httpGroup;
|
|
||||||
home = httpRoot;
|
|
||||||
isSystemUser = true;
|
|
||||||
};
|
|
||||||
phpfpm = {
|
|
||||||
name = phpfpmUser;
|
|
||||||
group = phpfpmGroup;
|
|
||||||
home = phpfpmRoot;
|
|
||||||
isSystemUser = true;
|
|
||||||
};
|
|
||||||
keycloak = {
|
|
||||||
name = keycloakUser;
|
|
||||||
group = keycloakGroup;
|
|
||||||
# home ?
|
|
||||||
isSystemUser = true;
|
|
||||||
};
|
|
||||||
keycloakcli = {
|
|
||||||
name = "keycloakcli";
|
|
||||||
group = "keycloakcli";
|
|
||||||
extraGroups = [ "keys" ];
|
|
||||||
isSystemUser = true;
|
|
||||||
};
|
|
||||||
"${vaultwarden.user}" = {
|
|
||||||
name = vaultwarden.user;
|
|
||||||
group = vaultwarden.group;
|
|
||||||
extraGroups = [ "keys" ];
|
|
||||||
isSystemUser = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# deployment.keys = {
|
|
||||||
# keycloakdbpassword.text = ''
|
|
||||||
# KC_DB_PASSWORD="${secret "${domain}/keycloakdbpassword"}"
|
|
||||||
# '';
|
|
||||||
|
|
||||||
# keycloakinitialadmin.text = ''
|
|
||||||
# KEYCLOAK_ADMIN_PASSWORD="${secret "${domain}/${keycloak.subdomain}/admin"}"
|
|
||||||
# '';
|
|
||||||
|
|
||||||
# # This convention is for keycloak-cli-config
|
|
||||||
# "keycloak.password" = {
|
|
||||||
# destDir = keycloaksecretsdir;
|
|
||||||
# user = "keycloakcli";
|
|
||||||
# text = secret "${domain}/${keycloak.subdomain}/admin";
|
|
||||||
# };
|
|
||||||
# "keycloakusers" =
|
|
||||||
# let
|
|
||||||
# e = str: lib.strings.escape [''\''] (lib.strings.escape [''"''] str);
|
|
||||||
# in
|
|
||||||
# {
|
|
||||||
# user = "keycloakcli";
|
|
||||||
# text = lib.concatMapStringsSep "\n"
|
|
||||||
# (name: "KEYCLOAK_USERS_${lib.strings.toUpper name}_PASSWORD=${e (secret "${domain}/${keycloak.subdomain}/${name}")}")
|
|
||||||
# keycloakusers;
|
|
||||||
# };
|
|
||||||
# }
|
|
||||||
# // vaultwarden.deployKeys domain;
|
|
||||||
|
|
||||||
security.acme = {
|
|
||||||
acceptTerms = true;
|
|
||||||
certs = {
|
|
||||||
"${domain}" = {
|
|
||||||
extraDomainNames = ["*.${domain}"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
defaults = {
|
|
||||||
group = httpGroup;
|
|
||||||
email = "ibizapeanut@gmail.com";
|
|
||||||
dnsProvider = "linode";
|
|
||||||
dnsResolver = "8.8.8.8";
|
|
||||||
|
|
||||||
# For example, to use Linode to prove the dns challenge,
|
|
||||||
# the content of the file should be the following, with
|
|
||||||
# XXX replaced by your Linode API token.
|
|
||||||
# LINODE_HTTP_TIMEOUT=10
|
|
||||||
# LINODE_POLLING_INTERVAL=10
|
|
||||||
# LINODE_PROPAGATION_TIMEOUT=240
|
|
||||||
# LINODE_TOKEN=XXX
|
|
||||||
credentialsFile = "/run/secrets/linode";
|
|
||||||
enableDebugLogs = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services = {
|
|
||||||
openssh = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
disnix = {
|
|
||||||
enable = true;
|
|
||||||
# useWebServiceInterface = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
dnsmasq = {
|
|
||||||
enable = true;
|
|
||||||
servers = [ "192.168.50.15" "192.168.50.1" ];
|
|
||||||
extraConfig =
|
|
||||||
let
|
|
||||||
subdomains = [
|
|
||||||
"machine1"
|
|
||||||
keycloak.subdomain
|
|
||||||
vaultwarden.subdomain
|
|
||||||
];
|
|
||||||
|
|
||||||
inherit domain;
|
|
||||||
in (lib.concatMapStrings
|
|
||||||
(subdomain: "address=/${subdomain}.${domain}/127.0.0.1\naddress=/${subdomain}/127.0.0.1\n")
|
|
||||||
subdomains)
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
# tomcat.enable = false;
|
|
||||||
|
|
||||||
postgresql = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.postgresql_14;
|
|
||||||
|
|
||||||
port = 5432;
|
|
||||||
enableTCPIP = true;
|
|
||||||
authentication = pkgs.lib.mkOverride 10 ''
|
|
||||||
local all all trust
|
|
||||||
host all all 127.0.0.1/32 trust
|
|
||||||
host all all ::1/128 trust
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
dysnomia = {
|
|
||||||
enable = true;
|
|
||||||
enableLegacyModules = false;
|
|
||||||
extraContainerProperties = {
|
|
||||||
system = {
|
|
||||||
inherit domain;
|
|
||||||
};
|
|
||||||
postgresql-database = {
|
|
||||||
service_name = "postgresql.service";
|
|
||||||
port = builtins.toString services.postgresql.port;
|
|
||||||
};
|
|
||||||
keycloaksecrets = {
|
|
||||||
rootdir = keycloaksecretsdir;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ services.postgresql.port ] ++ virtualbox.guestPorts;
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualbox = rec {
|
|
||||||
portMappings = [
|
|
||||||
{ name = "ssh";
|
|
||||||
host = 22;
|
|
||||||
guest = 22;
|
|
||||||
}
|
|
||||||
{ name = "dns";
|
|
||||||
host = 53;
|
|
||||||
guest = 53;
|
|
||||||
}
|
|
||||||
{ name = "https";
|
|
||||||
host = 443;
|
|
||||||
guest = 443;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
hostPorts = map (x: x.host) portMappings;
|
|
||||||
guestPorts = map (x: x.guest) portMappings;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
let
|
|
||||||
hostname = "machine1";
|
|
||||||
domain = "mydomain.com";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
machine1 = { system, pkgs, lib, ... }:
|
|
||||||
let
|
|
||||||
utils = pkgs.lib.callPackageWith pkgs ./utils.nix { };
|
|
||||||
|
|
||||||
base = ((import ./network.nix).machine1 {
|
|
||||||
inherit system pkgs lib;
|
|
||||||
inherit domain utils;
|
|
||||||
});
|
|
||||||
|
|
||||||
vbox = (import ./network.nix).virtualbox;
|
|
||||||
in
|
|
||||||
lib.recursiveUpdate base rec {
|
|
||||||
deployment.targetHost = hostname;
|
|
||||||
imports = [
|
|
||||||
(import ./machines/machine1-configuration.nix {
|
|
||||||
inherit hostname;
|
|
||||||
|
|
||||||
userName = "me";
|
|
||||||
userPackages = with pkgs; [];
|
|
||||||
systemPackages = with pkgs; [
|
|
||||||
curl
|
|
||||||
inetutils
|
|
||||||
mtr
|
|
||||||
sysstat
|
|
||||||
tmux
|
|
||||||
vim
|
|
||||||
];
|
|
||||||
address = "45.79.76.142";
|
|
||||||
gateway = "45.79.76.1";
|
|
||||||
sshPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB/UeAaMECJNLMZ23vLb3A3XT7OJDcpj2OWgXzt8+GLU me@laptop";
|
|
||||||
allowedTCPPorts = vbox.guestPorts;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
linode: ENC[AES256_GCM,data:Rg/k/gmBJ8iBP9KW8Zom7gGecNG404v9oQ85MuXPB+fjKowmm36YJ61tiVhUADPsFMWezulJG3RpvpoqZLPU+8cCX1KPsfJgUN77MlQRjjdraqVMq/opcEXfwIs3g76y9hDvbTMVIGpKCVE8hl7N5XTRPkQPSpracj+papL0bdFLhmsDgGi/vmaH7zs9K6gwYQv/mzz2oy6oZh8NoAlF,iv:2Z4NLAQmf/m5oemdM7Z+MAAyVUBVZoA4Zia/bqcW8u0=,tag:WeVnU4swvvQdA7QU9Ax4Xg==,type:str]
|
|
||||||
sops:
|
|
||||||
kms: []
|
|
||||||
gcp_kms: []
|
|
||||||
azure_kv: []
|
|
||||||
hc_vault: []
|
|
||||||
age:
|
|
||||||
- recipient: age1nj0ulq6863y9tdk0pkwjx4ltuyjpx6gftwy27mk3gkwja6k325esgaerlr
|
|
||||||
enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAweERHTXJWU1BWNXJJUVNM
|
|
||||||
WUZObm82a0JHSWM2cWdBQTI2ckRvMnRGMDIwCm9TZWtTTUlaTTRuYVcvd1J3TnVF
|
|
||||||
dUN0NFdtaTZWL2IraE5BcE43WWdXcmMKLS0tIDFyQ3FGT1F4dkVtU0U5R2FNNlRa
|
|
||||||
dXB5OEx1clZiMktxdkFVUVpWOUtleU0Kb4E+x2cxcOayFigQDo9dv3e/si9a19YJ
|
|
||||||
mw2PUTb1Tm3PQ/ZXW6R6y5CfzFf7FhBTpRas84sPDg9MrOrWLygUgw==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
lastmodified: "2023-04-09T05:03:49Z"
|
|
||||||
mac: ENC[AES256_GCM,data:t03zcu+puxqs6bqb+7MJDY67UXJVN4RP48VSY2aBYtzRp4tcZ0zKpqGDJ2y9sFkLkUu5Mvha68g5Bd6uylQFwVyks2HQyyT0UFsQWJfpk9WGXElmJ+BeU4m51QFLK17rAFGSqvlHjbo3U47IgySlr4vViIyikOxY/UAUI0r2jsQ=,iv:Q5QgjDszLKbcqN415YLdqMGLa0b3Cy4WbXOtkT4HKBs=,tag:Cgpox1V73/UU4kg+dLgyWA==,type:str]
|
|
||||||
pgp: []
|
|
||||||
unencrypted_suffix: _unencrypted
|
|
||||||
version: 3.7.3
|
|
|
@ -1,198 +0,0 @@
|
||||||
{ system, pkgs, distribution, invDistribution }:
|
|
||||||
|
|
||||||
let
|
|
||||||
utils = pkgs.lib.callPackageWith pkgs ./utils.nix { };
|
|
||||||
|
|
||||||
customPkgs = (pkgs.callPackage (./../../..) {}).customPkgs {
|
|
||||||
inherit system pkgs utils;
|
|
||||||
};
|
|
||||||
|
|
||||||
getTarget = name: builtins.elemAt (builtins.getAttr name distribution) 0;
|
|
||||||
|
|
||||||
getDomain = name: (getTarget name).containers.system.domain;
|
|
||||||
|
|
||||||
realm = "myrealm";
|
|
||||||
|
|
||||||
smtp = utils.recursiveMerge [
|
|
||||||
{
|
|
||||||
from = "vaultwarden@${realm}.com";
|
|
||||||
fromName = "vaultwarden";
|
|
||||||
port = 587;
|
|
||||||
authMechanism = "Login";
|
|
||||||
}
|
|
||||||
vaultwarden.smtp
|
|
||||||
];
|
|
||||||
|
|
||||||
keycloak = customPkgs.keycloak {};
|
|
||||||
|
|
||||||
KeycloakService = customPkgs.mkKeycloakService {
|
|
||||||
name = "KeycloakService";
|
|
||||||
subdomain = keycloak.subdomain;
|
|
||||||
|
|
||||||
# TODO: Get these from infrastructure.nix
|
|
||||||
user = "keycloak";
|
|
||||||
group = "keycloak";
|
|
||||||
|
|
||||||
postgresServiceName = (getTarget "KeycloakPostgresDB").containers.postgresql-database.service_name;
|
|
||||||
initialAdminUsername = "admin";
|
|
||||||
|
|
||||||
keys = {
|
|
||||||
dbPassword = "keycloakdbpassword";
|
|
||||||
initialAdminPassword = "keycloakinitialadmin";
|
|
||||||
};
|
|
||||||
|
|
||||||
# logLevel = "DEBUG,org.hibernate:info,org.keycloak.authentication:debug,org.keycloak:info,org.postgresql:info,freemarker:info";
|
|
||||||
logLevel = "INFO";
|
|
||||||
hostname = "${keycloak.subdomain}.${getDomain "KeycloakService"}";
|
|
||||||
listenPort = 8080;
|
|
||||||
|
|
||||||
dbType = "postgres";
|
|
||||||
dbDatabase = keycloak.database.name;
|
|
||||||
dbUsername = keycloak.database.username;
|
|
||||||
dbHost = {KeycloakPostgresDB}: KeycloakPostgresDB.target.properties.hostname;
|
|
||||||
dbPort = (getTarget "KeycloakPostgresDB").containers.postgresql-database.port;
|
|
||||||
|
|
||||||
KeycloakPostgresDB = keycloak.db;
|
|
||||||
};
|
|
||||||
|
|
||||||
KeycloakCliService = customPkgs.mkKeycloakCliService rec {
|
|
||||||
name = "KeycloakCliService";
|
|
||||||
|
|
||||||
keycloakServiceName = "keycloak.service";
|
|
||||||
keycloakSecretsDir = (getTarget name).containers.keycloaksecrets.rootdir;
|
|
||||||
keycloakUrl = "https://${keycloak.subdomain}.${(getDomain "KeycloakService")}";
|
|
||||||
keycloakUser = KeycloakService.initialAdminUsername;
|
|
||||||
keys = {
|
|
||||||
userpasswords = "keycloakusers";
|
|
||||||
};
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit KeycloakService HaproxyService;
|
|
||||||
};
|
|
||||||
|
|
||||||
config = (utils.recursiveMerge [
|
|
||||||
rec {
|
|
||||||
inherit realm;
|
|
||||||
domain = getDomain name;
|
|
||||||
roles = {
|
|
||||||
user = [];
|
|
||||||
admin = ["user"];
|
|
||||||
};
|
|
||||||
users = {
|
|
||||||
me = {
|
|
||||||
email = "me@${domain}";
|
|
||||||
firstName = "Me";
|
|
||||||
lastName = "Me";
|
|
||||||
roles = ["admin"];
|
|
||||||
initialPassword = true;
|
|
||||||
};
|
|
||||||
friend = {
|
|
||||||
email = "friend@${domain}";
|
|
||||||
firstName = "Friend";
|
|
||||||
lastName = "Friend";
|
|
||||||
roles = ["user"];
|
|
||||||
initialPassword = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
vaultwarden.keycloakCliConfig
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
KeycloakHaproxyService = customPkgs.mkKeycloakHaproxyService {
|
|
||||||
name = "KeycloakHaproxyService";
|
|
||||||
|
|
||||||
domain = "https://${keycloak.subdomain}.${getDomain "KeycloakService"}";
|
|
||||||
realms = [realm];
|
|
||||||
|
|
||||||
inherit KeycloakService;
|
|
||||||
};
|
|
||||||
|
|
||||||
vaultwarden = customPkgs.vaultwarden {
|
|
||||||
subdomain = "vaultwarden";
|
|
||||||
ingress = 18005;
|
|
||||||
sso.realm = realm;
|
|
||||||
sso.userRole = "user";
|
|
||||||
sso.adminRole = "admin";
|
|
||||||
|
|
||||||
inherit smtp;
|
|
||||||
|
|
||||||
inherit distribution HaproxyService KeycloakService KeycloakCliService;
|
|
||||||
};
|
|
||||||
|
|
||||||
HaproxyService = customPkgs.mkHaproxyService {
|
|
||||||
name = "HaproxyService";
|
|
||||||
|
|
||||||
user = "http";
|
|
||||||
group = "http";
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit KeycloakHaproxyService;
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {...}:
|
|
||||||
let
|
|
||||||
domain = getDomain "HaproxyService";
|
|
||||||
in {
|
|
||||||
certPath = "/var/lib/acme/${domain}/full.pem";
|
|
||||||
stats = {
|
|
||||||
port = 8404;
|
|
||||||
uri = "/stats";
|
|
||||||
refresh = "10s";
|
|
||||||
prometheusUri = "/metrics";
|
|
||||||
};
|
|
||||||
defaults = {
|
|
||||||
default-server = "init-addr last,none";
|
|
||||||
};
|
|
||||||
resolvers = {
|
|
||||||
default = {
|
|
||||||
nameservers = {
|
|
||||||
ns1 = "127.0.0.1:53";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
sites = {
|
|
||||||
vaultwarden = vaultwarden.haproxy distribution.VaultwardenService;
|
|
||||||
keycloak = {
|
|
||||||
frontend = {
|
|
||||||
capture = [
|
|
||||||
"request header origin len 128"
|
|
||||||
];
|
|
||||||
acl = {
|
|
||||||
acl_keycloak = "hdr_beg(host) ${keycloak.subdomain}.";
|
|
||||||
acl_keycloak_authorized_origin = "capture.req.hdr(0) -m end .${domain}";
|
|
||||||
};
|
|
||||||
use_backend = "if acl_keycloak";
|
|
||||||
http-response = {
|
|
||||||
add-header = map (x: x + " if acl_keycloak_authorized_origin") [
|
|
||||||
"Access-Control-Allow-Origin %[capture.req.hdr(0)]"
|
|
||||||
"Access-Control-Allow-Methods GET,\\ HEAD,\\ OPTIONS,\\ POST,\\ PUT"
|
|
||||||
"Access-Control-Allow-Credentials true"
|
|
||||||
"Access-Control-Allow-Headers Origin,\\ Accept,\\ X-Requested-With,\\ Content-Type,\\ Access-Control-Request-Method,\\ Access-Control-Request-Headers,\\ Authorization"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
backend = {
|
|
||||||
servers = [
|
|
||||||
{
|
|
||||||
name = "keycloak1";
|
|
||||||
address = "127.0.0.1:8080"; # TODO: should use the hostname
|
|
||||||
resolvers = "default";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
cookie = "JSESSIONID prefix";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
|
|
||||||
with pkgs.lib.attrsets;
|
|
||||||
rec {
|
|
||||||
inherit KeycloakPostgresDB KeycloakService KeycloakCliService KeycloakHaproxyService;
|
|
||||||
|
|
||||||
inherit HaproxyService;
|
|
||||||
}
|
|
||||||
// keycloak.services
|
|
||||||
// vaultwarden.services
|
|
|
@ -1,17 +0,0 @@
|
||||||
### Deploy to prod
|
|
||||||
|
|
||||||
Please read the [deploy to staging](/deploystaging.md) first as all
|
|
||||||
commands are very similar. I only show a summary of the commands with
|
|
||||||
staging variables replaced by prod ones.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export NIXOPS_DEPLOYMENT=prod
|
|
||||||
export DISNIXOS_USE_NIXOPS=1
|
|
||||||
|
|
||||||
nixops create ./network-prod.nix -d prod
|
|
||||||
|
|
||||||
nixops deploy --option extra-builtins-file $(pwd)/extra-builtins.nix
|
|
||||||
nixops reboot
|
|
||||||
|
|
||||||
disnixos-env -s services.nix -n network-prod.nix -d distribution.nix
|
|
||||||
```
|
|
|
@ -1,52 +0,0 @@
|
||||||
# Deploy to staging environment
|
|
||||||
|
|
||||||
Instead of deploying to prod machines, you'll deploy to VMs running on
|
|
||||||
your computer with Virtualbox. This is tremendously helpful for
|
|
||||||
testing.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export NIXOPS_DEPLOYMENT=vboxtest
|
|
||||||
export DISNIXOS_USE_NIXOPS=1
|
|
||||||
|
|
||||||
nixops create ./network-virtualbox.nix -d vboxtest
|
|
||||||
|
|
||||||
nixops deploy --option extra-builtins-file $(pwd)/extra-builtins.nix
|
|
||||||
nixops reboot
|
|
||||||
|
|
||||||
disnixos-env -s services.nix -n network-virtualbox.nix -d distribution.nix
|
|
||||||
```
|
|
||||||
|
|
||||||
For the `nixops deploy` step to start, you'll need to generate all
|
|
||||||
necessary passwords. The easiest is to try the command and see on what
|
|
||||||
password it fails, generating it then re-issuing the command.
|
|
||||||
|
|
||||||
It's okay if the `nixops deploy` command fails to activate the new
|
|
||||||
configuration on first run because of the `virtualbox.service`. If
|
|
||||||
that happens, continue with the `nixops reboot` command. The service
|
|
||||||
will activate itself after the reboot.
|
|
||||||
|
|
||||||
Rebooting after deploying is anyway needed for systemd to pickup the
|
|
||||||
`/etc/systemd-mutable` path through the `SYSTEMD_UNIT_PATH`
|
|
||||||
environment variable.
|
|
||||||
|
|
||||||
The `extra-builtins-file` allows us to use password store as the
|
|
||||||
secrets manager. You'll probably see errors about missing passwords
|
|
||||||
when running this for the first time. To fix those, generate the
|
|
||||||
password with `pass`.
|
|
||||||
|
|
||||||
## Handle host reboot
|
|
||||||
|
|
||||||
After restarting the computer running the VMs, do `nixops start` and
|
|
||||||
continue from the `nixops deploy ...` step.
|
|
||||||
|
|
||||||
## Cleanup
|
|
||||||
|
|
||||||
To start from scratch, run `nixops destroy` and start at the `nixops
|
|
||||||
deploy ...` step. This can be useful after fiddling with creating
|
|
||||||
directories. You could do this on prod too but... it's probably not a
|
|
||||||
good idea.
|
|
||||||
|
|
||||||
Also, you'll need to add the `--no-upgrade` option when running
|
|
||||||
`disnixos-env` the first time. Otherwise, disnix will try to
|
|
||||||
deactivate services but since the machine is clean, it will fail to
|
|
||||||
deactivate the services.
|
|
|
@ -1,37 +0,0 @@
|
||||||
# Integration Tests
|
|
||||||
|
|
||||||
Integration tests configure real virtual machines and run tests on
|
|
||||||
those to assert some properties.
|
|
||||||
|
|
||||||
You can find all integration tests under the [tests/integration](/tests/integration) directory.
|
|
||||||
|
|
||||||
## Run integration tests
|
|
||||||
|
|
||||||
```console
|
|
||||||
nix-build -A tests.integration.all
|
|
||||||
```
|
|
||||||
|
|
||||||
To run the "simple" integration test for keycloak, execute:
|
|
||||||
|
|
||||||
```console
|
|
||||||
nix-build -A tests.integration.keycloak.simple
|
|
||||||
```
|
|
||||||
|
|
||||||
## Write integration tests
|
|
||||||
|
|
||||||
To create an integration test for disnix, you'll need:
|
|
||||||
- a tarball,
|
|
||||||
- a manifest build,
|
|
||||||
- and a test definition with a test script written in Python.
|
|
||||||
|
|
||||||
The [NixOS official
|
|
||||||
documentation](https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests)
|
|
||||||
has a complete reference on what python functions are available in the
|
|
||||||
test script.
|
|
||||||
|
|
||||||
To iterate on the test script, the easiest is by far to use the interactive mode by using the `.driverInteractive` attribute, like so:
|
|
||||||
|
|
||||||
```console
|
|
||||||
nix-build -A tests.integration.keycloak.simple.driverInteractive
|
|
||||||
./result/bin/nixos-test-driver
|
|
||||||
```
|
|
|
@ -1,6 +0,0 @@
|
||||||
# Deploy to Linode
|
|
||||||
|
|
||||||
To deploy on a [Linode](linode.com) server, you first need to follow
|
|
||||||
[this
|
|
||||||
guide](https://www.linode.com/docs/guides/install-nixos-on-linode) to
|
|
||||||
install NixOS.
|
|
|
@ -1,24 +0,0 @@
|
||||||
# Unit Tests
|
|
||||||
|
|
||||||
Unit tests are used in Self Host Blocks to check that parsing
|
|
||||||
configurations produce the expected result.
|
|
||||||
|
|
||||||
You can find all unit tests under the [tests/unit](/tests/unit) directory.
|
|
||||||
|
|
||||||
To run the units test, do:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix-instantiate --eval --strict . -A tests.unit
|
|
||||||
```
|
|
||||||
|
|
||||||
If all tests pass, you'll see the following output:
|
|
||||||
|
|
||||||
```
|
|
||||||
{ }
|
|
||||||
```
|
|
||||||
|
|
||||||
Otherwise, you'll see one attribute for each failing test. For example, you can dig into the first failing haproxy test with:
|
|
||||||
|
|
||||||
```
|
|
||||||
nix-instantiate --eval --strict . -A tests.unit.haproxy.0
|
|
||||||
```
|
|
|
@ -1,3 +0,0 @@
|
||||||
{ exec, ... }: {
|
|
||||||
pass = name: exec [./nix-pass.sh name];
|
|
||||||
}
|
|
|
@ -1,482 +0,0 @@
|
||||||
{ lib
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
|
|
||||||
with builtins;
|
|
||||||
with lib;
|
|
||||||
with lib.attrsets;
|
|
||||||
with lib.lists;
|
|
||||||
with lib.strings;
|
|
||||||
with utils;
|
|
||||||
let
|
|
||||||
getAttrWithDefault = name: default: attrset:
|
|
||||||
if isAttrs attrset && hasAttr name attrset then
|
|
||||||
getAttr name attrset
|
|
||||||
else
|
|
||||||
default;
|
|
||||||
|
|
||||||
augmentedContent = fieldName: rules: parent: set:
|
|
||||||
let
|
|
||||||
print = {rule = k: parent: v:
|
|
||||||
assert assertMsg (isString v || isInt v) "cannot print key '${fieldName}.${k}' of type '${typeOf v}', should be string or int instead";
|
|
||||||
"${k} ${toString v}";};
|
|
||||||
|
|
||||||
matchingRule = k: v: findFirst (rule: rule.match k parent v) print rules;
|
|
||||||
|
|
||||||
augment = parent: k: v:
|
|
||||||
let
|
|
||||||
match = matchingRule k v;
|
|
||||||
rule = if hasAttr "rule" match then match.rule else null;
|
|
||||||
rules = if hasAttr "rules" match then match.rules else null;
|
|
||||||
indent = map (x: if hasAttr "indent" match then match.indent + x else x);
|
|
||||||
headerFn = if hasAttr "header" match then match.header else null;
|
|
||||||
header = optional (headerFn != null) (headerFn k);
|
|
||||||
trailer = optional (headerFn != null) "";
|
|
||||||
content = header ++ indent (augmentedContent "${fieldName}.${k}" rules (parent ++ [k]) v) ++ trailer;
|
|
||||||
in
|
|
||||||
if rule != null
|
|
||||||
then rule k parent v
|
|
||||||
else
|
|
||||||
assert assertMsg (isAttrs v) "attempt to apply rules on key '${toString k}' which is a '${typeOf v}' but should be a set:\n${toString v}";
|
|
||||||
if hasAttr "order" match then
|
|
||||||
{
|
|
||||||
inherit (match) order;
|
|
||||||
inherit content;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
content;
|
|
||||||
|
|
||||||
augmented = mapAttrsToList (augment parent) (
|
|
||||||
assert assertMsg (isAttrs set) "attempt to apply rules on field ${fieldName} having type '${typeOf set}':\n${toString set}";
|
|
||||||
set
|
|
||||||
);
|
|
||||||
|
|
||||||
sortAugmented = sort (a: b:
|
|
||||||
(isAttrs a && hasAttr "order" a)
|
|
||||||
&& (isAttrs b && hasAttr "order" b)
|
|
||||||
&& a.order < b.order
|
|
||||||
);
|
|
||||||
|
|
||||||
onlyContent = (x: if isAttrs x && hasAttr "content" x then x.content else x);
|
|
||||||
in
|
|
||||||
flatten (map onlyContent (sortAugmented augmented));
|
|
||||||
|
|
||||||
updateByPath = path: fn: set:
|
|
||||||
if hasAttrByPath path set then
|
|
||||||
recursiveUpdate set (setAttrByPath path (fn (getAttrFromPath path set)))
|
|
||||||
else
|
|
||||||
set;
|
|
||||||
|
|
||||||
schema =
|
|
||||||
let
|
|
||||||
mkRule =
|
|
||||||
{ redirect ? false
|
|
||||||
, scheme ? "https"
|
|
||||||
, code ? null
|
|
||||||
, condition ? null
|
|
||||||
}:
|
|
||||||
concatStringsRecursive " " [
|
|
||||||
(optional redirect "redirect")
|
|
||||||
"scheme" scheme
|
|
||||||
(optional (code != null) "code ${toString code}")
|
|
||||||
(optional (condition != null) "if ${condition}")
|
|
||||||
];
|
|
||||||
|
|
||||||
mkBind =
|
|
||||||
{ addr
|
|
||||||
, ssl ? false
|
|
||||||
, crt ? null
|
|
||||||
}:
|
|
||||||
concatStringsRecursive " " [
|
|
||||||
"bind"
|
|
||||||
addr
|
|
||||||
(optional ssl "ssl")
|
|
||||||
(optional (crt != null) "crt ${crt}")
|
|
||||||
];
|
|
||||||
|
|
||||||
mkServer =
|
|
||||||
{ name
|
|
||||||
, address
|
|
||||||
, balance ? null
|
|
||||||
, check ? null
|
|
||||||
, httpcheck ? null
|
|
||||||
, forwardfor ? true
|
|
||||||
, resolvers ? null
|
|
||||||
}:
|
|
||||||
[
|
|
||||||
"mode http"
|
|
||||||
(optional forwardfor "option forwardfor")
|
|
||||||
(optional (httpcheck != null) "option httpchk ${httpcheck}")
|
|
||||||
(optional (balance != null) "balance ${balance}")
|
|
||||||
(concatStringsRecursive " " [
|
|
||||||
"server"
|
|
||||||
name
|
|
||||||
address
|
|
||||||
(optionals (check != null) (if
|
|
||||||
isBool check
|
|
||||||
then (if check then ["check"] else [])
|
|
||||||
else mapAttrsToList (k: v: "${k} ${v}") check))
|
|
||||||
(optional (resolvers != null) "resolvers ${resolvers}")
|
|
||||||
])
|
|
||||||
];
|
|
||||||
|
|
||||||
# Lua's import system requires the import path to be something like:
|
|
||||||
#
|
|
||||||
# /nix/store/123-name/<package>/<file.lua>
|
|
||||||
#
|
|
||||||
# Then the lua-prepend-path can be:
|
|
||||||
#
|
|
||||||
# /nix/store/123-name/?/<file.lua>
|
|
||||||
#
|
|
||||||
# Then when lua code imports <package>, it will search in the
|
|
||||||
# prepend paths and replace the question mark with the <package>
|
|
||||||
# name to get a match.
|
|
||||||
#
|
|
||||||
# But the config.source is actually without the <package> name:
|
|
||||||
#
|
|
||||||
# /nix/store/123-name/<file.lua>
|
|
||||||
#
|
|
||||||
# This requires us to create a new directory structure and we're
|
|
||||||
# using a linkFarm for this.
|
|
||||||
createPluginLinks = configs:
|
|
||||||
let
|
|
||||||
mkLink = name: config: {
|
|
||||||
inherit name;
|
|
||||||
path = config.source;
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
pkgs.linkFarm "haproxyplugins" (mapAttrsToList mkLink configs);
|
|
||||||
|
|
||||||
mkPlugin = links: name:
|
|
||||||
{ luapaths ? []
|
|
||||||
, cpaths ? []
|
|
||||||
, load ? null
|
|
||||||
, ...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
lua-prepend-path =
|
|
||||||
let
|
|
||||||
f = ext: type: path:
|
|
||||||
{
|
|
||||||
inherit type;
|
|
||||||
path =
|
|
||||||
if path == "." then
|
|
||||||
"${links}/${name}/?.${ext}"
|
|
||||||
else
|
|
||||||
"${links}/${name}/${path}/?.${ext}";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
map (f "lua" "path") (toList luapaths)
|
|
||||||
++ map (f "so" "cpath") (toList cpaths);
|
|
||||||
} // optionalAttrs (load != null) {
|
|
||||||
lua-load = ["${links}/${name}/${load}"];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Takes plugins as an attrset of name to {init, load, source},
|
|
||||||
# transforms them to a [attrset] with fields lua-prepend-path
|
|
||||||
# and optionally lua-load then returns a list of lines with all
|
|
||||||
# lua-prepend-path first and all lua-load afterwards.
|
|
||||||
mkPlugins = v:
|
|
||||||
let
|
|
||||||
f = recursiveMerge (mapAttrsToList (mkPlugin (createPluginLinks v)) v);
|
|
||||||
lua-prepend-path = map ({path, type}: "lua-prepend-path ${path} ${type}") (getAttrWithDefault "lua-prepend-path" [] f);
|
|
||||||
lua-load = map (x: "lua-load ${x}") (getAttrWithDefault "lua-load" [] f);
|
|
||||||
in
|
|
||||||
lua-prepend-path ++ lua-load;
|
|
||||||
in [
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "defaults";
|
|
||||||
order = 2;
|
|
||||||
indent = " ";
|
|
||||||
header = k: k;
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "timeout";
|
|
||||||
rule = k: parent: v: mapAttrsToList (k1: v1: "${k} ${k1} ${v1}") v;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "global";
|
|
||||||
order = 1;
|
|
||||||
indent = " ";
|
|
||||||
header = k: k;
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "plugins";
|
|
||||||
rule = k: parent: v: mkPlugins v;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "setenv";
|
|
||||||
rule = k: parent: v: mapAttrsToList (k: v: "setenv ${k} ${v}" ) v;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "resolvers";
|
|
||||||
order = 3;
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: parent: v: true;
|
|
||||||
header = k: "resolvers " + k;
|
|
||||||
indent = " ";
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "nameservers";
|
|
||||||
rule = k: parent: v: mapAttrsToList (k1: v1: "nameserver ${k1} ${v1}") v;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "frontend";
|
|
||||||
order = 4;
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: parent: v: true;
|
|
||||||
header = k: "frontend " + k;
|
|
||||||
indent = " ";
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "rules";
|
|
||||||
rule = k: parent: v: map mkRule v;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "bind" && isAttrs v;
|
|
||||||
rule = k: parent: v: mkBind v;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "use_backend";
|
|
||||||
rule = k: parent: v:
|
|
||||||
let
|
|
||||||
use = name: value: "use_backend ${name} ${toString value}";
|
|
||||||
in
|
|
||||||
if isList v then
|
|
||||||
map (v: use v.name v.value) v
|
|
||||||
else
|
|
||||||
use v.name v.value;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: true ;
|
|
||||||
rule = k: parent: v:
|
|
||||||
let
|
|
||||||
l = prefix: v:
|
|
||||||
if isAttrs v then
|
|
||||||
mapAttrsToList (k: v: l "${prefix} ${k}" v) v
|
|
||||||
else if isList v then
|
|
||||||
map (l prefix) v
|
|
||||||
else if isBool v then
|
|
||||||
optional v prefix
|
|
||||||
else
|
|
||||||
assert assertMsg (isString v) "value for field ${k} should be a string, bool, attr or list, got: ${typeOf v}";
|
|
||||||
"${prefix} ${v}";
|
|
||||||
in
|
|
||||||
l k v;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "backend";
|
|
||||||
order = 5;
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: parent: v: true;
|
|
||||||
header = k: "backend " + k;
|
|
||||||
indent = " ";
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "options";
|
|
||||||
rule = k: parent: v: v;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: parent: v: k == "servers";
|
|
||||||
rule = k: parent: v: map mkServer v;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
concatStringsRecursive = sep: strings:
|
|
||||||
concatStringsSep sep (flatten strings);
|
|
||||||
|
|
||||||
assertHasAttr = name: attrPath: v:
|
|
||||||
assertMsg
|
|
||||||
(hasAttrByPath attrPath v)
|
|
||||||
"no ${last attrPath} defined in config for site ${name}.${concatStringsSep "." (init attrPath)}, found attr names: ${toString (attrNames (getAttrFromPath (init attrPath) v))}";
|
|
||||||
|
|
||||||
# Takes a function producing a [nameValuePair], applies it to
|
|
||||||
# all name-value pair in the given set and merges the resulting
|
|
||||||
# [[nameValuePair]].
|
|
||||||
mapAttrsFlatten = f: set: listToAttrs (concatLists (mapAttrsToList f set));
|
|
||||||
|
|
||||||
mapIfIsAttrs = f: value:
|
|
||||||
if isAttrs value
|
|
||||||
then f value
|
|
||||||
else value;
|
|
||||||
|
|
||||||
flattenAttrs = sep: cond: set:
|
|
||||||
let
|
|
||||||
recurse = mapIfIsAttrs (mapAttrsFlatten (
|
|
||||||
n: v: let
|
|
||||||
result = recurse v;
|
|
||||||
in
|
|
||||||
if isAttrs result && cond n v
|
|
||||||
then mapAttrsToList (n2: v2: nameValuePair "${n}${sep}${n2}" v2) result
|
|
||||||
else [(nameValuePair n result)]
|
|
||||||
));
|
|
||||||
in recurse set;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit updateByPath recursiveMerge;
|
|
||||||
|
|
||||||
default =
|
|
||||||
{ user
|
|
||||||
, group
|
|
||||||
, certPath
|
|
||||||
, plugins ? {}
|
|
||||||
, globalEnvs ? {}
|
|
||||||
, stats ? null
|
|
||||||
, debug ? false
|
|
||||||
, sites ? {}
|
|
||||||
, globals ? {}
|
|
||||||
, defaults ? {}
|
|
||||||
, resolvers ? {}
|
|
||||||
}: {
|
|
||||||
global = {
|
|
||||||
# Silence a warning issued by haproxy. Using 2048
|
|
||||||
# instead of the default 1024 makes the connection stronger.
|
|
||||||
"tune.ssl.default-dh-param" = 2048;
|
|
||||||
|
|
||||||
maxconn = 20000;
|
|
||||||
|
|
||||||
inherit user group;
|
|
||||||
|
|
||||||
log = "/dev/log local0 info";
|
|
||||||
|
|
||||||
inherit plugins;
|
|
||||||
|
|
||||||
setenv = globalEnvs;
|
|
||||||
} // globals;
|
|
||||||
|
|
||||||
defaults = {
|
|
||||||
log = "global";
|
|
||||||
option = "httplog";
|
|
||||||
|
|
||||||
timeout = {
|
|
||||||
connect = "10s";
|
|
||||||
client = "15s";
|
|
||||||
server = "30s";
|
|
||||||
queue = "100s";
|
|
||||||
};
|
|
||||||
} // defaults;
|
|
||||||
|
|
||||||
frontend = {
|
|
||||||
http-to-https = {
|
|
||||||
mode = "http";
|
|
||||||
bind = "*:80";
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
redirect = true;
|
|
||||||
scheme = "https";
|
|
||||||
code = 301;
|
|
||||||
condition = "!{ ssl_fc }";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
backend = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
https = (
|
|
||||||
let
|
|
||||||
r = (
|
|
||||||
[{
|
|
||||||
mode = "http";
|
|
||||||
bind = {
|
|
||||||
addr = "*:443";
|
|
||||||
ssl = true;
|
|
||||||
crt = certPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
http-request = {
|
|
||||||
set-header = [
|
|
||||||
"X-Forwarded-Port %[dst_port]"
|
|
||||||
"X-Forwarded-For %[src]"
|
|
||||||
];
|
|
||||||
add-header = [
|
|
||||||
"X-Forwarded-Proto https"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
http-response = {
|
|
||||||
set-header = [
|
|
||||||
''Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"''
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}]
|
|
||||||
++ (mapAttrsToList (name: config:
|
|
||||||
assert assertHasAttr name ["frontend"] config;
|
|
||||||
(updateByPath ["frontend" "use_backend"] (x: [(nameValuePair name x)]) config).frontend
|
|
||||||
) sites)
|
|
||||||
++ (mapAttrsToList (name: config:
|
|
||||||
if (hasAttr "debugHeaders" config && (getAttr "debugHeaders" config) != null) then {
|
|
||||||
option = "httplog";
|
|
||||||
http-request = {
|
|
||||||
capture = "req.hdrs len 512 if ${config.debugHeaders}";
|
|
||||||
};
|
|
||||||
log-format = ''"%ci:%cp [%tr] %ft [[%hr]] %hs %{+Q}r"'';
|
|
||||||
} else {}
|
|
||||||
) sites)
|
|
||||||
);
|
|
||||||
in
|
|
||||||
recursiveMerge r
|
|
||||||
)
|
|
||||||
// optionalAttrs (debug) {
|
|
||||||
log-format = ''"%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %sslv %sslc %[ssl_fc_cipherlist_str]"'';
|
|
||||||
};
|
|
||||||
} // optionalAttrs (stats != null)
|
|
||||||
(let
|
|
||||||
stats_ = {
|
|
||||||
enable = true;
|
|
||||||
port = 8404;
|
|
||||||
uri = "/stats";
|
|
||||||
refresh = "10s";
|
|
||||||
prometheusUri = null;
|
|
||||||
hide-version = false;
|
|
||||||
} // stats;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
stats = {
|
|
||||||
bind = "localhost:${toString stats_.port}";
|
|
||||||
mode = "http";
|
|
||||||
stats = {
|
|
||||||
enable = stats_.enable;
|
|
||||||
hide-version = stats_.hide-version;
|
|
||||||
uri = stats_.uri;
|
|
||||||
refresh = stats_.refresh;
|
|
||||||
};
|
|
||||||
} // optionalAttrs (stats_.prometheusUri != null) {
|
|
||||||
http-request = [
|
|
||||||
"use-service prometheus-exporter if { path ${stats_.prometheusUri} }"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
backend =
|
|
||||||
mapAttrs' (name: config:
|
|
||||||
assert assertMsg (hasAttr "backend" config) "no backend defined in config for site ${name}, found attr names: ${toString (attrNames config)}";
|
|
||||||
nameValuePair name config.backend)
|
|
||||||
sites;
|
|
||||||
|
|
||||||
inherit resolvers;
|
|
||||||
};
|
|
||||||
|
|
||||||
render = config:
|
|
||||||
concatStringsSep "\n" (augmentedContent "" schema [] config);
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
{ pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
, user
|
|
||||||
, group
|
|
||||||
, config
|
|
||||||
, pidfile ? "/run/haproxy/haproxy.pid"
|
|
||||||
, socket ? "/run/haproxy/haproxy.sock"
|
|
||||||
, dependsOn ? {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
configcreator = pkgs.callPackage ./configcreator.nix {inherit utils;};
|
|
||||||
|
|
||||||
content = configcreator.render (configcreator.default (config dependsOn // {inherit user group;}));
|
|
||||||
configfile = pkgs.writeText "haproxy.cfg" content;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
inherit user group;
|
|
||||||
|
|
||||||
pkg = dependsOn: utils.systemd.mkService {
|
|
||||||
name = "haproxy";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=HAProxy Load Balancer
|
|
||||||
Documentation=https://www.haproxy.com/documentation/hapee/latest/onepage/
|
|
||||||
After=network.target network-online.target
|
|
||||||
Wants=network-online.target systemd-networkd-wait-online.target
|
|
||||||
${utils.unitDepends "After" dependsOn}
|
|
||||||
${utils.unitDepends "Wants" dependsOn}
|
|
||||||
|
|
||||||
StartLimitInterval=14400
|
|
||||||
StartLimitBurst=10
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Environment="CONFIG=${configfile}" "PIDFILE=${pidfile}" "EXTRAOPTS=-S ${socket}"
|
|
||||||
ExecStart=${pkgs.haproxy}/bin/haproxy -Ws -f $CONFIG -p $PIDFILE $EXTRAOPTS
|
|
||||||
ExecReload=${pkgs.haproxy}/bin/haproxy -Ws -f $CONFIG -c -q $EXTRAOPTS
|
|
||||||
ExecReload=${pkgs.coreutils}/bin/kill -USR2 $MAINPID
|
|
||||||
KillMode=mixed
|
|
||||||
Restart=always
|
|
||||||
SuccessExitStatus=143
|
|
||||||
Type=notify
|
|
||||||
|
|
||||||
|
|
||||||
# Restart=on-abnormal
|
|
||||||
RuntimeDirectory=haproxy
|
|
||||||
|
|
||||||
# KillMode=mixed
|
|
||||||
# KillSignal=SIGQUIT
|
|
||||||
TimeoutStopSec=5s
|
|
||||||
|
|
||||||
LimitNOFILE=1048576
|
|
||||||
LimitNPROC=512
|
|
||||||
|
|
||||||
PrivateDevices=true
|
|
||||||
LockPersonality=true
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateDevices=true
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectClock=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectHome=true
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectKernelLogs=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectSystem=full
|
|
||||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
|
|
||||||
RestrictNamespaces=true
|
|
||||||
RestrictRealtime=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
|
|
||||||
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
|
||||||
# AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
||||||
|
|
||||||
# ProtectSystem=strict
|
|
||||||
# ReadWritePaths=/var/lib/haproxy /var/log/haproxy
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit dependsOn;
|
|
||||||
type = "systemd-unit";
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
# Jellyfin
|
|
||||||
|
|
||||||
This packages installs Jellyfin.
|
|
||||||
|
|
||||||
## TODOs
|
|
||||||
|
|
||||||
Review all settings for jellyfin, for example prometheus metrics https://jellyfin.org/docs/general/networking/monitoring/
|
|
|
@ -1,210 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
}:
|
|
||||||
{ realm
|
|
||||||
, domain
|
|
||||||
, roles ? {}
|
|
||||||
, clients ? {}
|
|
||||||
, users ? {}
|
|
||||||
, groups ? []
|
|
||||||
}:
|
|
||||||
|
|
||||||
with builtins;
|
|
||||||
with (pkgs.lib.attrsets);
|
|
||||||
let
|
|
||||||
mkRole = k: v:
|
|
||||||
let
|
|
||||||
iscomposite = (length v) > 0;
|
|
||||||
in {
|
|
||||||
name = k;
|
|
||||||
composite = if iscomposite then true else false;
|
|
||||||
} // optionalAttrs iscomposite {
|
|
||||||
composites = {
|
|
||||||
realm = v;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
mkClientRole =
|
|
||||||
let
|
|
||||||
roles = config: config.roles or [];
|
|
||||||
|
|
||||||
c = v:
|
|
||||||
{
|
|
||||||
name = v;
|
|
||||||
clientRole = true;
|
|
||||||
};
|
|
||||||
in k: config: map c (roles config);
|
|
||||||
|
|
||||||
mkGroup = name: {
|
|
||||||
inherit name;
|
|
||||||
path = "/${name}";
|
|
||||||
attributes = {};
|
|
||||||
realmRoles = [];
|
|
||||||
clientRoles = {};
|
|
||||||
subGroups = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
mkClient = k: config:
|
|
||||||
let
|
|
||||||
url = "https://${k}.${domain}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
clientId = k;
|
|
||||||
rootUrl = url;
|
|
||||||
clientAuthenticatorType = "client-secret";
|
|
||||||
redirectUris = ["${url}/oauth2/callback"];
|
|
||||||
webOrigins = [url];
|
|
||||||
authorizationServicesEnabled = true;
|
|
||||||
serviceAccountsEnabled = true;
|
|
||||||
protocol = "openid-connect";
|
|
||||||
publicClient = false;
|
|
||||||
protocolMappers = [
|
|
||||||
{
|
|
||||||
name = "Client ID";
|
|
||||||
protocol = "openid-connect";
|
|
||||||
protocolMapper = "oidc-usersessionmodel-note-mapper";
|
|
||||||
consentRequired = false;
|
|
||||||
config = {
|
|
||||||
"user.session.note" = "clientId";
|
|
||||||
"id.token.claim" = "true";
|
|
||||||
"access.token.claim" = "true";
|
|
||||||
"claim.name" = "clientId";
|
|
||||||
"jsonType.label" = "String";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "Client Host";
|
|
||||||
protocol = "openid-connect";
|
|
||||||
protocolMapper = "oidc-usersessionmodel-note-mapper";
|
|
||||||
consentRequired = false;
|
|
||||||
config = {
|
|
||||||
"user.session.note" = "clientHost";
|
|
||||||
"id.token.claim" = "true";
|
|
||||||
"access.token.claim" = "true";
|
|
||||||
"claim.name" = "clientHost";
|
|
||||||
"jsonType.label" = "String";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "Client IP Address";
|
|
||||||
protocol = "openid-connect";
|
|
||||||
protocolMapper = "oidc-usersessionmodel-note-mapper";
|
|
||||||
consentRequired = false;
|
|
||||||
config = {
|
|
||||||
"user.session.note" = "clientAddress";
|
|
||||||
"id.token.claim" = "true";
|
|
||||||
"access.token.claim" = "true";
|
|
||||||
"claim.name" = "clientAddress";
|
|
||||||
"jsonType.label" = "String";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "Audience";
|
|
||||||
protocol = "openid-connect";
|
|
||||||
protocolMapper = "oidc-audience-mapper";
|
|
||||||
config = {
|
|
||||||
"included.client.audience" = k;
|
|
||||||
"id.token.claim" = "false";
|
|
||||||
"access.token.claim" = "true";
|
|
||||||
"included.custom.audience" = k;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "Group";
|
|
||||||
protocol = "openid-connect";
|
|
||||||
protocolMapper = "oidc-group-membership-mapper";
|
|
||||||
config = {
|
|
||||||
"full.path" = "true";
|
|
||||||
"id.token.claim" = "true";
|
|
||||||
"access.token.claim" = "true";
|
|
||||||
"claim.name" = "groups";
|
|
||||||
"userinfo.token.claim" = "true";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
authorizationSettings = {
|
|
||||||
policyEnforcementMode = "ENFORCING";
|
|
||||||
|
|
||||||
resources =
|
|
||||||
let
|
|
||||||
mkResource = name: uris: {
|
|
||||||
inherit name;
|
|
||||||
type = "urn:${k}:resources:${name}";
|
|
||||||
ownerManagedAccess = false;
|
|
||||||
inherit uris;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
mapAttrsToList mkResource (config.resourcesUris or {});
|
|
||||||
|
|
||||||
policies =
|
|
||||||
let
|
|
||||||
mkPolicyRole = role: {
|
|
||||||
id = role;
|
|
||||||
required = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
mkPolicy = name: roles: {
|
|
||||||
name = "${concatStringsSep "," roles} has access";
|
|
||||||
type = "role";
|
|
||||||
logic = "POSITIVE";
|
|
||||||
decisionStrategy = "UNANIMOUS";
|
|
||||||
config = {
|
|
||||||
roles = toJSON (map mkPolicyRole roles);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
mkPermission = name: roles: resources: {
|
|
||||||
name = "${concatStringsSep "," roles} has access to ${concatStringsSep "," resources}";
|
|
||||||
type = "resource";
|
|
||||||
logic = "POSITIVE";
|
|
||||||
decisionStrategy = "UNANIMOUS";
|
|
||||||
config = {
|
|
||||||
resources = toJSON resources;
|
|
||||||
applyPolicies = toJSON (map (r: "${concatStringsSep "," roles} has access") roles);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
(mapAttrsToList (name: {roles, ...}: mkPolicy name roles) (config.access or {}))
|
|
||||||
++ (mapAttrsToList (name: {roles, resources}: mkPermission name roles resources) (config.access or {}));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
mkUser = k: config:
|
|
||||||
{
|
|
||||||
username = k;
|
|
||||||
enabled = true;
|
|
||||||
emailVerified = true;
|
|
||||||
|
|
||||||
inherit (config) email firstName lastName;
|
|
||||||
} // optionalAttrs (config ? "groups") {
|
|
||||||
inherit (config) groups;
|
|
||||||
} // optionalAttrs (config ? "roles") {
|
|
||||||
realmRoles = config.roles;
|
|
||||||
} // optionalAttrs (config ? "initialPassword") {
|
|
||||||
credentials = [
|
|
||||||
{
|
|
||||||
type = "password";
|
|
||||||
userLabel = "initial";
|
|
||||||
value = "$(keycloak.users.${k}.password)";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit realm;
|
|
||||||
id = realm;
|
|
||||||
enabled = true;
|
|
||||||
|
|
||||||
clients = mapAttrsToList mkClient clients;
|
|
||||||
|
|
||||||
roles = {
|
|
||||||
realm = mapAttrsToList mkRole roles;
|
|
||||||
client = mapAttrs mkClientRole clients;
|
|
||||||
};
|
|
||||||
|
|
||||||
groups = map mkGroup groups;
|
|
||||||
|
|
||||||
users = mapAttrsToList mkUser users;
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
|
|
||||||
, config
|
|
||||||
|
|
||||||
, keycloakServiceName
|
|
||||||
, keycloakSecretsDir
|
|
||||||
, keycloakAvailabilityTimeout ? "120s"
|
|
||||||
, keycloakUrl
|
|
||||||
, keycloakUser
|
|
||||||
, keys
|
|
||||||
, debug ? false
|
|
||||||
|
|
||||||
, dependsOn ? {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
# https://github.com/adorsys/keycloak-config-cli
|
|
||||||
|
|
||||||
# Password must be given through a file name "keycloak.password" under keycloakSecretsDir.
|
|
||||||
|
|
||||||
let
|
|
||||||
configcreator = pkgs.callPackage ./configcreator.nix {};
|
|
||||||
|
|
||||||
configfile = pkgs.writeText "keycloakcliconfig.json" (builtins.toJSON (configcreator config));
|
|
||||||
|
|
||||||
envs = lib.concatMapStrings (x: "\nEnvironment=" + x) ([
|
|
||||||
"SPRING_CONFIG_IMPORT=configtree:${keycloakSecretsDir}/"
|
|
||||||
"KEYCLOAK_URL=${keycloakUrl}"
|
|
||||||
"KEYCLOAK_USER=${keycloakUser}"
|
|
||||||
"KEYCLOAK_AVAILABILITYCHECK_ENABLED=true"
|
|
||||||
"KEYCLOAK_AVAILABILITYCHECK_TIMEOUT=${keycloakAvailabilityTimeout}"
|
|
||||||
"IMPORT_VARSUBSTITUTION_ENABLED=true"
|
|
||||||
"IMPORT_FILES_LOCATIONS=${configfile}"
|
|
||||||
] ++ (if !debug then [] else [
|
|
||||||
"DEBUG=true"
|
|
||||||
"LOGGING_LEVEL_ROOT=debug"
|
|
||||||
"LOGGING_LEVEL_HTTP=debug"
|
|
||||||
"LOGGING_LEVEL_REALMCONFIG=debug"
|
|
||||||
"LOGGING_LEVEL_KEYCLOAKCONFIGCLI=debug"
|
|
||||||
]));
|
|
||||||
|
|
||||||
keycloak-cli-config = pkgs.stdenv.mkDerivation rec {
|
|
||||||
pname = "keycloak-cli-config";
|
|
||||||
version = "5.3.1";
|
|
||||||
keycloakVersion = "18.0.2";
|
|
||||||
|
|
||||||
src = pkgs.fetchurl {
|
|
||||||
url = "https://github.com/adorsys/keycloak-config-cli/releases/download/v${version}/keycloak-config-cli-${keycloakVersion}.jar";
|
|
||||||
sha256 = "sha256-vC0d0g5TFddetpBwRDMokloTCr7ibFK//Yuvh+m77RA=";
|
|
||||||
};
|
|
||||||
|
|
||||||
buildInputs = [ pkgs.makeWrapper pkgs.jre ];
|
|
||||||
|
|
||||||
phases = [ "installPhase" ];
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/bin
|
|
||||||
cp $src $out/bin/keycloak-cli-config.jar
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
pkg = {...}: utils.systemd.mkService rec {
|
|
||||||
name = "keycloak-cli-config";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=Keycloak Realm Config
|
|
||||||
After=${keycloakServiceName}
|
|
||||||
Wants=${keycloakServiceName}
|
|
||||||
After=${utils.keyServiceDependencies keys}
|
|
||||||
Wants=${utils.keyServiceDependencies keys}
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=keycloakcli
|
|
||||||
Group=keycloakcli
|
|
||||||
|
|
||||||
${utils.keyEnvironmentFile keys.userpasswords}
|
|
||||||
Type=oneshot${envs}
|
|
||||||
ExecStart=${pkgs.jre}/bin/java -jar ${keycloak-cli-config}/bin/keycloak-cli-config.jar
|
|
||||||
|
|
||||||
RuntimeDirectory=keycloak-cli-config
|
|
||||||
|
|
||||||
PrivateDevices=true
|
|
||||||
LockPersonality=true
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateDevices=true
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectClock=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectHome=true
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectKernelLogs=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectSystem=full
|
|
||||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
|
|
||||||
RestrictNamespaces=true
|
|
||||||
RestrictRealtime=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit dependsOn;
|
|
||||||
type = "systemd-unit";
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name ? "keycloak-haproxy"
|
|
||||||
, domain
|
|
||||||
, realms ? []
|
|
||||||
, every ? "10m"
|
|
||||||
|
|
||||||
, KeycloakService
|
|
||||||
}:
|
|
||||||
|
|
||||||
rec {
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
stateDir = "keycloak-public-keys";
|
|
||||||
downloadDir = "/var/lib/keycloak-public-keys";
|
|
||||||
systemdUnitFile = "keycloak-haproxy.service";
|
|
||||||
|
|
||||||
pkg =
|
|
||||||
with pkgs.lib;
|
|
||||||
let
|
|
||||||
bin = pkgs.writeShellApplication {
|
|
||||||
name = "get-realms.sh";
|
|
||||||
runtimeInputs = [ pkgs.coreutils pkgs.curl pkgs.jq ];
|
|
||||||
text = ''
|
|
||||||
set -euxo pipefail
|
|
||||||
|
|
||||||
realms="$1"
|
|
||||||
|
|
||||||
for realm in $realms; do
|
|
||||||
curl "${domain}/realms/$realm" | jq --raw-output .public_key > "${downloadDir}/$realm.pem"
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{ KeycloakService
|
|
||||||
, ...
|
|
||||||
}: utils.systemd.mkService rec {
|
|
||||||
name = "keycloak-haproxy";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=Get Keycloak realms for Haproxy
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=${bin}/bin/get-realms.sh ${concatStringsSep " " realms}
|
|
||||||
DynamicUser=true
|
|
||||||
|
|
||||||
CapabilityBoundingSet=
|
|
||||||
AmbientCapabilities=
|
|
||||||
StateDirectory=${stateDir}
|
|
||||||
PrivateUsers=yes
|
|
||||||
NoNewPrivileges=yes
|
|
||||||
ProtectSystem=strict
|
|
||||||
ProtectHome=yes
|
|
||||||
PrivateTmp=yes
|
|
||||||
PrivateDevices=yes
|
|
||||||
ProtectHostname=yes
|
|
||||||
ProtectClock=yes
|
|
||||||
ProtectKernelTunables=yes
|
|
||||||
ProtectKernelModules=yes
|
|
||||||
ProtectKernelLogs=yes
|
|
||||||
ProtectControlGroups=yes
|
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
||||||
RestrictNamespaces=yes
|
|
||||||
LockPersonality=yes
|
|
||||||
MemoryDenyWriteExecute=yes
|
|
||||||
RestrictRealtime=yes
|
|
||||||
RestrictSUIDSGID=yes
|
|
||||||
RemoveIPC=yes
|
|
||||||
|
|
||||||
SystemCallFilter=@system-service
|
|
||||||
SystemCallFilter=~@privileged @resources
|
|
||||||
SystemCallArchitectures=native
|
|
||||||
'';
|
|
||||||
|
|
||||||
timer = ''
|
|
||||||
[Unit]
|
|
||||||
Description=Run ${name}
|
|
||||||
After=network.target ${KeycloakService.systemdUnitFile}
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnUnitActiveSec=${every}
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit KeycloakService;
|
|
||||||
};
|
|
||||||
type = "systemd-unit";
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
{ customPkgs
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ serviceName ? "Keycloak"
|
|
||||||
, subdomain ? "keycloak"
|
|
||||||
|
|
||||||
, database ?
|
|
||||||
{
|
|
||||||
name = subdomain;
|
|
||||||
username = "keycloak";
|
|
||||||
# TODO: use passwordFile
|
|
||||||
password = "keycloak";
|
|
||||||
}
|
|
||||||
}:
|
|
||||||
rec {
|
|
||||||
inherit subdomain;
|
|
||||||
inherit database;
|
|
||||||
|
|
||||||
db = customPkgs.mkPostgresDB {
|
|
||||||
name = "KeycloakPostgresDB";
|
|
||||||
database = database.name;
|
|
||||||
username = database.username;
|
|
||||||
# TODO: use passwordFile
|
|
||||||
password = database.password;
|
|
||||||
};
|
|
||||||
|
|
||||||
services = {
|
|
||||||
${db.name} = db;
|
|
||||||
};
|
|
||||||
|
|
||||||
distribute = on: {
|
|
||||||
${db.name} = on;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
, user ? "keycloak"
|
|
||||||
, group ? "keycloak"
|
|
||||||
, dbType ? "postgres"
|
|
||||||
, postgresServiceName
|
|
||||||
, initialAdminUsername ? null
|
|
||||||
, keys
|
|
||||||
, listenPort ? 8080
|
|
||||||
|
|
||||||
, logLevel ? "INFO"
|
|
||||||
, metricsEnabled ? false
|
|
||||||
, hostname
|
|
||||||
, subdomain
|
|
||||||
|
|
||||||
, dbUsername ? "keycloak"
|
|
||||||
, dbHost ? x: "localhost"
|
|
||||||
, dbPort ? "5432"
|
|
||||||
, dbDatabase ? "keycloak"
|
|
||||||
|
|
||||||
, KeycloakPostgresDB
|
|
||||||
}:
|
|
||||||
|
|
||||||
assert lib.assertOneOf "dbType" dbType ["postgres"];
|
|
||||||
|
|
||||||
let
|
|
||||||
keycloak = pkgs.keycloak.override {
|
|
||||||
# This is needed for keycloak to build with the correct driver.
|
|
||||||
confFile = pkgs.writeText "keycloak.conf" ''
|
|
||||||
db=${dbType}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
|
||||||
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
inherit initialAdminUsername;
|
|
||||||
inherit hostname subdomain listenPort;
|
|
||||||
|
|
||||||
systemdUnitFile = "${name}.service";
|
|
||||||
|
|
||||||
pkg = { KeycloakPostgresDB }:
|
|
||||||
let
|
|
||||||
configFile = pkgs.writeText "keycloak.conf" ''
|
|
||||||
# The password of the database user is given by an environment variable.
|
|
||||||
db=${dbType}
|
|
||||||
db-username=${dbUsername}
|
|
||||||
db-url-host=${dbHost {inherit KeycloakPostgresDB;}}
|
|
||||||
db-url-port=${dbPort}
|
|
||||||
db-url-database=${dbDatabase}
|
|
||||||
# db-url-properties= # Would be used for ssl, see https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/web-apps/keycloak.nix#L491
|
|
||||||
|
|
||||||
# Observability
|
|
||||||
|
|
||||||
# If the server should expose metrics and healthcheck endpoints.
|
|
||||||
metrics-enabled=${if metricsEnabled then "true" else "false"}
|
|
||||||
|
|
||||||
# HTTP
|
|
||||||
|
|
||||||
http-host=127.0.0.1
|
|
||||||
http-port=${builtins.toString listenPort}
|
|
||||||
|
|
||||||
# The file path to a server certificate or certificate chain in PEM format.
|
|
||||||
#https-certificate-file=''${kc.home.dir}conf/server.crt.pem
|
|
||||||
|
|
||||||
# The file path to a private key in PEM format.
|
|
||||||
#https-certificate-key-file=''${kc.home.dir}conf/server.key.pem
|
|
||||||
|
|
||||||
# The proxy address forwarding mode if the server is behind a reverse proxy.
|
|
||||||
# https://www.keycloak.org/server/reverseproxy
|
|
||||||
proxy=edge
|
|
||||||
|
|
||||||
# Do not attach route to cookies and rely on the session affinity capabilities from reverse proxy
|
|
||||||
#spi-sticky-session-encoder-infinispan-should-attach-route=false
|
|
||||||
|
|
||||||
# Hostname for the Keycloak server.
|
|
||||||
hostname=${hostname}
|
|
||||||
|
|
||||||
spi-x509cert-lookup-provider=haproxy
|
|
||||||
|
|
||||||
log-level=${logLevel}
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
with lib.attrsets;
|
|
||||||
utils.systemd.mkService rec {
|
|
||||||
name = "keycloak";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=Keycloak server
|
|
||||||
After=network-online.target
|
|
||||||
Wants=network-online.target systemd-networkd-wait-online.service ${postgresServiceName}
|
|
||||||
After=${utils.keyServiceDependencies keys}
|
|
||||||
Wants=${utils.keyServiceDependencies keys}
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=${user}
|
|
||||||
Group=${group}
|
|
||||||
|
|
||||||
${utils.keyEnvironmentFile keys.dbPassword}
|
|
||||||
${if initialAdminUsername != null then "Environment=KEYCLOAK_ADMIN="+initialAdminUsername else ""}
|
|
||||||
${if hasAttr "initialAdminPassword" keys then utils.keyEnvironmentFile keys.initialAdminPassword else ""}
|
|
||||||
Environment=PATH=${pkgs.coreutils}/bin
|
|
||||||
Environment=KC_HOME_DIR="/run/keycloak"
|
|
||||||
|
|
||||||
# build is ran upstream in the pkgs.keycloak definition, we add
|
|
||||||
# the --optimized flag to avoid running build on startup
|
|
||||||
ExecStart=${keycloak}/bin/kc.sh -cf ${configFile} start --optimized
|
|
||||||
|
|
||||||
# ReadWritePaths=/var/lib/keycloak
|
|
||||||
# ReadWritePaths=/var/log/keycloak
|
|
||||||
# ReadWritePaths=/usr/share/java/keycloak/lib/quarkus
|
|
||||||
RuntimeDirectory=keycloak
|
|
||||||
DynamicUser=true
|
|
||||||
|
|
||||||
# Disable timeout logic and wait until process is stopped
|
|
||||||
TimeoutStopSec=0
|
|
||||||
TimeoutStartSec=10min
|
|
||||||
|
|
||||||
# SIGTERM signal is used to stop the Java process
|
|
||||||
KillSignal=SIGTERM
|
|
||||||
|
|
||||||
# Send the signal only to the JVM rather than its control group
|
|
||||||
KillMode=process
|
|
||||||
|
|
||||||
# Java process is never killed
|
|
||||||
SendSIGKILL=no
|
|
||||||
|
|
||||||
# When a JVM receives a SIGTERM signal it exits with code 143
|
|
||||||
SuccessExitStatus=143
|
|
||||||
|
|
||||||
# Hardening options
|
|
||||||
# CapabilityBoundingSet=
|
|
||||||
# AmbientCapabilities=CAP_NET_BIND_SERVICES
|
|
||||||
# NoNewPrivileges=true
|
|
||||||
# Fails with:
|
|
||||||
# Failed to set up mount namespacing: /run/systemd/unit-root/var/lib/keycloak: No such file or directory
|
|
||||||
# ProtectHome=true
|
|
||||||
# ProtectSystem=strict
|
|
||||||
# ProtectKernelTunables=true
|
|
||||||
# ProtectKernelModules=true
|
|
||||||
# ProtectControlGroups=true
|
|
||||||
# PrivateTmp=true
|
|
||||||
# PrivateDevices=true
|
|
||||||
# LockPersonality=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit KeycloakPostgresDB;
|
|
||||||
};
|
|
||||||
type = "systemd-unit";
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
let
|
|
||||||
hercules-ci-agent =
|
|
||||||
builtins.fetchTarball "https://github.com/hercules-ci/hercules-ci-agent/archive/stable.tar.gz";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
network.description = "Hercules CI agents";
|
|
||||||
|
|
||||||
agent = {
|
|
||||||
imports = [
|
|
||||||
(hercules-ci-agent + "/module.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
services.hercules-ci-agent.enable = true;
|
|
||||||
services.hercules-ci-agent.concurrentTasks = 4; # Number of jobs to run
|
|
||||||
deployment.keys."cluster-join-token.key".keyFile = ./cluster-join-token.key;
|
|
||||||
deployment.keys."binary-caches.json".keyFile = ./binary-caches.json;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,252 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
, siteName
|
|
||||||
, user ? "http"
|
|
||||||
, group ? "http"
|
|
||||||
, pidFile ? "/run/nginx/nginx.pid"
|
|
||||||
, runtimeDirectory
|
|
||||||
|
|
||||||
, config ? {}
|
|
||||||
, dependsOn ? {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
nginxSocket = "${runtimeDirectory}/${config.siteName}.sock";
|
|
||||||
|
|
||||||
listen =
|
|
||||||
if nginxSocket != null then
|
|
||||||
"unix:${nginxSocket}"
|
|
||||||
else
|
|
||||||
config.port;
|
|
||||||
|
|
||||||
fastcgi =
|
|
||||||
if config.phpFpmSiteSocket == null then
|
|
||||||
""
|
|
||||||
else
|
|
||||||
''
|
|
||||||
location ~ \.php$ {
|
|
||||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
|
||||||
fastcgi_pass unix:${config.phpFpmSiteSocket};
|
|
||||||
fastcgi_index index.php;
|
|
||||||
|
|
||||||
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
|
||||||
fastcgi_param SERVER_SOFTWARE nginx;
|
|
||||||
fastcgi_param QUERY_STRING $query_string;
|
|
||||||
fastcgi_param REQUEST_METHOD $request_method;
|
|
||||||
fastcgi_param CONTENT_TYPE $content_type;
|
|
||||||
fastcgi_param CONTENT_LENGTH $content_length;
|
|
||||||
fastcgi_param SCRIPT_FILENAME ${config.siteRoot}$fastcgi_script_name;
|
|
||||||
# fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
|
||||||
fastcgi_param REQUEST_URI $request_uri;
|
|
||||||
fastcgi_param DOCUMENT_URI $document_uri;
|
|
||||||
fastcgi_param DOCUMENT_ROOT ${config.siteRoot};
|
|
||||||
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
|
||||||
fastcgi_param REMOTE_ADDR $remote_addr;
|
|
||||||
fastcgi_param REMOTE_PORT $remote_port;
|
|
||||||
fastcgi_param SERVER_ADDR $server_addr;
|
|
||||||
fastcgi_param SERVER_PORT $server_port;
|
|
||||||
fastcgi_param SERVER_NAME $server_name;
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
|
|
||||||
mkConfig =
|
|
||||||
{ port
|
|
||||||
, siteName
|
|
||||||
, siteRoot ? "/usr/share/webapps/${siteName}"
|
|
||||||
, siteSocket ? null
|
|
||||||
, phpFpmSiteSocket ? null
|
|
||||||
, logLevel ? "WARN"
|
|
||||||
}: ''
|
|
||||||
error_log syslog:server=unix:/dev/log,tag=nginx${siteName},nohostname,severity=error;
|
|
||||||
|
|
||||||
worker_processes 5;
|
|
||||||
worker_rlimit_nofile 8192;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 4096;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
access_log syslog:server=unix:/dev/log,tag=nginx${siteName},nohostname,severity=info combined;
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen ${listen};
|
|
||||||
root ${siteRoot};
|
|
||||||
|
|
||||||
index index.php index.html;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
${fastcgi}
|
|
||||||
}
|
|
||||||
|
|
||||||
types {
|
|
||||||
text/html html htm shtml;
|
|
||||||
text/css css;
|
|
||||||
text/xml xml;
|
|
||||||
image/gif gif;
|
|
||||||
image/jpeg jpeg jpg;
|
|
||||||
application/x-javascript js;
|
|
||||||
application/atom+xml atom;
|
|
||||||
application/rss+xml rss;
|
|
||||||
|
|
||||||
text/mathml mml;
|
|
||||||
text/plain txt;
|
|
||||||
text/vnd.sun.j2me.app-descriptor jad;
|
|
||||||
text/vnd.wap.wml wml;
|
|
||||||
text/x-component htc;
|
|
||||||
|
|
||||||
image/png png;
|
|
||||||
image/tiff tif tiff;
|
|
||||||
image/vnd.wap.wbmp wbmp;
|
|
||||||
image/x-icon ico;
|
|
||||||
image/x-jng jng;
|
|
||||||
image/x-ms-bmp bmp;
|
|
||||||
image/svg+xml svg svgz;
|
|
||||||
image/webp webp;
|
|
||||||
|
|
||||||
application/java-archive jar war ear;
|
|
||||||
application/mac-binhex40 hqx;
|
|
||||||
application/msword doc;
|
|
||||||
application/pdf pdf;
|
|
||||||
application/postscript ps eps ai;
|
|
||||||
application/rtf rtf;
|
|
||||||
application/vnd.ms-excel xls;
|
|
||||||
application/vnd.ms-powerpoint ppt;
|
|
||||||
application/vnd.wap.wmlc wmlc;
|
|
||||||
application/vnd.google-earth.kml+xml kml;
|
|
||||||
application/vnd.google-earth.kmz kmz;
|
|
||||||
application/x-7z-compressed 7z;
|
|
||||||
application/x-cocoa cco;
|
|
||||||
application/x-java-archive-diff jardiff;
|
|
||||||
application/x-java-jnlp-file jnlp;
|
|
||||||
application/x-makeself run;
|
|
||||||
application/x-perl pl pm;
|
|
||||||
application/x-pilot prc pdb;
|
|
||||||
application/x-rar-compressed rar;
|
|
||||||
application/x-redhat-package-manager rpm;
|
|
||||||
application/x-sea sea;
|
|
||||||
application/x-shockwave-flash swf;
|
|
||||||
application/x-stuffit sit;
|
|
||||||
application/x-tcl tcl tk;
|
|
||||||
application/x-x509-ca-cert der pem crt;
|
|
||||||
application/x-xpinstall xpi;
|
|
||||||
application/xhtml+xml xhtml;
|
|
||||||
application/zip zip;
|
|
||||||
|
|
||||||
application/octet-stream bin exe dll;
|
|
||||||
application/octet-stream deb;
|
|
||||||
application/octet-stream dmg;
|
|
||||||
application/octet-stream eot;
|
|
||||||
application/octet-stream iso img;
|
|
||||||
application/octet-stream msi msp msm;
|
|
||||||
|
|
||||||
audio/midi mid midi kar;
|
|
||||||
audio/mpeg mp3;
|
|
||||||
audio/ogg ogg;
|
|
||||||
audio/x-m4a m4a;
|
|
||||||
audio/x-realaudio ra;
|
|
||||||
|
|
||||||
video/3gpp 3gpp 3gp;
|
|
||||||
video/mp4 mp4;
|
|
||||||
video/mpeg mpeg mpg;
|
|
||||||
video/quicktime mov;
|
|
||||||
video/webm webm;
|
|
||||||
video/x-flv flv;
|
|
||||||
video/x-m4v m4v;
|
|
||||||
video/x-mng mng;
|
|
||||||
video/x-ms-asf asx asf;
|
|
||||||
video/x-ms-wmv wmv;
|
|
||||||
video/x-msvideo avi;
|
|
||||||
}
|
|
||||||
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
gzip_types text/plain text/xml text/css
|
|
||||||
text/comma-separated-values
|
|
||||||
text/javascript application/x-javascript
|
|
||||||
application/atom+xml;
|
|
||||||
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
|
|
||||||
configFile = pkgs.writeText "nginx.conf" (mkConfig config);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
inherit runtimeDirectory nginxSocket;
|
|
||||||
inherit user group;
|
|
||||||
|
|
||||||
pkg = utils.systemd.mkService rec {
|
|
||||||
name = "nginx-${siteName}";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=Nginx webserver
|
|
||||||
|
|
||||||
After=network.target network-online.target
|
|
||||||
Wants=network-online.target systemd-networkd-wait-online.target
|
|
||||||
${utils.unitDepends "After" dependsOn}
|
|
||||||
${utils.unitDepends "Wants" dependsOn}
|
|
||||||
|
|
||||||
StartLimitInterval=14400
|
|
||||||
StartLimitBurst=10
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=forking
|
|
||||||
User=${user}
|
|
||||||
Group=${group}
|
|
||||||
PIDFile=${pidFile}
|
|
||||||
ExecStart=${pkgs.nginx}/bin/nginx -c ${configFile} -g 'pid ${pidFile};'
|
|
||||||
ExecReload=${pkgs.nginx}/bin/nginx -s reload
|
|
||||||
KillMode=mixed
|
|
||||||
# Nginx verifies it can open a file under here even when configured
|
|
||||||
# to write elsewhere.
|
|
||||||
LogsDirectory=nginx
|
|
||||||
CacheDirectory=nginx
|
|
||||||
RuntimeDirectory=nginx
|
|
||||||
|
|
||||||
# Restart=on-abnormal
|
|
||||||
|
|
||||||
# KillSignal=SIGQUIT
|
|
||||||
TimeoutStopSec=5s
|
|
||||||
|
|
||||||
LimitNOFILE=1048576
|
|
||||||
LimitNPROC=512
|
|
||||||
|
|
||||||
LockPersonality=true
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateDevices=true
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectClock=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectHome=true
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectKernelLogs=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectSystem=full
|
|
||||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
|
|
||||||
RestrictNamespaces=true
|
|
||||||
RestrictRealtime=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
|
|
||||||
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
|
||||||
|
|
||||||
# ProtectSystem=strict
|
|
||||||
# ReadWritePaths=/var/lib/nginx /var/log/nginx
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit dependsOn;
|
|
||||||
type = "systemd-unit";
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# nix-pass.sh
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
f=$(mktemp)
|
|
||||||
trap "rm $f" EXIT
|
|
||||||
pass show "$1" | head -c -1 > $f
|
|
||||||
nix-instantiate --eval -E "builtins.readFile $f"
|
|
|
@ -1,158 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
, serviceName
|
|
||||||
, domain
|
|
||||||
, keycloakSubdomain ? "keycloak"
|
|
||||||
, keycloakDomain ? domain
|
|
||||||
, realm
|
|
||||||
, allowed_roles ? []
|
|
||||||
, skip_auth_routes ? []
|
|
||||||
, api_routes ? []
|
|
||||||
|
|
||||||
, ingress
|
|
||||||
, egress
|
|
||||||
, metricsPort
|
|
||||||
, keys
|
|
||||||
|
|
||||||
, distribution
|
|
||||||
, KeycloakService
|
|
||||||
, KeycloakCliService
|
|
||||||
, HaproxyService
|
|
||||||
|
|
||||||
, debug ? true
|
|
||||||
}:
|
|
||||||
|
|
||||||
with builtins;
|
|
||||||
with pkgs.lib.lists;
|
|
||||||
with pkgs.lib.strings;
|
|
||||||
rec {
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
pkg =
|
|
||||||
{ KeycloakService
|
|
||||||
, KeycloakCliService
|
|
||||||
, HaproxyService
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
config = pkgs.writeText "${serviceName}.cfg" (''
|
|
||||||
provider = "keycloak-oidc"
|
|
||||||
provider_display_name="Keycloak"
|
|
||||||
http_address = "${ingress}"
|
|
||||||
upstreams = [ "${concatStringsSep " " egress}" ]
|
|
||||||
metrics_address = "127.0.0.1:${toString metricsPort}"
|
|
||||||
|
|
||||||
client_id = "${serviceName}"
|
|
||||||
scope="openid"
|
|
||||||
|
|
||||||
redirect_url = "https://${serviceName}.${domain}/oauth2/callback"
|
|
||||||
oidc_issuer_url = "https://${keycloakSubdomain}.${keycloakDomain}/realms/${realm}"
|
|
||||||
|
|
||||||
email_domains = [ "*" ]
|
|
||||||
allowed_roles = ${builtins.toJSON allowed_roles}
|
|
||||||
skip_auth_routes = ${builtins.toJSON skip_auth_routes}
|
|
||||||
api_routes = ${builtins.toJSON api_routes}
|
|
||||||
|
|
||||||
reverse_proxy = "true"
|
|
||||||
# trusted_ips = "@"
|
|
||||||
|
|
||||||
skip_provider_button = "true"
|
|
||||||
|
|
||||||
# pass_authorization_header = true
|
|
||||||
# pass_access_token = true
|
|
||||||
# pass_user_headers = true
|
|
||||||
# set_authorization_header = true
|
|
||||||
# set_xauthrequest = true
|
|
||||||
'' + (if !debug then "" else ''
|
|
||||||
auth_logging = "true"
|
|
||||||
request_logging = "true"
|
|
||||||
''));
|
|
||||||
|
|
||||||
exec = pkgs.writeShellApplication {
|
|
||||||
name = "oauth2proxy-wrapper";
|
|
||||||
runtimeInputs = with pkgs; [curl coreutils];
|
|
||||||
text = ''
|
|
||||||
while ! curl --silent ${KeycloakService.hostname}:${builtins.toString KeycloakService.listenPort} > /dev/null; do
|
|
||||||
echo "Waiting for port ${builtins.toString KeycloakService.listenPort} to open..."
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
sleep 2
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth2-proxy =
|
|
||||||
let
|
|
||||||
version = "f93166229fe9b57f7d54fb0a9c42939f3f30340f";
|
|
||||||
src = pkgs.fetchFromGitHub {
|
|
||||||
owner = "ibizaman";
|
|
||||||
repo = "oauth2-proxy";
|
|
||||||
rev = version;
|
|
||||||
sha256 = "sha256-RI34N+YmUqAanuJOGUA+rUTS1TpUoy8rw6EFGeLh5L0=";
|
|
||||||
# sha256 = pkgs.lib.fakeSha256;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
(pkgs.callPackage "${pkgs.path}/pkgs/tools/backup/kopia" {
|
|
||||||
buildGoModule = args: pkgs.buildGo118Module (args // {
|
|
||||||
vendorSha256 = "sha256-2WUd2RxeOal0lpp/TuGSyfP1ppvG/Vd3bgsSsNO8ejo=";
|
|
||||||
inherit src version;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
oauth2proxyBin = "${oauth2-proxy}/bin/oauth2-proxy";
|
|
||||||
in utils.systemd.mkService rec {
|
|
||||||
name = "oauth2proxy-${serviceName}";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=Oauth2 proxy for ${serviceName}
|
|
||||||
After=${KeycloakService.systemdUnitFile}
|
|
||||||
Wants=${KeycloakService.systemdUnitFile}
|
|
||||||
After=${utils.keyServiceDependencies keys}
|
|
||||||
Wants=${utils.keyServiceDependencies keys}
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStartPre=${exec}/bin/oauth2proxy-wrapper
|
|
||||||
TimeoutStartSec=8m
|
|
||||||
ExecStart=${oauth2proxyBin} --config ${config}
|
|
||||||
DynamicUser=true
|
|
||||||
RuntimeDirectory=oauth2proxy-${serviceName}
|
|
||||||
${utils.keyEnvironmentFiles keys}
|
|
||||||
|
|
||||||
CapabilityBoundingSet=
|
|
||||||
AmbientCapabilities=
|
|
||||||
PrivateUsers=yes
|
|
||||||
NoNewPrivileges=yes
|
|
||||||
ProtectSystem=strict
|
|
||||||
ProtectHome=yes
|
|
||||||
PrivateTmp=yes
|
|
||||||
PrivateDevices=yes
|
|
||||||
ProtectHostname=yes
|
|
||||||
ProtectClock=yes
|
|
||||||
ProtectKernelTunables=yes
|
|
||||||
ProtectKernelModules=yes
|
|
||||||
ProtectKernelLogs=yes
|
|
||||||
ProtectControlGroups=yes
|
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
||||||
RestrictNamespaces=yes
|
|
||||||
LockPersonality=yes
|
|
||||||
MemoryDenyWriteExecute=yes
|
|
||||||
RestrictRealtime=yes
|
|
||||||
RestrictSUIDSGID=yes
|
|
||||||
RemoveIPC=yes
|
|
||||||
|
|
||||||
SystemCallFilter=@system-service
|
|
||||||
SystemCallFilter=~@privileged @resources
|
|
||||||
SystemCallArchitectures=native
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit HaproxyService KeycloakService KeycloakCliService;
|
|
||||||
};
|
|
||||||
type = "systemd-unit";
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
{ pkgs
|
|
||||||
, siteName
|
|
||||||
, logLevel ? "notice"
|
|
||||||
, siteRoot ? "/usr/share/webapps/${siteName}"
|
|
||||||
, user
|
|
||||||
, group
|
|
||||||
, siteSocket
|
|
||||||
, allowedClients ? "127.0.0.1"
|
|
||||||
, socketUser
|
|
||||||
, socketGroup
|
|
||||||
|
|
||||||
, statusPath ? "/status"
|
|
||||||
, maxChildren ? 5
|
|
||||||
, startServers ? 2
|
|
||||||
, minSpareServers ? 1
|
|
||||||
, maxSpareServers ? 3
|
|
||||||
}: pkgs.writeText "php-fpm-${siteName}.conf" ''
|
|
||||||
[global]
|
|
||||||
error_log = syslog
|
|
||||||
syslog.ident = php-fpm
|
|
||||||
log_level = ${logLevel}
|
|
||||||
|
|
||||||
[${siteName}]
|
|
||||||
user = ${user}
|
|
||||||
group = ${group}
|
|
||||||
listen = ${siteSocket}
|
|
||||||
listen.allowed_clients = ${allowedClients}
|
|
||||||
listen.owner = ${socketUser}
|
|
||||||
listen.group = ${socketGroup}
|
|
||||||
|
|
||||||
env[PATH] = /usr/local/bin:/usr/bin:/bin
|
|
||||||
env[TMP] = /tmp
|
|
||||||
|
|
||||||
chdir = ${siteRoot}
|
|
||||||
|
|
||||||
pm = dynamic
|
|
||||||
|
|
||||||
pm.max_children = ${builtins.toString maxChildren}
|
|
||||||
pm.start_servers = ${builtins.toString startServers}
|
|
||||||
pm.min_spare_servers = ${builtins.toString minSpareServers}
|
|
||||||
pm.max_spare_servers = ${builtins.toString maxSpareServers}
|
|
||||||
|
|
||||||
catch_workers_output = yes
|
|
||||||
|
|
||||||
pm.status_path = ${statusPath}
|
|
||||||
''
|
|
|
@ -1,95 +0,0 @@
|
||||||
{ lib
|
|
||||||
, pkgs
|
|
||||||
|
|
||||||
, siteName
|
|
||||||
, prependFile ? null
|
|
||||||
, extensions ? [
|
|
||||||
# "bcmath"
|
|
||||||
# "curl"
|
|
||||||
# "gd"
|
|
||||||
# "gmp"
|
|
||||||
# "iconv"
|
|
||||||
# "imagick"
|
|
||||||
# "intl"
|
|
||||||
# "ldap"
|
|
||||||
# "pdo_pgsql"
|
|
||||||
# "pdo_sqlite"
|
|
||||||
# "pgsql"
|
|
||||||
# "soap"
|
|
||||||
# "sqlite3"
|
|
||||||
# "zip"
|
|
||||||
]
|
|
||||||
, zend_extensions ? [
|
|
||||||
# "opcache"
|
|
||||||
]
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
concatWithPrefix = prefix: content:
|
|
||||||
lib.strings.concatMapStrings
|
|
||||||
(x: prefix + x + "\n")
|
|
||||||
content;
|
|
||||||
in
|
|
||||||
|
|
||||||
pkgs.writeText "php-${siteName}.ini" ''
|
|
||||||
[PHP]
|
|
||||||
engine = On
|
|
||||||
short_open_tag = Off
|
|
||||||
precision = 14
|
|
||||||
output_buffering = 4096
|
|
||||||
zlib.output_compression = Off
|
|
||||||
implicit_flush = Off
|
|
||||||
serialize_precision = -1
|
|
||||||
zend.enable_gc = On
|
|
||||||
zend.exception_ignore_args = On
|
|
||||||
expose_php = Off
|
|
||||||
max_execution_time = 30 ; seconds
|
|
||||||
max_input_time = 60
|
|
||||||
memory_limit = 1024M
|
|
||||||
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
|
||||||
display_errors = Off
|
|
||||||
display_startup_errors = Off
|
|
||||||
log_errors = On
|
|
||||||
log_errors_max_len = 1024
|
|
||||||
ignore_repeated_errors = On
|
|
||||||
ignore_repeated_source = On
|
|
||||||
report_memleaks = On
|
|
||||||
error_log = syslog
|
|
||||||
syslog.ident = php
|
|
||||||
|
|
||||||
cgi.fix_pathinfo=1
|
|
||||||
|
|
||||||
post_max_size = 8M
|
|
||||||
|
|
||||||
auto_prepend_file = "${if prependFile == null then "" else prependFile}"
|
|
||||||
auto_append_file =
|
|
||||||
|
|
||||||
extension_dir = "/usr/lib/php/modules/"
|
|
||||||
|
|
||||||
${concatWithPrefix "extension=" extensions}
|
|
||||||
${concatWithPrefix "zend_extension=" zend_extensions}
|
|
||||||
|
|
||||||
[CLI Server]
|
|
||||||
cli_server.color = On
|
|
||||||
|
|
||||||
; [PostgreSQL]
|
|
||||||
; pgsql.allow_persistent = On
|
|
||||||
; pgsql.auto_reset_persistent = Off
|
|
||||||
; pgsql.max_persistent = -1
|
|
||||||
; pgsql.max_links = -1
|
|
||||||
; pgsql.ignore_notice = 0
|
|
||||||
; pgsql.log_notice = 0
|
|
||||||
|
|
||||||
; [Session]
|
|
||||||
; session.save_handler = redis
|
|
||||||
; session.save_path = "unix:///run/redis/redis.sock?database=1"
|
|
||||||
; session.use_strict_mode = 1
|
|
||||||
; session.use_cookies = 1
|
|
||||||
; session.use_only_cookies = 1
|
|
||||||
|
|
||||||
; [opcache]
|
|
||||||
; opcache.enable=1
|
|
||||||
; opcache.memory_consumption=128
|
|
||||||
; opcache.interned_strings_buffer=16
|
|
||||||
; opcache.max_accelerated_files=20000
|
|
||||||
''
|
|
|
@ -1,86 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
, siteName
|
|
||||||
, user
|
|
||||||
, group
|
|
||||||
, socketUser
|
|
||||||
, socketGroup
|
|
||||||
, runtimeDirectory ? "/run/${siteName}"
|
|
||||||
, phpIniConfig ? {}
|
|
||||||
, siteConfig ? {}
|
|
||||||
, extensions ? []
|
|
||||||
, zend_extensions ? []
|
|
||||||
|
|
||||||
, dependsOn ? {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
phpIniFile = pkgs.callPackage (import ./php-ini.nix) {
|
|
||||||
inherit siteName;
|
|
||||||
inherit extensions zend_extensions;
|
|
||||||
} // phpIniConfig;
|
|
||||||
|
|
||||||
siteSocket = "${runtimeDirectory}/${siteName}.sock";
|
|
||||||
|
|
||||||
siteConfigFile = pkgs.callPackage (import ./php-fpm.nix) {
|
|
||||||
inherit siteName;
|
|
||||||
inherit user group;
|
|
||||||
inherit siteSocket socketUser socketGroup;
|
|
||||||
} // siteConfig;
|
|
||||||
in
|
|
||||||
# This service runs as root, each pool runs as a user.
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
inherit user group;
|
|
||||||
inherit socketUser socketGroup;
|
|
||||||
|
|
||||||
inherit siteSocket;
|
|
||||||
|
|
||||||
pkg = utils.systemd.mkService rec {
|
|
||||||
name = "php-fpm-${siteName}";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=The PHP FastCGI Process Manager
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=notify
|
|
||||||
PIDFile=/run/${siteName}/php-fpm.pid
|
|
||||||
ExecStart=${pkgs.php}/bin/php-fpm --nodaemonize --fpm-config ${siteConfigFile} --php-ini ${phpIniFile}
|
|
||||||
ExecReload=/bin/kill -USR2 $MAINPID
|
|
||||||
|
|
||||||
# Keeping this around to avoid uncommenting them. These directories
|
|
||||||
# are handled through tmpfiles.d.
|
|
||||||
#
|
|
||||||
# RuntimeDirectory=${siteName}
|
|
||||||
# StateDirectory=${siteName}
|
|
||||||
|
|
||||||
LockPersonality=true
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateDevices=true
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectClock=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectHome=true
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectKernelLogs=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectSystem=full
|
|
||||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX
|
|
||||||
RestrictNamespaces=true
|
|
||||||
RestrictRealtime=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit dependsOn;
|
|
||||||
type = "systemd-unit";
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
|
|
||||||
, database
|
|
||||||
, username
|
|
||||||
, password ? null
|
|
||||||
, passwordFile ? null
|
|
||||||
|
|
||||||
, dependsOn ? {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
assert lib.assertMsg (
|
|
||||||
(password == null && passwordFile != null)
|
|
||||||
|| (password != null && passwordFile == null)
|
|
||||||
) "set either postgresPassword or postgresPasswordFile";
|
|
||||||
|
|
||||||
# From https://github.com/svanderburg/dysnomia/blob/master/dysnomia-modules/postgresql-database.in
|
|
||||||
# and https://github.com/svanderburg/dysnomia/blob/master/tests/deployment/postgresql-database.nix
|
|
||||||
#
|
|
||||||
# On activation, an initial dump can be restored. If the mutable component
|
|
||||||
# contains a sub folder named postgresql-databases/, then the dump files stored
|
|
||||||
# inside get imported.
|
|
||||||
|
|
||||||
# TODO: https://stackoverflow.com/a/69480184/1013628
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
inherit database username password passwordFile;
|
|
||||||
|
|
||||||
pkg = stdenv.mkDerivation {
|
|
||||||
name = database;
|
|
||||||
|
|
||||||
src = pkgs.writeTextDir "${database}.sql" ''
|
|
||||||
CREATE USER "${username}" WITH PASSWORD '${password}';
|
|
||||||
GRANT ALL PRIVILEGES ON DATABASE "${database}" TO "${username}";
|
|
||||||
'';
|
|
||||||
|
|
||||||
buildCommand = ''
|
|
||||||
mkdir -p $out/postgresql-databases
|
|
||||||
cp $src/*.sql $out/postgresql-databases
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit dependsOn;
|
|
||||||
type = "postgresql-database";
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{ pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
unit = pkgs.callPackage ./unit { inherit utils; };
|
|
||||||
integration = pkgs.callPackage ./integration { inherit utils; };
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
{ nixpkgs, pkgs }:
|
|
||||||
let
|
|
||||||
generateManifestSrc =
|
|
||||||
{name, tarball}:
|
|
||||||
|
|
||||||
pkgs.stdenv.mkDerivation {
|
|
||||||
name = "${name}-manifest-src";
|
|
||||||
buildCommand =
|
|
||||||
''
|
|
||||||
mkdir -p $out
|
|
||||||
cd $out
|
|
||||||
tar xfvj ${tarball}/tarballs/*.tar.bz2 --strip-components=1
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
disnixos = import "${pkgs.disnixos}/share/disnixos/testing.nix" {
|
|
||||||
inherit nixpkgs;
|
|
||||||
};
|
|
||||||
|
|
||||||
# We need this function because, for a reason that eludes me, the
|
|
||||||
# one defined in disnixos fails the name attribute not correctly set
|
|
||||||
# in the call to simpleTest. The only difference between this
|
|
||||||
# function and the one in disnixos is the additional `inherit name`
|
|
||||||
# line.
|
|
||||||
customDisnixTest = system:
|
|
||||||
{name, manifest, tarball, networkFile, externalNetworkFile ? false, testScript, dysnomiaStateDir ? "", postActivateTimeout ? 1}:
|
|
||||||
|
|
||||||
let
|
|
||||||
manifestSrc = generateManifestSrc {
|
|
||||||
inherit name tarball;
|
|
||||||
};
|
|
||||||
|
|
||||||
network = if externalNetworkFile then import networkFile else import "${manifestSrc}/${networkFile}";
|
|
||||||
in
|
|
||||||
with import "${nixpkgs}/nixos/lib/testing-python.nix" { inherit system; };
|
|
||||||
|
|
||||||
simpleTest {
|
|
||||||
nodes = network;
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
testScript = import "${pkgs.disnixos}/share/disnixos/generate-testscript.nix" {
|
|
||||||
inherit network testScript dysnomiaStateDir postActivateTimeout;
|
|
||||||
inherit (pkgs) disnix daemon socat libxml2;
|
|
||||||
inherit (pkgs.lib) concatMapStrings;
|
|
||||||
manifestFile = "${manifest}/manifest.xml";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit (disnixos) sourceTarball;
|
|
||||||
|
|
||||||
genBuilds = systems: config:
|
|
||||||
pkgs.lib.genAttrs systems (system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
|
|
||||||
disnixos = import "${pkgs.disnixos}/share/disnixos/testing.nix" {
|
|
||||||
inherit nixpkgs system;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
disnixos.buildManifest config
|
|
||||||
);
|
|
||||||
|
|
||||||
disnixTest = currentSystem: manifest: config:
|
|
||||||
customDisnixTest currentSystem (config // {
|
|
||||||
manifest = builtins.getAttr currentSystem manifest;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{ pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
rec {
|
|
||||||
all = [keycloak];
|
|
||||||
|
|
||||||
keycloak = pkgs.callPackage ./keycloak.nix {};
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
# Run tests with nix-build -A tests.integration.keycloak
|
|
||||||
{ nixpkgs ? <nixpkgs>
|
|
||||||
, systems ? [ "i686-linux" "x86_64-linux" ]
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs {};
|
|
||||||
|
|
||||||
version = "1.0";
|
|
||||||
|
|
||||||
disnixos = pkgs.callPackage ./common.nix { inherit nixpkgs; };
|
|
||||||
in
|
|
||||||
|
|
||||||
rec {
|
|
||||||
tarball = disnixos.sourceTarball {
|
|
||||||
name = "testproject-zip";
|
|
||||||
inherit version;
|
|
||||||
src = ../../.;
|
|
||||||
officialRelease = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
builds = {
|
|
||||||
simple = disnixos.genBuilds systems {
|
|
||||||
name = "test-project-manifest";
|
|
||||||
inherit version;
|
|
||||||
inherit tarball;
|
|
||||||
servicesFile = "tests/integration/keycloak/services.nix";
|
|
||||||
networkFile = "tests/integration/keycloak/network.nix";
|
|
||||||
distributionFile = "tests/integration/keycloak/distribution.nix";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
tests = {
|
|
||||||
simple = disnixos.disnixTest builtins.currentSystem builds.simple {
|
|
||||||
name = "test-project-test";
|
|
||||||
inherit tarball;
|
|
||||||
networkFile = "tests/integration/keycloak/network.nix";
|
|
||||||
# dysnomiaStateDir = /var/state/dysnomia;
|
|
||||||
testScript =
|
|
||||||
''
|
|
||||||
def assert_service_started(machine, name):
|
|
||||||
code, log = machine.systemctl("status " + name)
|
|
||||||
if code != 0:
|
|
||||||
raise Exception(name + " could not be started:\n---\n" + log + "---\n")
|
|
||||||
|
|
||||||
def assert_database_exists(machine, name):
|
|
||||||
if machine.succeed("""psql -XtA -U postgres -h localhost -c "SELECT 1 FROM pg_database WHERE datname='{}'" """.format(name)) != '1\n':
|
|
||||||
raise Exception("could not find database '{}' in postgresql".format(name))
|
|
||||||
|
|
||||||
with subtest("check postgres service started"):
|
|
||||||
assert_service_started(test1, "postgresql.service")
|
|
||||||
|
|
||||||
with subtest("check db is created"):
|
|
||||||
assert_database_exists(test1, "keycloak")
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}.tests
|
|
|
@ -1,7 +0,0 @@
|
||||||
{ infrastructure }:
|
|
||||||
|
|
||||||
with infrastructure;
|
|
||||||
{
|
|
||||||
KeycloakPostgresDB = [ test1 ];
|
|
||||||
KeycloakService = [ test1 ];
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
rec {
|
|
||||||
test1 = { system
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
, ... }:
|
|
||||||
let
|
|
||||||
domain = "local";
|
|
||||||
|
|
||||||
utils = pkgs.lib.callPackageWith pkgs ../../../utils.nix { };
|
|
||||||
|
|
||||||
customPkgs = import ../../../all-packages.nix {
|
|
||||||
inherit system pkgs utils;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
rec {
|
|
||||||
users.groups = {
|
|
||||||
keycloak = {
|
|
||||||
name = "keycloak";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
users.users = {
|
|
||||||
keycloak = {
|
|
||||||
name = "keycloak";
|
|
||||||
group = "keycloak";
|
|
||||||
isSystemUser = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Normally, you'd provision the deploy target with secrets.
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
# Type Path Mode User Group Age Argument...
|
|
||||||
''d /run/keys 0755 root root - -''
|
|
||||||
''f+ /run/keys/keycloackinitialadmin 0755 root root - KEYCLOAK_ADMIN_PASSWORD="KEYCLOAK_ADMIN_PASSWORD"''
|
|
||||||
];
|
|
||||||
|
|
||||||
services = {
|
|
||||||
openssh = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
disnix = {
|
|
||||||
enable = true;
|
|
||||||
# useWebServiceInterface = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
postgresql = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.postgresql_14;
|
|
||||||
|
|
||||||
port = 5432;
|
|
||||||
enableTCPIP = true;
|
|
||||||
authentication = pkgs.lib.mkOverride 10 ''
|
|
||||||
local all all trust
|
|
||||||
host all all 127.0.0.1/32 trust
|
|
||||||
host all all ::1/128 trust
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
dysnomia = {
|
|
||||||
enable = true;
|
|
||||||
enableLegacyModules = false;
|
|
||||||
extraContainerProperties = {
|
|
||||||
system = {
|
|
||||||
inherit domain;
|
|
||||||
};
|
|
||||||
postgresql-database = {
|
|
||||||
service_name = "postgresql.service";
|
|
||||||
port = builtins.toString services.postgresql.port;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ services.postgresql.port ];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
{ system, pkgs, distribution, invDistribution }:
|
|
||||||
|
|
||||||
let
|
|
||||||
utils = pkgs.lib.callPackageWith pkgs ../../../utils.nix { };
|
|
||||||
|
|
||||||
customPkgs = import ../../../all-packages.nix {
|
|
||||||
inherit system pkgs utils;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
with utils;
|
|
||||||
rec {
|
|
||||||
KeycloakPostgresDB = customPkgs.mkPostgresDB {
|
|
||||||
name = "KeycloakPostgresDB";
|
|
||||||
database = "keycloak";
|
|
||||||
username = "keycloak";
|
|
||||||
# TODO: use passwordFile
|
|
||||||
password = "keycloak";
|
|
||||||
};
|
|
||||||
|
|
||||||
KeycloakService = customPkgs.mkKeycloakService {
|
|
||||||
name = "KeycloakService";
|
|
||||||
subdomain = "keycloak";
|
|
||||||
|
|
||||||
# Get these from infrastructure.nix
|
|
||||||
user = "keycloak";
|
|
||||||
group = "keycloak";
|
|
||||||
|
|
||||||
postgresServiceName = (getTarget distribution "KeycloakPostgresDB").containers.postgresql-database.service_name;
|
|
||||||
initialAdminUsername = "admin";
|
|
||||||
|
|
||||||
keys = {
|
|
||||||
dbPassword = "keycloakdbpassword";
|
|
||||||
initialAdminPassword = "keycloakinitialadmin";
|
|
||||||
};
|
|
||||||
|
|
||||||
logLevel = "INFO";
|
|
||||||
hostname = "keycloak.${getDomain distribution "KeycloakService"}";
|
|
||||||
|
|
||||||
dbType = "postgres";
|
|
||||||
dbDatabase = KeycloakPostgresDB.database;
|
|
||||||
dbUsername = KeycloakPostgresDB.username;
|
|
||||||
dbHost = {KeycloakPostgresDB}: KeycloakPostgresDB.target.properties.hostname;
|
|
||||||
dbPort = (getTarget distribution "KeycloakPostgresDB").containers.postgresql-database.port;
|
|
||||||
|
|
||||||
inherit KeycloakPostgresDB;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{ pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
haproxy = pkgs.callPackage ./haproxy.nix { inherit utils; };
|
|
||||||
keycloak = pkgs.callPackage ./keycloak.nix {};
|
|
||||||
keycloak-cli-config = pkgs.callPackage ./keycloak-cli-config.nix {};
|
|
||||||
}
|
|
|
@ -1,562 +0,0 @@
|
||||||
# to run these tests:
|
|
||||||
# nix-instantiate --eval --strict . -A tests.haproxy
|
|
||||||
|
|
||||||
{ lib
|
|
||||||
, stdenv
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
configcreator = pkgs.callPackage ./../../haproxy/configcreator.nix { inherit utils; };
|
|
||||||
mksiteconfig = pkgs.callPackage ./../../haproxy/siteconfig.nix {};
|
|
||||||
|
|
||||||
diff = testResult:
|
|
||||||
with builtins;
|
|
||||||
with lib.strings;
|
|
||||||
if isString testResult.expected && isString testResult.result then
|
|
||||||
let
|
|
||||||
# Taken from nixpkgs master
|
|
||||||
commonPrefixLength = a: b:
|
|
||||||
let
|
|
||||||
m = lib.min (stringLength a) (stringLength b);
|
|
||||||
go = i: if i >= m then m else if substring i 1 a == substring i 1 b then go (i + 1) else i;
|
|
||||||
in go 0;
|
|
||||||
# Taken from nixpkgs master
|
|
||||||
commonSuffixLength = a: b:
|
|
||||||
let
|
|
||||||
m = lib.min (stringLength a) (stringLength b);
|
|
||||||
go = i: if i >= m then m else if substring (stringLength a - i - 1) 1 a == substring (stringLength b - i - 1) 1 b then go (i + 1) else i;
|
|
||||||
in go 0;
|
|
||||||
|
|
||||||
p = commonPrefixLength testResult.expected testResult.result;
|
|
||||||
s = commonSuffixLength testResult.expected testResult.result;
|
|
||||||
expectedSuffixLen = stringLength testResult.expected - s - p;
|
|
||||||
resultSuffixLen = stringLength testResult.result - s - p;
|
|
||||||
expectedDiff = substring p expectedSuffixLen testResult.expected;
|
|
||||||
resultDiff = substring p resultSuffixLen testResult.result;
|
|
||||||
omitted = len: if len == 0 then "" else "[... ${toString len} omitted]";
|
|
||||||
in
|
|
||||||
{inherit (testResult) name;
|
|
||||||
commonPrefix = substring 0 p testResult.expected;
|
|
||||||
commonSuffix = substring (stringLength testResult.expected - s) s testResult.expected;
|
|
||||||
expected = "${omitted p}${expectedDiff}${omitted s}";
|
|
||||||
result = "${omitted p}${resultDiff}${omitted s}";
|
|
||||||
allExpected = testResult.expected;
|
|
||||||
allResult = testResult.result;
|
|
||||||
}
|
|
||||||
else testResult;
|
|
||||||
|
|
||||||
runTests = x: map diff (lib.runTests x);
|
|
||||||
in
|
|
||||||
|
|
||||||
with lib.attrsets;
|
|
||||||
runTests {
|
|
||||||
testDiffSame = {
|
|
||||||
expr = "abdef";
|
|
||||||
expected = "abdef";
|
|
||||||
};
|
|
||||||
testUpdateByPath1 = {
|
|
||||||
expr = configcreator.updateByPath ["a"] (x: x+1) {
|
|
||||||
a = 1;
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
a = 2;
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
testUpdateByPath2 = {
|
|
||||||
expr = configcreator.updateByPath ["a" "a"] (x: x+1) {
|
|
||||||
a = {
|
|
||||||
a = 1;
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
a = {
|
|
||||||
a = 2;
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
testUpdateByPath3 = {
|
|
||||||
expr = configcreator.updateByPath ["a" "a" "a"] (x: x+1) {
|
|
||||||
a = {
|
|
||||||
a = {
|
|
||||||
a = 1;
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
a = {
|
|
||||||
a = {
|
|
||||||
a = 2;
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
b = 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testRecursiveMerge1 = {
|
|
||||||
expr = configcreator.recursiveMerge [
|
|
||||||
{a = 1;}
|
|
||||||
{b = 2;}
|
|
||||||
];
|
|
||||||
expected = {
|
|
||||||
a = 1;
|
|
||||||
b = 2;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testRecursiveMerge2 = {
|
|
||||||
expr = configcreator.recursiveMerge [
|
|
||||||
{a = {a = 1; b = 2;};}
|
|
||||||
{a = {a = 2;};}
|
|
||||||
];
|
|
||||||
expected = {
|
|
||||||
a = {a = 2; b = 2;};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
tesFlattenArgs1 = {
|
|
||||||
expr = configcreator.flattenAttrs {
|
|
||||||
a = 1;
|
|
||||||
b = 2;
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
a = 1;
|
|
||||||
b = 2;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
tesFlattenArgs2 = {
|
|
||||||
expr = configcreator.flattenAttrs {
|
|
||||||
a = {
|
|
||||||
a = 1;
|
|
||||||
b = {
|
|
||||||
c = 3;
|
|
||||||
d = 4;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
b = 2;
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
"a.a" = 1;
|
|
||||||
"a.b.c" = 3;
|
|
||||||
"a.b.d" = 4;
|
|
||||||
b = 2;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testHaproxyConfigDefaultRender = {
|
|
||||||
expr = configcreator.render (configcreator.default {
|
|
||||||
user = "me";
|
|
||||||
group = "mygroup";
|
|
||||||
certPath = "/cert/path";
|
|
||||||
plugins = {
|
|
||||||
zone = {
|
|
||||||
luapaths = "lib";
|
|
||||||
source = pkgs.writeText "one.lua" "a binary";
|
|
||||||
};
|
|
||||||
two = {
|
|
||||||
load = "right/two.lua";
|
|
||||||
luapaths = ".";
|
|
||||||
cpaths = "right";
|
|
||||||
source = pkgs.writeText "two.lua" "a binary";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
globalEnvs = {
|
|
||||||
ABC = "hello";
|
|
||||||
};
|
|
||||||
stats = null;
|
|
||||||
debug = false;
|
|
||||||
});
|
|
||||||
expected = ''
|
|
||||||
global
|
|
||||||
group mygroup
|
|
||||||
log /dev/log local0 info
|
|
||||||
maxconn 20000
|
|
||||||
lua-prepend-path /nix/store/ybcka9g095hp8s1hnm2ncfh1hp56v9yq-haproxyplugins/two/?.lua path
|
|
||||||
lua-prepend-path /nix/store/ybcka9g095hp8s1hnm2ncfh1hp56v9yq-haproxyplugins/two/right/?.so cpath
|
|
||||||
lua-prepend-path /nix/store/ybcka9g095hp8s1hnm2ncfh1hp56v9yq-haproxyplugins/zone/lib/?.lua path
|
|
||||||
lua-load /nix/store/ybcka9g095hp8s1hnm2ncfh1hp56v9yq-haproxyplugins/two/right/two.lua
|
|
||||||
setenv ABC hello
|
|
||||||
tune.ssl.default-dh-param 2048
|
|
||||||
user me
|
|
||||||
|
|
||||||
defaults
|
|
||||||
log global
|
|
||||||
option httplog
|
|
||||||
timeout client 15s
|
|
||||||
timeout connect 10s
|
|
||||||
timeout queue 100s
|
|
||||||
timeout server 30s
|
|
||||||
|
|
||||||
frontend http-to-https
|
|
||||||
bind *:80
|
|
||||||
mode http
|
|
||||||
redirect scheme https code 301 if !{ ssl_fc }
|
|
||||||
|
|
||||||
frontend https
|
|
||||||
bind *:443 ssl crt /cert/path
|
|
||||||
http-request add-header X-Forwarded-Proto https
|
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]
|
|
||||||
http-request set-header X-Forwarded-For %[src]
|
|
||||||
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
|
|
||||||
mode http
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
testHaproxyConfigDefaultRenderWithStatsAndDebug = {
|
|
||||||
expr = configcreator.render (configcreator.default {
|
|
||||||
user = "me";
|
|
||||||
group = "mygroup";
|
|
||||||
certPath = "/cert/path";
|
|
||||||
stats = {
|
|
||||||
port = 8405;
|
|
||||||
uri = "/stats";
|
|
||||||
refresh = "10s";
|
|
||||||
prometheusUri = "/prom/etheus";
|
|
||||||
hide-version = true;
|
|
||||||
};
|
|
||||||
debug = true;
|
|
||||||
});
|
|
||||||
expected = ''
|
|
||||||
global
|
|
||||||
group mygroup
|
|
||||||
log /dev/log local0 info
|
|
||||||
maxconn 20000
|
|
||||||
tune.ssl.default-dh-param 2048
|
|
||||||
user me
|
|
||||||
|
|
||||||
defaults
|
|
||||||
log global
|
|
||||||
option httplog
|
|
||||||
timeout client 15s
|
|
||||||
timeout connect 10s
|
|
||||||
timeout queue 100s
|
|
||||||
timeout server 30s
|
|
||||||
|
|
||||||
frontend http-to-https
|
|
||||||
bind *:80
|
|
||||||
mode http
|
|
||||||
redirect scheme https code 301 if !{ ssl_fc }
|
|
||||||
|
|
||||||
frontend https
|
|
||||||
bind *:443 ssl crt /cert/path
|
|
||||||
http-request add-header X-Forwarded-Proto https
|
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]
|
|
||||||
http-request set-header X-Forwarded-For %[src]
|
|
||||||
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
|
|
||||||
log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %sslv %sslc %[ssl_fc_cipherlist_str]"
|
|
||||||
mode http
|
|
||||||
|
|
||||||
frontend stats
|
|
||||||
bind localhost:8405
|
|
||||||
http-request use-service prometheus-exporter if { path /prom/etheus }
|
|
||||||
mode http
|
|
||||||
stats enable
|
|
||||||
stats hide-version
|
|
||||||
stats refresh 10s
|
|
||||||
stats uri /stats
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
testRenderHaproxyConfigWithSite = {
|
|
||||||
expr = configcreator.render (configcreator.default {
|
|
||||||
user = "me";
|
|
||||||
group = "mygroup";
|
|
||||||
certPath = "/cert/path";
|
|
||||||
stats = null;
|
|
||||||
debug = false;
|
|
||||||
sites = {
|
|
||||||
siteName = {
|
|
||||||
frontend = {
|
|
||||||
capture = [
|
|
||||||
"request header origin len 128"
|
|
||||||
];
|
|
||||||
acl = {
|
|
||||||
acl_siteName = "hdr_beg(host) siteName.";
|
|
||||||
acl_siteName_path = "path_beg /siteName";
|
|
||||||
};
|
|
||||||
http-response = {
|
|
||||||
add-header = [
|
|
||||||
"Access-Control-Allow-Origin1 $[capture]"
|
|
||||||
"Access-Control-Allow-Origin2 $[capture]"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
use_backend = "if acl_siteName OR acl_siteName_path";
|
|
||||||
};
|
|
||||||
backend = {
|
|
||||||
servers = [
|
|
||||||
{
|
|
||||||
name = "serviceName1";
|
|
||||||
address = "serviceSocket";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
options = [
|
|
||||||
"cookie JSESSIONID prefix"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
expected = ''
|
|
||||||
global
|
|
||||||
group mygroup
|
|
||||||
log /dev/log local0 info
|
|
||||||
maxconn 20000
|
|
||||||
tune.ssl.default-dh-param 2048
|
|
||||||
user me
|
|
||||||
|
|
||||||
defaults
|
|
||||||
log global
|
|
||||||
option httplog
|
|
||||||
timeout client 15s
|
|
||||||
timeout connect 10s
|
|
||||||
timeout queue 100s
|
|
||||||
timeout server 30s
|
|
||||||
|
|
||||||
frontend http-to-https
|
|
||||||
bind *:80
|
|
||||||
mode http
|
|
||||||
redirect scheme https code 301 if !{ ssl_fc }
|
|
||||||
|
|
||||||
frontend https
|
|
||||||
acl acl_siteName hdr_beg(host) siteName.
|
|
||||||
acl acl_siteName_path path_beg /siteName
|
|
||||||
bind *:443 ssl crt /cert/path
|
|
||||||
capture request header origin len 128
|
|
||||||
http-request add-header X-Forwarded-Proto https
|
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]
|
|
||||||
http-request set-header X-Forwarded-For %[src]
|
|
||||||
http-response add-header Access-Control-Allow-Origin1 $[capture]
|
|
||||||
http-response add-header Access-Control-Allow-Origin2 $[capture]
|
|
||||||
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
|
|
||||||
mode http
|
|
||||||
use_backend siteName if acl_siteName OR acl_siteName_path
|
|
||||||
|
|
||||||
backend siteName
|
|
||||||
cookie JSESSIONID prefix
|
|
||||||
mode http
|
|
||||||
option forwardfor
|
|
||||||
server serviceName1 serviceSocket
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
testRenderHaproxyConfigWith2Sites = {
|
|
||||||
expr = configcreator.render (configcreator.default {
|
|
||||||
user = "me";
|
|
||||||
group = "mygroup";
|
|
||||||
certPath = "/cert/path";
|
|
||||||
stats = null;
|
|
||||||
debug = false;
|
|
||||||
sites = {
|
|
||||||
siteName = {
|
|
||||||
frontend = {
|
|
||||||
capture = [
|
|
||||||
"request header origin len 128"
|
|
||||||
];
|
|
||||||
acl = {
|
|
||||||
acl_siteName = "hdr_beg(host) siteName.";
|
|
||||||
acl_siteName_path = "path_beg /siteName";
|
|
||||||
};
|
|
||||||
http-response = {
|
|
||||||
add-header = [
|
|
||||||
"Access-Control-Allow-Origin1 $[capture]"
|
|
||||||
"Access-Control-Allow-Origin2 $[capture]"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
use_backend = "if acl_siteName OR acl_siteName_path";
|
|
||||||
};
|
|
||||||
backend = {
|
|
||||||
servers = [
|
|
||||||
{
|
|
||||||
name = "serviceName1";
|
|
||||||
address = "serviceSocket";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
options = [
|
|
||||||
"cookie JSESSIONID prefix"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
siteName2 = {
|
|
||||||
frontend = {
|
|
||||||
capture = [
|
|
||||||
"request header origin len 128"
|
|
||||||
];
|
|
||||||
acl = {
|
|
||||||
acl_siteName2 = "hdr_beg(host) siteName2.";
|
|
||||||
acl_siteName2_path = "path_beg /siteName2";
|
|
||||||
};
|
|
||||||
http-response = {
|
|
||||||
add-header = [
|
|
||||||
"Access-Control-Allow-Origin3 $[capture]"
|
|
||||||
"Access-Control-Allow-Origin4 $[capture]"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
use_backend = "if acl_siteName2 OR acl_siteName2_path";
|
|
||||||
};
|
|
||||||
backend = {
|
|
||||||
servers = [
|
|
||||||
{
|
|
||||||
name = "serviceName2";
|
|
||||||
address = "serviceSocket";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
options = [
|
|
||||||
"cookie JSESSIONID prefix"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
expected = ''
|
|
||||||
global
|
|
||||||
group mygroup
|
|
||||||
log /dev/log local0 info
|
|
||||||
maxconn 20000
|
|
||||||
tune.ssl.default-dh-param 2048
|
|
||||||
user me
|
|
||||||
|
|
||||||
defaults
|
|
||||||
log global
|
|
||||||
option httplog
|
|
||||||
timeout client 15s
|
|
||||||
timeout connect 10s
|
|
||||||
timeout queue 100s
|
|
||||||
timeout server 30s
|
|
||||||
|
|
||||||
frontend http-to-https
|
|
||||||
bind *:80
|
|
||||||
mode http
|
|
||||||
redirect scheme https code 301 if !{ ssl_fc }
|
|
||||||
|
|
||||||
frontend https
|
|
||||||
acl acl_siteName hdr_beg(host) siteName.
|
|
||||||
acl acl_siteName2 hdr_beg(host) siteName2.
|
|
||||||
acl acl_siteName2_path path_beg /siteName2
|
|
||||||
acl acl_siteName_path path_beg /siteName
|
|
||||||
bind *:443 ssl crt /cert/path
|
|
||||||
capture request header origin len 128
|
|
||||||
capture request header origin len 128
|
|
||||||
http-request add-header X-Forwarded-Proto https
|
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]
|
|
||||||
http-request set-header X-Forwarded-For %[src]
|
|
||||||
http-response add-header Access-Control-Allow-Origin1 $[capture]
|
|
||||||
http-response add-header Access-Control-Allow-Origin2 $[capture]
|
|
||||||
http-response add-header Access-Control-Allow-Origin3 $[capture]
|
|
||||||
http-response add-header Access-Control-Allow-Origin4 $[capture]
|
|
||||||
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
|
|
||||||
mode http
|
|
||||||
use_backend siteName if acl_siteName OR acl_siteName_path
|
|
||||||
use_backend siteName2 if acl_siteName2 OR acl_siteName2_path
|
|
||||||
|
|
||||||
backend siteName
|
|
||||||
cookie JSESSIONID prefix
|
|
||||||
mode http
|
|
||||||
option forwardfor
|
|
||||||
server serviceName1 serviceSocket
|
|
||||||
|
|
||||||
backend siteName2
|
|
||||||
cookie JSESSIONID prefix
|
|
||||||
mode http
|
|
||||||
option forwardfor
|
|
||||||
server serviceName2 serviceSocket
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
testRenderHaproxyConfigWithSiteDebugHeaders = {
|
|
||||||
expr = configcreator.render (configcreator.default {
|
|
||||||
user = "me";
|
|
||||||
group = "mygroup";
|
|
||||||
certPath = "/cert/path";
|
|
||||||
stats = null;
|
|
||||||
debug = false;
|
|
||||||
sites = {
|
|
||||||
siteName = {
|
|
||||||
frontend = {
|
|
||||||
capture = [
|
|
||||||
"request header origin len 128"
|
|
||||||
];
|
|
||||||
acl = {
|
|
||||||
acl_siteName = "hdr_beg(host) siteName.";
|
|
||||||
acl_siteName_path = "path_beg /siteName";
|
|
||||||
};
|
|
||||||
http-response = {
|
|
||||||
add-header = [
|
|
||||||
"Access-Control-Allow-Origin1 $[capture]"
|
|
||||||
"Access-Control-Allow-Origin2 $[capture]"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
use_backend = "if acl_siteName OR acl_siteName_path";
|
|
||||||
};
|
|
||||||
backend = {
|
|
||||||
servers = [
|
|
||||||
{
|
|
||||||
name = "serviceName1";
|
|
||||||
address = "serviceSocket";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
options = [
|
|
||||||
"cookie JSESSIONID prefix"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
debugHeaders = "acl_siteName";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
expected = ''
|
|
||||||
global
|
|
||||||
group mygroup
|
|
||||||
log /dev/log local0 info
|
|
||||||
maxconn 20000
|
|
||||||
tune.ssl.default-dh-param 2048
|
|
||||||
user me
|
|
||||||
|
|
||||||
defaults
|
|
||||||
log global
|
|
||||||
option httplog
|
|
||||||
timeout client 15s
|
|
||||||
timeout connect 10s
|
|
||||||
timeout queue 100s
|
|
||||||
timeout server 30s
|
|
||||||
|
|
||||||
frontend http-to-https
|
|
||||||
bind *:80
|
|
||||||
mode http
|
|
||||||
redirect scheme https code 301 if !{ ssl_fc }
|
|
||||||
|
|
||||||
frontend https
|
|
||||||
acl acl_siteName hdr_beg(host) siteName.
|
|
||||||
acl acl_siteName_path path_beg /siteName
|
|
||||||
bind *:443 ssl crt /cert/path
|
|
||||||
capture request header origin len 128
|
|
||||||
http-request add-header X-Forwarded-Proto https
|
|
||||||
http-request capture req.hdrs len 512 if acl_siteName
|
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]
|
|
||||||
http-request set-header X-Forwarded-For %[src]
|
|
||||||
http-response add-header Access-Control-Allow-Origin1 $[capture]
|
|
||||||
http-response add-header Access-Control-Allow-Origin2 $[capture]
|
|
||||||
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
|
|
||||||
log-format "%ci:%cp [%tr] %ft [[%hr]] %hs %{+Q}r"
|
|
||||||
mode http
|
|
||||||
option httplog
|
|
||||||
use_backend siteName if acl_siteName OR acl_siteName_path
|
|
||||||
|
|
||||||
backend siteName
|
|
||||||
cookie JSESSIONID prefix
|
|
||||||
mode http
|
|
||||||
option forwardfor
|
|
||||||
server serviceName1 serviceSocket
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,343 +0,0 @@
|
||||||
# to run these tests:
|
|
||||||
# nix-instantiate --eval --strict . -A tests.keycloak-cli-config
|
|
||||||
|
|
||||||
{ lib
|
|
||||||
, stdenv
|
|
||||||
, pkgs
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
configcreator = pkgs.callPackage ./../../keycloak-cli-config/configcreator.nix {};
|
|
||||||
|
|
||||||
default_config = {
|
|
||||||
realm = "myrealm";
|
|
||||||
domain = "mydomain.com";
|
|
||||||
};
|
|
||||||
|
|
||||||
keep_fields = fields:
|
|
||||||
lib.filterAttrs (n: v: lib.any (n_: n_ == n) fields);
|
|
||||||
in
|
|
||||||
|
|
||||||
lib.runTests {
|
|
||||||
testDefault = {
|
|
||||||
expr = configcreator default_config;
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
id = "myrealm";
|
|
||||||
realm = "myrealm";
|
|
||||||
enabled = true;
|
|
||||||
clients = [];
|
|
||||||
roles = {
|
|
||||||
realm = [];
|
|
||||||
client = {};
|
|
||||||
};
|
|
||||||
groups = [];
|
|
||||||
users = [];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testUsers = {
|
|
||||||
expr = (configcreator (default_config // {
|
|
||||||
users = {
|
|
||||||
me = {
|
|
||||||
email = "me@mydomain.com";
|
|
||||||
firstName = "me";
|
|
||||||
lastName = "stillme";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})).users;
|
|
||||||
|
|
||||||
expected = [
|
|
||||||
{
|
|
||||||
username = "me";
|
|
||||||
enabled = true;
|
|
||||||
email = "me@mydomain.com";
|
|
||||||
emailVerified = true;
|
|
||||||
firstName = "me";
|
|
||||||
lastName = "stillme";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testUsersWithGroups = {
|
|
||||||
expr = (configcreator (default_config // {
|
|
||||||
users = {
|
|
||||||
me = {
|
|
||||||
email = "me@mydomain.com";
|
|
||||||
firstName = "me";
|
|
||||||
lastName = "stillme";
|
|
||||||
groups = [ "MyGroup" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})).users;
|
|
||||||
|
|
||||||
expected = [
|
|
||||||
{
|
|
||||||
username = "me";
|
|
||||||
enabled = true;
|
|
||||||
email = "me@mydomain.com";
|
|
||||||
emailVerified = true;
|
|
||||||
firstName = "me";
|
|
||||||
lastName = "stillme";
|
|
||||||
groups = [ "MyGroup" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testUsersWithRoles = {
|
|
||||||
expr = (configcreator (default_config // {
|
|
||||||
users = {
|
|
||||||
me = {
|
|
||||||
email = "me@mydomain.com";
|
|
||||||
firstName = "me";
|
|
||||||
lastName = "stillme";
|
|
||||||
roles = [ "MyRole" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})).users;
|
|
||||||
|
|
||||||
expected = [
|
|
||||||
{
|
|
||||||
username = "me";
|
|
||||||
enabled = true;
|
|
||||||
email = "me@mydomain.com";
|
|
||||||
emailVerified = true;
|
|
||||||
firstName = "me";
|
|
||||||
lastName = "stillme";
|
|
||||||
realmRoles = [ "MyRole" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testUsersWithInitialPassword = {
|
|
||||||
expr = (configcreator (default_config // {
|
|
||||||
users = {
|
|
||||||
me = {
|
|
||||||
email = "me@mydomain.com";
|
|
||||||
firstName = "me";
|
|
||||||
lastName = "stillme";
|
|
||||||
initialPassword = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})).users;
|
|
||||||
|
|
||||||
expected = [
|
|
||||||
{
|
|
||||||
username = "me";
|
|
||||||
enabled = true;
|
|
||||||
email = "me@mydomain.com";
|
|
||||||
emailVerified = true;
|
|
||||||
firstName = "me";
|
|
||||||
lastName = "stillme";
|
|
||||||
credentials = [
|
|
||||||
{
|
|
||||||
type = "password";
|
|
||||||
userLabel = "initial";
|
|
||||||
value = "$(keycloak.users.me.password)";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testGroups = {
|
|
||||||
expr = (configcreator (default_config // {
|
|
||||||
groups = [ "MyGroup" ];
|
|
||||||
})).groups;
|
|
||||||
|
|
||||||
expected = [
|
|
||||||
{
|
|
||||||
name = "MyGroup";
|
|
||||||
path = "/MyGroup";
|
|
||||||
attributes = {};
|
|
||||||
realmRoles = [];
|
|
||||||
clientRoles = {};
|
|
||||||
subGroups = [];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testRealmRoles = {
|
|
||||||
expr = (configcreator (default_config // {
|
|
||||||
roles = {
|
|
||||||
A = [ "B" ];
|
|
||||||
B = [ ];
|
|
||||||
};
|
|
||||||
})).roles;
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
client = {};
|
|
||||||
realm = [
|
|
||||||
{
|
|
||||||
name = "A";
|
|
||||||
composite = true;
|
|
||||||
composites = {
|
|
||||||
realm = [ "B" ];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "B";
|
|
||||||
composite = false;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testClientRoles = {
|
|
||||||
expr = (configcreator (default_config // {
|
|
||||||
clients = {
|
|
||||||
clientA = {
|
|
||||||
roles = [ "cA" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})).roles;
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
client = {
|
|
||||||
clientA = [
|
|
||||||
{
|
|
||||||
name = "cA";
|
|
||||||
clientRole = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
realm = [];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testClient = {
|
|
||||||
expr = map (keep_fields [
|
|
||||||
"clientId"
|
|
||||||
"rootUrl"
|
|
||||||
"redirectUris"
|
|
||||||
"webOrigins"
|
|
||||||
"authorizationSettings"
|
|
||||||
]) (configcreator (default_config // {
|
|
||||||
clients = {
|
|
||||||
clientA = {};
|
|
||||||
};
|
|
||||||
})).clients;
|
|
||||||
expected = [
|
|
||||||
{
|
|
||||||
clientId = "clientA";
|
|
||||||
rootUrl = "https://clientA.mydomain.com";
|
|
||||||
redirectUris = ["https://clientA.mydomain.com/oauth2/callback"];
|
|
||||||
webOrigins = ["https://clientA.mydomain.com"];
|
|
||||||
authorizationSettings = {
|
|
||||||
policyEnforcementMode = "ENFORCING";
|
|
||||||
resources = [];
|
|
||||||
policies = [];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testClientAuthorization = with builtins; {
|
|
||||||
expr = (head (configcreator (default_config // {
|
|
||||||
clients = {
|
|
||||||
clientA = {
|
|
||||||
resourcesUris = {
|
|
||||||
adminPath = ["/admin/*"];
|
|
||||||
userPath = ["/*"];
|
|
||||||
};
|
|
||||||
access = {
|
|
||||||
admin = {
|
|
||||||
roles = [ "admin" ];
|
|
||||||
resources = [ "adminPath" ];
|
|
||||||
};
|
|
||||||
user = {
|
|
||||||
roles = [ "user" ];
|
|
||||||
resources = [ "userPath" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})).clients).authorizationSettings;
|
|
||||||
expected = {
|
|
||||||
policyEnforcementMode = "ENFORCING";
|
|
||||||
resources = [
|
|
||||||
{
|
|
||||||
name = "adminPath";
|
|
||||||
type = "urn:clientA:resources:adminPath";
|
|
||||||
ownerManagedAccess = false;
|
|
||||||
uris = ["/admin/*"];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "userPath";
|
|
||||||
type = "urn:clientA:resources:userPath";
|
|
||||||
ownerManagedAccess = false;
|
|
||||||
uris = ["/*"];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
policies = [
|
|
||||||
{
|
|
||||||
name = "admin has access";
|
|
||||||
type = "role";
|
|
||||||
logic = "POSITIVE";
|
|
||||||
decisionStrategy = "UNANIMOUS";
|
|
||||||
config = {
|
|
||||||
roles = ''[{"id":"admin","required":true}]'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "user has access";
|
|
||||||
type = "role";
|
|
||||||
logic = "POSITIVE";
|
|
||||||
decisionStrategy = "UNANIMOUS";
|
|
||||||
config = {
|
|
||||||
roles = ''[{"id":"user","required":true}]'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "admin has access to adminPath";
|
|
||||||
type = "resource";
|
|
||||||
logic = "POSITIVE";
|
|
||||||
decisionStrategy = "UNANIMOUS";
|
|
||||||
config = {
|
|
||||||
resources = ''["adminPath"]'';
|
|
||||||
applyPolicies = ''["admin has access"]'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "user has access to userPath";
|
|
||||||
type = "resource";
|
|
||||||
logic = "POSITIVE";
|
|
||||||
decisionStrategy = "UNANIMOUS";
|
|
||||||
config = {
|
|
||||||
resources = ''["userPath"]'';
|
|
||||||
applyPolicies = ''["user has access"]'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testClientAudience =
|
|
||||||
let
|
|
||||||
audienceProtocolMapper = config:
|
|
||||||
with builtins;
|
|
||||||
let
|
|
||||||
protocolMappers = (head config.clients).protocolMappers;
|
|
||||||
protocolMapperByName = name: protocolMappers: head (filter (x: x.name == name) protocolMappers);
|
|
||||||
in
|
|
||||||
protocolMapperByName "Audience" protocolMappers;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
expr = audienceProtocolMapper (configcreator (default_config // {
|
|
||||||
clients = {
|
|
||||||
clientA = {};
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
expected = {
|
|
||||||
name = "Audience";
|
|
||||||
protocol = "openid-connect";
|
|
||||||
protocolMapper = "oidc-audience-mapper";
|
|
||||||
config = {
|
|
||||||
"included.client.audience" = "clientA";
|
|
||||||
"id.token.claim" = "false";
|
|
||||||
"access.token.claim" = "true";
|
|
||||||
"included.custom.audience" = "clientA";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,295 +0,0 @@
|
||||||
# to run these tests:
|
|
||||||
# nix-instantiate --eval --strict . -A tests.keycloak
|
|
||||||
|
|
||||||
{ lib
|
|
||||||
, stdenv
|
|
||||||
, pkgs
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
configcreator = pkgs.callPackage ./../../keycloak-cli-config/configcreator.nix {};
|
|
||||||
|
|
||||||
# Taken from https://github.com/NixOS/nixpkgs/blob/master/lib/attrsets.nix
|
|
||||||
updateManyAttrsByPath =
|
|
||||||
with builtins;
|
|
||||||
with lib.lists;
|
|
||||||
let
|
|
||||||
# When recursing into attributes, instead of updating the `path` of each
|
|
||||||
# update using `tail`, which needs to allocate an entirely new list,
|
|
||||||
# we just pass a prefix length to use and make sure to only look at the
|
|
||||||
# path without the prefix length, so that we can reuse the original list
|
|
||||||
# entries.
|
|
||||||
go = prefixLength: hasValue: value: updates:
|
|
||||||
let
|
|
||||||
# Splits updates into ones on this level (split.right)
|
|
||||||
# And ones on levels further down (split.wrong)
|
|
||||||
split = partition (el: length el.path == prefixLength) updates;
|
|
||||||
|
|
||||||
# Groups updates on further down levels into the attributes they modify
|
|
||||||
nested = groupBy (el: elemAt el.path prefixLength) split.wrong;
|
|
||||||
|
|
||||||
# Applies only nested modification to the input value
|
|
||||||
withNestedMods =
|
|
||||||
# Return the value directly if we don't have any nested modifications
|
|
||||||
if split.wrong == [] then
|
|
||||||
if hasValue then value
|
|
||||||
else
|
|
||||||
# Throw an error if there is no value. This `head` call here is
|
|
||||||
# safe, but only in this branch since `go` could only be called
|
|
||||||
# with `hasValue == false` for nested updates, in which case
|
|
||||||
# it's also always called with at least one update
|
|
||||||
let updatePath = (head split.right).path; in
|
|
||||||
throw
|
|
||||||
( "updateManyAttrsByPath: Path '${showAttrPath updatePath}' does "
|
|
||||||
+ "not exist in the given value, but the first update to this "
|
|
||||||
+ "path tries to access the existing value.")
|
|
||||||
else
|
|
||||||
# If there are nested modifications, try to apply them to the value
|
|
||||||
if ! hasValue then
|
|
||||||
# But if we don't have a value, just use an empty attribute set
|
|
||||||
# as the value, but simplify the code a bit
|
|
||||||
mapAttrs (name: go (prefixLength + 1) false null) nested
|
|
||||||
else if isAttrs value then
|
|
||||||
# If we do have a value and it's an attribute set, override it
|
|
||||||
# with the nested modifications
|
|
||||||
value //
|
|
||||||
mapAttrs (name: go (prefixLength + 1) (value ? ${name}) value.${name}) nested
|
|
||||||
else
|
|
||||||
# However if it's not an attribute set, we can't apply the nested
|
|
||||||
# modifications, throw an error
|
|
||||||
let updatePath = (head split.wrong).path; in
|
|
||||||
throw
|
|
||||||
( "updateManyAttrsByPath: Path '${showAttrPath updatePath}' needs to "
|
|
||||||
+ "be updated, but path '${showAttrPath (take prefixLength updatePath)}' "
|
|
||||||
+ "of the given value is not an attribute set, so we can't "
|
|
||||||
+ "update an attribute inside of it.");
|
|
||||||
|
|
||||||
# We get the final result by applying all the updates on this level
|
|
||||||
# after having applied all the nested updates
|
|
||||||
# We use foldl instead of foldl' so that in case of multiple updates,
|
|
||||||
# intermediate values aren't evaluated if not needed
|
|
||||||
in foldl (acc: el: el.update acc) withNestedMods split.right;
|
|
||||||
|
|
||||||
in updates: value: go 0 true value updates;
|
|
||||||
in
|
|
||||||
|
|
||||||
with lib.attrsets;
|
|
||||||
lib.runTests {
|
|
||||||
testConfigEmpty = {
|
|
||||||
expr = configcreator {
|
|
||||||
realm = "myrealm";
|
|
||||||
domain = "domain.com";
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
id = "myrealm";
|
|
||||||
realm = "myrealm";
|
|
||||||
enabled = true;
|
|
||||||
clients = [];
|
|
||||||
groups = [];
|
|
||||||
roles = {
|
|
||||||
client = {};
|
|
||||||
realm = [];
|
|
||||||
};
|
|
||||||
users = [];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testConfigRole = {
|
|
||||||
expr = configcreator {
|
|
||||||
realm = "myrealm";
|
|
||||||
domain = "domain.com";
|
|
||||||
roles = {
|
|
||||||
user = [];
|
|
||||||
admin = ["user"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
id = "myrealm";
|
|
||||||
realm = "myrealm";
|
|
||||||
enabled = true;
|
|
||||||
clients = [];
|
|
||||||
groups = [];
|
|
||||||
roles = {
|
|
||||||
realm = [
|
|
||||||
{
|
|
||||||
name = "admin";
|
|
||||||
composite = true;
|
|
||||||
composites = {
|
|
||||||
realm = ["user"];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "user";
|
|
||||||
composite = false;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
client = {};
|
|
||||||
};
|
|
||||||
users = [];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testConfigClient = {
|
|
||||||
expr =
|
|
||||||
let
|
|
||||||
c = configcreator {
|
|
||||||
realm = "myrealm";
|
|
||||||
domain = "domain.com";
|
|
||||||
clients = {
|
|
||||||
myclient = {};
|
|
||||||
myclient2 = {
|
|
||||||
roles = ["uma"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
updateManyAttrsByPath [
|
|
||||||
{
|
|
||||||
path = [ "clients" ];
|
|
||||||
# We don't care about the value of the protocolMappers
|
|
||||||
# field because its value is hardcoded.
|
|
||||||
update = clients: map (filterAttrs (n: v: n != "protocolMappers")) clients;
|
|
||||||
}
|
|
||||||
] c;
|
|
||||||
expected = {
|
|
||||||
id = "myrealm";
|
|
||||||
realm = "myrealm";
|
|
||||||
enabled = true;
|
|
||||||
clients = [
|
|
||||||
{
|
|
||||||
clientId = "myclient";
|
|
||||||
rootUrl = "https://myclient.domain.com";
|
|
||||||
clientAuthenticatorType = "client-secret";
|
|
||||||
redirectUris = [
|
|
||||||
"https://myclient.domain.com/oauth2/callback"
|
|
||||||
];
|
|
||||||
webOrigins = [
|
|
||||||
"https://myclient.domain.com"
|
|
||||||
];
|
|
||||||
authorizationServicesEnabled = true;
|
|
||||||
serviceAccountsEnabled = true;
|
|
||||||
protocol = "openid-connect";
|
|
||||||
publicClient = false;
|
|
||||||
authorizationSettings = {
|
|
||||||
policyEnforcementMode = "ENFORCING";
|
|
||||||
resources = [];
|
|
||||||
policies = [];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
clientId = "myclient2";
|
|
||||||
rootUrl = "https://myclient2.domain.com";
|
|
||||||
clientAuthenticatorType = "client-secret";
|
|
||||||
redirectUris = [
|
|
||||||
"https://myclient2.domain.com/oauth2/callback"
|
|
||||||
];
|
|
||||||
webOrigins = [
|
|
||||||
"https://myclient2.domain.com"
|
|
||||||
];
|
|
||||||
authorizationServicesEnabled = true;
|
|
||||||
serviceAccountsEnabled = true;
|
|
||||||
protocol = "openid-connect";
|
|
||||||
publicClient = false;
|
|
||||||
authorizationSettings = {
|
|
||||||
policyEnforcementMode = "ENFORCING";
|
|
||||||
resources = [];
|
|
||||||
policies = [];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
groups = [];
|
|
||||||
roles = {
|
|
||||||
client = {
|
|
||||||
myclient = [];
|
|
||||||
myclient2 = [
|
|
||||||
{
|
|
||||||
name = "uma";
|
|
||||||
clientRole = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
realm = [];
|
|
||||||
};
|
|
||||||
users = [];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testConfigUser = {
|
|
||||||
expr = configcreator {
|
|
||||||
realm = "myrealm";
|
|
||||||
domain = "domain.com";
|
|
||||||
users = {
|
|
||||||
me = {
|
|
||||||
email = "me@me.com";
|
|
||||||
firstName = null;
|
|
||||||
lastName = "Me";
|
|
||||||
realmRoles = [ "role" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
id = "myrealm";
|
|
||||||
realm = "myrealm";
|
|
||||||
enabled = true;
|
|
||||||
clients = [];
|
|
||||||
groups = [];
|
|
||||||
roles = {
|
|
||||||
client = {};
|
|
||||||
realm = [];
|
|
||||||
};
|
|
||||||
users = [
|
|
||||||
{
|
|
||||||
enabled = true;
|
|
||||||
username = "me";
|
|
||||||
email = "me@me.com";
|
|
||||||
emailVerified = true;
|
|
||||||
firstName = null;
|
|
||||||
lastName = "Me";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testConfigUserInitialPassword = {
|
|
||||||
expr = configcreator {
|
|
||||||
realm = "myrealm";
|
|
||||||
domain = "domain.com";
|
|
||||||
users = {
|
|
||||||
me = {
|
|
||||||
email = "me@me.com";
|
|
||||||
firstName = null;
|
|
||||||
lastName = "Me";
|
|
||||||
initialPassword = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
expected = {
|
|
||||||
id = "myrealm";
|
|
||||||
realm = "myrealm";
|
|
||||||
enabled = true;
|
|
||||||
clients = [];
|
|
||||||
groups = [];
|
|
||||||
roles = {
|
|
||||||
client = {};
|
|
||||||
realm = [];
|
|
||||||
};
|
|
||||||
users = [
|
|
||||||
{
|
|
||||||
enabled = true;
|
|
||||||
username = "me";
|
|
||||||
email = "me@me.com";
|
|
||||||
emailVerified = true;
|
|
||||||
firstName = null;
|
|
||||||
lastName = "Me";
|
|
||||||
credentials = [
|
|
||||||
{
|
|
||||||
type = "password";
|
|
||||||
userLabel = "initial";
|
|
||||||
value = "$(keycloak.users.me.password)";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
}:
|
|
||||||
{ documentRoot
|
|
||||||
, name ? "ttrss"
|
|
||||||
, serviceName ? "ttrss"
|
|
||||||
, subdomain ? "ttrss"
|
|
||||||
, user ? "http"
|
|
||||||
, group ? "http"
|
|
||||||
, domain
|
|
||||||
, lock_directory
|
|
||||||
, cache_directory
|
|
||||||
, feed_icons_directory
|
|
||||||
, db_host
|
|
||||||
, db_port
|
|
||||||
, db_username
|
|
||||||
, db_database
|
|
||||||
, db_password
|
|
||||||
# , domain
|
|
||||||
# , smtp_host
|
|
||||||
# , smtp_login
|
|
||||||
# , smtp_password
|
|
||||||
# , feedback_url ? ""
|
|
||||||
, auth_remote_post_logout_url ? null
|
|
||||||
, enabled_plugins ? [ "auth_remote" "note" ]
|
|
||||||
|
|
||||||
, dependsOn ? {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
asTtrssConfig = attrs: builtins.concatStringsSep "\n" (
|
|
||||||
["<?php" ""]
|
|
||||||
++ lib.attrsets.mapAttrsToList wrapPutenv attrs
|
|
||||||
++ [""] # Needs a newline at the end
|
|
||||||
);
|
|
||||||
wrapPutenv = key: value: "putenv('TTRSS_${lib.toUpper key}=${value}');";
|
|
||||||
|
|
||||||
config = self_url_path: db: {
|
|
||||||
db_type = "pgsql";
|
|
||||||
db_host = db_host db;
|
|
||||||
db_port = builtins.toString db_port;
|
|
||||||
db_user = db_username;
|
|
||||||
db_name = db_database;
|
|
||||||
db_pass = db_password;
|
|
||||||
|
|
||||||
self_url_path = self_url_path;
|
|
||||||
single_user_mode = "false";
|
|
||||||
simple_update_mode = "false";
|
|
||||||
php_executable = "${pkgs.php}/bin/php";
|
|
||||||
|
|
||||||
lock_directory = "${lock_directory}";
|
|
||||||
cache_dir = "${cache_directory}";
|
|
||||||
icons_dir = "${feed_icons_directory}";
|
|
||||||
icons_url = "feed-icons";
|
|
||||||
|
|
||||||
auth_auto_create = "true";
|
|
||||||
auth_auto_login = "false";
|
|
||||||
enable_registration = "false";
|
|
||||||
|
|
||||||
force_article_purge = "0";
|
|
||||||
sphinx_server = "localhost:9312";
|
|
||||||
sphinx_index = "ttrss, delta";
|
|
||||||
|
|
||||||
session_check_address = "true";
|
|
||||||
session_cookie_lifetime = "0";
|
|
||||||
session_expire_time = "86400";
|
|
||||||
|
|
||||||
smtp_from_name = "Tiny Tiny RSS";
|
|
||||||
# smtp_from_address = "noreply@${domain}";
|
|
||||||
# inherit smtp_host smtp_login smtp_password;
|
|
||||||
# inherit feedback_url;
|
|
||||||
digest_enable = "true";
|
|
||||||
digest_email_limit = "10";
|
|
||||||
digest_subject = "[tt-rss] New headlines for last 24 hours";
|
|
||||||
|
|
||||||
deamon_sends_digest = "true";
|
|
||||||
|
|
||||||
check_for_new_version = "false";
|
|
||||||
plugins = builtins.concatStringsSep ", " enabled_plugins;
|
|
||||||
|
|
||||||
log_destination = "syslog";
|
|
||||||
} // (
|
|
||||||
if auth_remote_post_logout_url != null then {
|
|
||||||
allow_remote_user_auth = "false";
|
|
||||||
auth_remote_post_logout_url = auth_remote_post_logout_url;
|
|
||||||
} else {}
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
name = serviceName;
|
|
||||||
|
|
||||||
pkg = {
|
|
||||||
db
|
|
||||||
}: stdenv.mkDerivation rec {
|
|
||||||
inherit name;
|
|
||||||
src = pkgs.tt-rss;
|
|
||||||
|
|
||||||
buildCommand =
|
|
||||||
let
|
|
||||||
configFile = pkgs.writeText "config.php" (asTtrssConfig (config "https://${subdomain}.${domain}/" db));
|
|
||||||
dr = dirOf documentRoot;
|
|
||||||
in
|
|
||||||
''
|
|
||||||
mkdir -p $out/${name}
|
|
||||||
cp -ra $src/* $out/${name}
|
|
||||||
cp ${configFile} $out/${name}/config.php
|
|
||||||
|
|
||||||
echo "${dr}" > $out/.dysnomia-targetdir
|
|
||||||
echo "${user}:${group}" > $out/.dysnomia-filesetowner
|
|
||||||
|
|
||||||
cat > $out/.dysnomia-fileset <<FILESET
|
|
||||||
symlink $out/${name}
|
|
||||||
target ${dr}
|
|
||||||
FILESET
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit dependsOn;
|
|
||||||
type = "fileset";
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
, user
|
|
||||||
, binDir
|
|
||||||
|
|
||||||
, dependsOn ? {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
pkg =
|
|
||||||
{ db
|
|
||||||
, config
|
|
||||||
}:
|
|
||||||
stdenv.mkDerivation {
|
|
||||||
name = "dbupgrade";
|
|
||||||
|
|
||||||
src = pkgs.writeTextDir "wrapper" ''
|
|
||||||
#!/bin/bash -e
|
|
||||||
|
|
||||||
sudo -u ${user} bash <<HERE
|
|
||||||
case "$1" in
|
|
||||||
activate)
|
|
||||||
${pkgs.php}/bin/php ${binDir}/update.php --update-schema=force-yes
|
|
||||||
;;
|
|
||||||
lock)
|
|
||||||
if [ -f /tmp/wrapper.lock ]
|
|
||||||
then
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "1" > /tmp/wrapper.lock
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
unlock)
|
|
||||||
rm -f /tmp/wrapper.lock
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
HERE
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/bin
|
|
||||||
cp $src/wrapper $out/bin
|
|
||||||
chmod +x $out/bin/*
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit dependsOn;
|
|
||||||
type = "wrapper";
|
|
||||||
}
|
|
|
@ -1,216 +0,0 @@
|
||||||
{ customPkgs
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ serviceName ? "Ttrss"
|
|
||||||
, siteName ? "ttrss"
|
|
||||||
, subdomain ? "ttrss"
|
|
||||||
, domain ? ""
|
|
||||||
, ingress ? 18010
|
|
||||||
|
|
||||||
, user ? "ttrss"
|
|
||||||
, group ? "ttrss"
|
|
||||||
, documentRoot ? "/usr/share/webapps/ttrss"
|
|
||||||
, postgresDatabase ? "ttrss"
|
|
||||||
, postgresUser ? "ttrss"
|
|
||||||
, postgresPasswordLocation ? "ttrss"
|
|
||||||
|
|
||||||
, smtp ? {}
|
|
||||||
, sso ? {}
|
|
||||||
|
|
||||||
, distribution ? {}
|
|
||||||
|
|
||||||
, configPkg ? pkgs.callPackage (import ./config.nix) {}
|
|
||||||
, normalizeHeaderPkg ? pkgs.callPackate (import ./normalize-headers.nix) {}
|
|
||||||
, updateServicePkg ? pkgs.callPackage (import ./update.nix) {inherit utils;}
|
|
||||||
, dbupgradePkg ? pkgs.callPackage (import ./dbupgrade.nix) {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
with pkgs.lib.attrsets;
|
|
||||||
let
|
|
||||||
rtdir = "/run/ttrss";
|
|
||||||
lock_directory = "${rtdir}/lock";
|
|
||||||
cache_directory = "${rtdir}/cache";
|
|
||||||
persistent_dir = "/var/lib/${siteName}";
|
|
||||||
feed_icons_directory = "${persistent_dir}/feed-icons";
|
|
||||||
in
|
|
||||||
rec {
|
|
||||||
inherit subdomain;
|
|
||||||
|
|
||||||
db = customPkgs.mkPostgresDB {
|
|
||||||
name = "${serviceName}PostgresDB";
|
|
||||||
|
|
||||||
database = postgresDatabase;
|
|
||||||
username = postgresUser;
|
|
||||||
# TODO: use passwordFile
|
|
||||||
password = postgresPasswordLocation;
|
|
||||||
};
|
|
||||||
|
|
||||||
config =
|
|
||||||
let
|
|
||||||
domain = utils.getDomain distribution "${serviceName}Config";
|
|
||||||
in
|
|
||||||
configPkg ({
|
|
||||||
name = "ttrss";
|
|
||||||
serviceName = "${serviceName}Config";
|
|
||||||
|
|
||||||
inherit subdomain;
|
|
||||||
inherit documentRoot;
|
|
||||||
inherit lock_directory cache_directory feed_icons_directory;
|
|
||||||
inherit (phpfpmService) user group;
|
|
||||||
inherit domain;
|
|
||||||
|
|
||||||
db_host = db: db.target.properties.hostname;
|
|
||||||
db_port = (utils.getTarget distribution "TtrssPostgresDB").containers.postgresql-database.port;
|
|
||||||
db_database = postgresDatabase;
|
|
||||||
db_username = postgresUser;
|
|
||||||
# TODO: use passwordFile
|
|
||||||
db_password = postgresPasswordLocation;
|
|
||||||
enabled_plugins = [ "auth_remote" "note" ];
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit db;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// optionalAttrs (sso != {}) {
|
|
||||||
auth_remote_post_logout_url = "https://keycloak.${domain}/realms/${sso.realm}/account";
|
|
||||||
});
|
|
||||||
|
|
||||||
dbupgrade = dbupgradePkg {
|
|
||||||
name = "${serviceName}DBUpgrade";
|
|
||||||
|
|
||||||
inherit user;
|
|
||||||
binDir = documentRoot;
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit config db;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
service = customPkgs.mkNginxService {
|
|
||||||
name = "${serviceName}Service";
|
|
||||||
|
|
||||||
inherit siteName;
|
|
||||||
inherit user group;
|
|
||||||
runtimeDirectory = "/run/nginx";
|
|
||||||
|
|
||||||
config = {
|
|
||||||
port = ingress;
|
|
||||||
inherit siteName;
|
|
||||||
siteRoot = documentRoot;
|
|
||||||
phpFpmSiteSocket = phpfpmService.siteSocket;
|
|
||||||
};
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
phpfpmService = customPkgs.mkPHPFPMService {
|
|
||||||
name = "${serviceName}PHPFPMService";
|
|
||||||
|
|
||||||
inherit siteName;
|
|
||||||
runtimeDirectory = rtdir;
|
|
||||||
|
|
||||||
# Must match haproxy for socket
|
|
||||||
inherit user group;
|
|
||||||
socketUser = service.user;
|
|
||||||
socketGroup = service.group;
|
|
||||||
|
|
||||||
phpIniConfig = {
|
|
||||||
prependFile = normalizeHeaderPkg {
|
|
||||||
debug = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
siteConfig = {
|
|
||||||
siteRoot = documentRoot;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
updateService = updateServicePkg {
|
|
||||||
name = "${serviceName}UpdateService";
|
|
||||||
|
|
||||||
inherit documentRoot;
|
|
||||||
inherit (phpfpmService) user group;
|
|
||||||
readOnlyPaths = [];
|
|
||||||
readWritePaths = [
|
|
||||||
lock_directory
|
|
||||||
cache_directory
|
|
||||||
feed_icons_directory
|
|
||||||
];
|
|
||||||
postgresServiceName = (utils.getTarget distribution "TtrssPostgresDB").containers.postgresql-database.service_name;
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit config db dbupgrade;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
haproxy = {
|
|
||||||
frontend = {
|
|
||||||
acl = {
|
|
||||||
acl_ttrss = "hdr_beg(host) ttrss.";
|
|
||||||
};
|
|
||||||
use_backend = "if acl_ttrss";
|
|
||||||
};
|
|
||||||
backend = {
|
|
||||||
servers = [
|
|
||||||
{
|
|
||||||
name = "ttrss1";
|
|
||||||
address = service.nginxSocket;
|
|
||||||
balance = "roundrobin";
|
|
||||||
check = {
|
|
||||||
inter = "5s";
|
|
||||||
downinter = "15s";
|
|
||||||
fall = "3";
|
|
||||||
rise = "3";
|
|
||||||
};
|
|
||||||
httpcheck = "GET /";
|
|
||||||
# captureoutput = {
|
|
||||||
# firstport = "3000";
|
|
||||||
# secondport = "3001";
|
|
||||||
# issocket = true;
|
|
||||||
# outputfile = "/tmp/haproxy/ttrss.stream";
|
|
||||||
# };
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
debugHeaders = "acl_ttrss";
|
|
||||||
};
|
|
||||||
|
|
||||||
keycloakCliConfig = {
|
|
||||||
clients = {
|
|
||||||
ttrss = {
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
deployKeys = domain: {};
|
|
||||||
|
|
||||||
services = {
|
|
||||||
${db.name} = db;
|
|
||||||
${config.name} = config;
|
|
||||||
${dbupgrade.name} = dbupgrade;
|
|
||||||
${service.name} = service;
|
|
||||||
${phpfpmService.name} = phpfpmService;
|
|
||||||
${updateService.name} = updateService;
|
|
||||||
};
|
|
||||||
|
|
||||||
distribute = on: {
|
|
||||||
${db.name} = on;
|
|
||||||
${config.name} = on;
|
|
||||||
${dbupgrade.name} = on;
|
|
||||||
${service.name} = on;
|
|
||||||
${phpfpmService.name} = on;
|
|
||||||
${updateService.name} = on;
|
|
||||||
};
|
|
||||||
|
|
||||||
directories_modes = {
|
|
||||||
"${rtdir}" = "0550";
|
|
||||||
"${lock_directory}" = "0770";
|
|
||||||
"${cache_directory}" = "0770";
|
|
||||||
"${cache_directory}/upload" = "0770";
|
|
||||||
"${cache_directory}/images" = "0770";
|
|
||||||
"${cache_directory}/export" = "0770";
|
|
||||||
"${feed_icons_directory}" = "0770";
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
{ pkgs
|
|
||||||
}:
|
|
||||||
{ debug ? false
|
|
||||||
}:
|
|
||||||
|
|
||||||
pkgs.writeText "normalize-headers.php" (''
|
|
||||||
<?php
|
|
||||||
|
|
||||||
$trustedProxies = array(
|
|
||||||
'127.0.0.1',
|
|
||||||
'@'
|
|
||||||
);
|
|
||||||
|
|
||||||
# phpinfo(INFO_VARIABLES);
|
|
||||||
|
|
||||||
if (isSet($_SERVER['REMOTE_ADDR'])) {
|
|
||||||
|
|
||||||
$remote = $_SERVER['REMOTE_ADDR'];
|
|
||||||
|
|
||||||
$allowedHeaders = array(
|
|
||||||
'HTTP_X_FORWARDED_FOR' => 'REMOTE_ADDR',
|
|
||||||
'HTTP_X_REAL_IP' => 'REMOTE_HOST',
|
|
||||||
'HTTP_X_FORWARDED_PORT' => 'REMOTE_PORT',
|
|
||||||
'HTTP_X_FORWARDED_HTTPS' => 'HTTPS',
|
|
||||||
'HTTP_X_FORWARDED_SERVER_ADDR' => 'SERVER_ADDR',
|
|
||||||
'HTTP_X_FORWARDED_SERVER_NAME' => 'SERVER_NAME',
|
|
||||||
'HTTP_X_FORWARDED_SERVER_PORT' => 'SERVER_PORT',
|
|
||||||
'HTTP_X_FORWARDED_PREFERRED_USERNAME' => 'REMOTE_USER',
|
|
||||||
);
|
|
||||||
|
|
||||||
if(in_array($remote, $trustedProxies)) {
|
|
||||||
foreach($allowedHeaders as $header => $serverVar) {
|
|
||||||
if(isSet($_SERVER[$header])) {
|
|
||||||
if(isSet($_SERVER[$serverVar])) {
|
|
||||||
$_SERVER["ORIGINAL_$serverVar"] = $_SERVER[$serverVar];
|
|
||||||
}
|
|
||||||
|
|
||||||
$_SERVER[$serverVar] = explode(',', $_SERVER[$header], 2)[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
'' + (if !debug then "" else ''
|
|
||||||
trigger_error(print_r($_SERVER, true), E_USER_WARNING);
|
|
||||||
'')
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
, user
|
|
||||||
, group
|
|
||||||
, documentRoot
|
|
||||||
, readOnlyPaths ? []
|
|
||||||
, readWritePaths ? []
|
|
||||||
, postgresServiceName
|
|
||||||
|
|
||||||
, dependsOn ? {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
# Assumptions:
|
|
||||||
# - Do not run as root.
|
|
||||||
# - Image cache should be writable.
|
|
||||||
# - Upload cache should be writable.
|
|
||||||
# - Data export cache should be writable.
|
|
||||||
# - ICONS_DIR should be writable.
|
|
||||||
# - LOCK_DIRECTORY should be writable.
|
|
||||||
|
|
||||||
let
|
|
||||||
fullPath = "${documentRoot}";
|
|
||||||
roPaths = [fullPath] ++ readOnlyPaths;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
pkg = {...}: utils.systemd.mkService rec {
|
|
||||||
name = "ttrss-update";
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=${name}
|
|
||||||
After=network.target ${postgresServiceName}
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=${user}
|
|
||||||
Group=${group}
|
|
||||||
ExecStart=${pkgs.php}/bin/php ${fullPath}/update_daemon2.php
|
|
||||||
|
|
||||||
RuntimeDirectory=${name}
|
|
||||||
|
|
||||||
PrivateDevices=true
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectKernelModules=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectKernelLogs=true
|
|
||||||
ProtectHome=true
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectClock=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
LockPersonality=true
|
|
||||||
NoNewPrivileges=true
|
|
||||||
|
|
||||||
SystemCallFilter=@basic-io @file-system @process @system-service
|
|
||||||
|
|
||||||
ProtectSystem=strict
|
|
||||||
ReadOnlyPaths=${builtins.concatStringsSep " " roPaths}
|
|
||||||
ReadWritePaths=${builtins.concatStringsSep " " readWritePaths}
|
|
||||||
|
|
||||||
# NoExecPaths=/
|
|
||||||
# ExecPaths=${pkgs.php}/bin
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit dependsOn;
|
|
||||||
type = "systemd-unit";
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
}:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
with lib.lists;
|
|
||||||
with lib.attrsets;
|
|
||||||
rec {
|
|
||||||
tmpFilesFromDirectories = user: group: d:
|
|
||||||
let
|
|
||||||
wrapTmpfiles = dir: mode: "d '${dir}' ${mode} ${user} ${group} - -";
|
|
||||||
in
|
|
||||||
mapAttrsToList wrapTmpfiles d;
|
|
||||||
|
|
||||||
systemd = {
|
|
||||||
mkService = {name, content, timer ? null}: stdenv.mkDerivation {
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
src = pkgs.writeTextDir "${name}.service" content;
|
|
||||||
timerSrc = pkgs.writeTextDir "${name}.timer" timer;
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/etc/systemd/system
|
|
||||||
cp $src/*.service $out/etc/systemd/system
|
|
||||||
'' + (if timer == null then "" else ''
|
|
||||||
cp $timerSrc/*.timer $out/etc/systemd/system
|
|
||||||
'');
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
mkConfigFile = {dir, name, content}: stdenv.mkDerivation rec {
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
src = pkgs.writeTextDir name content;
|
|
||||||
|
|
||||||
buildCommand = ''
|
|
||||||
mkdir -p $out
|
|
||||||
cp ${src}/${name} $out/${name}
|
|
||||||
|
|
||||||
echo "${dir}" > $out/.dysnomia-targetdir
|
|
||||||
|
|
||||||
cat > $out/.dysnomia-fileset <<FILESET
|
|
||||||
symlink $out/${name}
|
|
||||||
target .
|
|
||||||
FILESET
|
|
||||||
'';
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
dnsmasqConfig = domain: subdomains:
|
|
||||||
''
|
|
||||||
${concatMapStringsSep "\n" (x: "address=/${x}.${domain}/127.0.0.1") subdomains}
|
|
||||||
domain=${domain}
|
|
||||||
'';
|
|
||||||
|
|
||||||
keyEnvironmentFile = path: "EnvironmentFile=/run/keys/${path}";
|
|
||||||
keyEnvironmentFiles = names: concatMapStrings (path: "${keyEnvironmentFile path}\n") (attrValues names);
|
|
||||||
keyServiceDependencies = names: concatMapStringsSep " " (path: "${path}-key.service") (attrValues names);
|
|
||||||
|
|
||||||
recursiveMerge = attrList:
|
|
||||||
let f = attrPath:
|
|
||||||
zipAttrsWith (n: values:
|
|
||||||
if all isList values then
|
|
||||||
concatLists values
|
|
||||||
else if all isAttrs values then
|
|
||||||
f (attrPath ++ [n]) values
|
|
||||||
else
|
|
||||||
last values
|
|
||||||
);
|
|
||||||
in f [] attrList;
|
|
||||||
|
|
||||||
getTarget = distribution: name: builtins.elemAt (builtins.getAttr name distribution) 0;
|
|
||||||
|
|
||||||
getDomain = distribution: name: (getTarget distribution name).containers.system.domain;
|
|
||||||
|
|
||||||
unitDepends = verb: dependsOn:
|
|
||||||
let
|
|
||||||
withSystemdUnitFile = filter (hasAttr "systemdUnitFile") (attrValues dependsOn);
|
|
||||||
|
|
||||||
systemdUnitFiles = map (x: x.systemdUnitFile) withSystemdUnitFile;
|
|
||||||
in
|
|
||||||
if length systemdUnitFiles == 0 then
|
|
||||||
""
|
|
||||||
else
|
|
||||||
"${verb}=${concatStringsSep " " systemdUnitFiles}";
|
|
||||||
}
|
|
|
@ -1,267 +0,0 @@
|
||||||
{ customPkgs
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
, secret
|
|
||||||
}:
|
|
||||||
{ serviceName ? "Vaultwarden"
|
|
||||||
, subdomain ? "vaultwarden"
|
|
||||||
, ingress ? 18005
|
|
||||||
, signupsAllowed ? true # signups allowed since we're behind SSO
|
|
||||||
, signupsVerify ? false
|
|
||||||
|
|
||||||
, user ? "vaultwarden"
|
|
||||||
, group ? "vaultwarden"
|
|
||||||
, dataFolder ? "/var/lib/vaultwarden"
|
|
||||||
, postgresDatabase ? "vaultwarden"
|
|
||||||
, postgresUser ? "vaultwarden"
|
|
||||||
, postgresPasswordLocation ? "vaultwarden"
|
|
||||||
, webvaultEnabled ? true
|
|
||||||
, webvaultPath ? "/usr/share/webapps/vaultwarden"
|
|
||||||
|
|
||||||
, cookieSecretName ? "cookiesecret"
|
|
||||||
, clientSecretName ? "clientsecret"
|
|
||||||
|
|
||||||
, smtp ? {}
|
|
||||||
, sso ? {}
|
|
||||||
|
|
||||||
, distribution ? {}
|
|
||||||
, KeycloakService ? null
|
|
||||||
, KeycloakCliService ? null
|
|
||||||
, HaproxyService ? null
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
mkVaultwardenWeb = pkgs.callPackage ./web.nix {inherit utils;};
|
|
||||||
|
|
||||||
ssoIngress = if sso != {} then ingress else null;
|
|
||||||
serviceIngress = if sso != {} then ingress+1 else ingress;
|
|
||||||
metricsPort = if sso != {} then ingress+2 else ingress+1;
|
|
||||||
|
|
||||||
smtpConfig = smtp;
|
|
||||||
in
|
|
||||||
rec {
|
|
||||||
inherit user group;
|
|
||||||
inherit subdomain;
|
|
||||||
|
|
||||||
db = customPkgs.mkPostgresDB {
|
|
||||||
name = "${serviceName}PostgresDB";
|
|
||||||
|
|
||||||
database = postgresDatabase;
|
|
||||||
username = postgresUser;
|
|
||||||
# TODO: use passwordFile
|
|
||||||
password = postgresPasswordLocation;
|
|
||||||
};
|
|
||||||
|
|
||||||
web = mkVaultwardenWeb {
|
|
||||||
name = "${serviceName}Web";
|
|
||||||
|
|
||||||
path = webvaultPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
service = let
|
|
||||||
name = "${serviceName}Service";
|
|
||||||
domain = utils.getDomain distribution name;
|
|
||||||
|
|
||||||
pkgsVaultwarden-1_27_0 =
|
|
||||||
let pkg = builtins.fetchurl {
|
|
||||||
url = "https://raw.githubusercontent.com/NixOS/nixpkgs/988cc958c57ce4350ec248d2d53087777f9e1949/pkgs/tools/security/vaultwarden/default.nix";
|
|
||||||
sha256 = "0hwjbq5qb8y5frb2ca3m501x84zaibzyn088zzaf7zcwkxvqb0im";
|
|
||||||
};
|
|
||||||
in pkgs.callPackage pkg {
|
|
||||||
inherit (pkgs.darwin.apple_sdk.frameworks) Security CoreServices;
|
|
||||||
dbBackend = "postgresql";
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
pkg =
|
|
||||||
{ db
|
|
||||||
, web
|
|
||||||
}: let
|
|
||||||
postgresHost = db.target.properties.hostname;
|
|
||||||
in utils.systemd.mkService rec {
|
|
||||||
name = "vaultwarden";
|
|
||||||
|
|
||||||
content = ''
|
|
||||||
[Unit]
|
|
||||||
Description=Vaultwarden Server
|
|
||||||
Documentation=https://github.com/dani-garcia/vaultwarden
|
|
||||||
After=network.target
|
|
||||||
After=${utils.keyServiceDependencies smtpConfig.keys}
|
|
||||||
Wants=${utils.keyServiceDependencies smtpConfig.keys}
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Environment=DATA_FOLDER=${dataFolder}
|
|
||||||
Environment=DATABASE_URL=postgresql://${postgresUser}:${postgresPasswordLocation}@${postgresHost}/${postgresDatabase}
|
|
||||||
Environment=IP_HEADER=X-Real-IP
|
|
||||||
|
|
||||||
Environment=WEB_VAULT_FOLDER=${web.path}
|
|
||||||
Environment=WEB_VAULT_ENABLED=${if webvaultEnabled then "true" else "false"}
|
|
||||||
|
|
||||||
Environment=SIGNUPS_ALLOWED=${if signupsAllowed then "true" else "false"}
|
|
||||||
Environment=SIGNUPS_VERIFY=${if signupsVerify then "true" else "false"}
|
|
||||||
# Disabled because the /admin path is protected by SSO
|
|
||||||
Environment=DISABLE_ADMIN_TOKEN=true
|
|
||||||
Environment=INVITATIONS_ALLOWED=true
|
|
||||||
Environment=DOMAIN=https://${subdomain}.${domain}
|
|
||||||
|
|
||||||
# Assumes we're behind a reverse proxy
|
|
||||||
Environment=ROCKET_ADDRESS=127.0.0.1
|
|
||||||
Environment=ROCKET_PORT=${builtins.toString serviceIngress}
|
|
||||||
Environment=USE_SYSLOG=true
|
|
||||||
Environment=EXTENDED_LOGGING=true
|
|
||||||
Environment=LOG_FILE=
|
|
||||||
Environment=LOG_LEVEL=trace
|
|
||||||
|
|
||||||
${utils.keyEnvironmentFiles smtpConfig.keys}
|
|
||||||
Environment=SMTP_FROM=${smtpConfig.from}
|
|
||||||
Environment=SMTP_FROM_NAME=${smtpConfig.fromName}
|
|
||||||
Environment=SMTP_PORT=${builtins.toString smtpConfig.port}
|
|
||||||
Environment=SMTP_AUTH_MECHANISM=${smtpConfig.authMechanism}
|
|
||||||
|
|
||||||
ExecStart=${pkgsVaultwarden-1_27_0}/bin/vaultwarden
|
|
||||||
WorkingDirectory=${dataFolder}
|
|
||||||
StateDirectory=${name}
|
|
||||||
User=${user}
|
|
||||||
Group=${group}
|
|
||||||
|
|
||||||
# Allow vaultwarden to bind ports in the range of 0-1024 and restrict it to
|
|
||||||
# that capability
|
|
||||||
CapabilityBoundingSet=${if serviceIngress <= 1024 then "CAP_NET_BIND_SERVICE" else ""}
|
|
||||||
AmbientCapabilities=${if serviceIngress <= 1024 then "CAP_NET_BIND_SERVICE" else ""}
|
|
||||||
|
|
||||||
PrivateUsers=yes
|
|
||||||
NoNewPrivileges=yes
|
|
||||||
LimitNOFILE=1048576
|
|
||||||
UMask=0077
|
|
||||||
ProtectSystem=strict
|
|
||||||
ProtectHome=yes
|
|
||||||
# ReadWritePaths=${dataFolder}
|
|
||||||
PrivateTmp=yes
|
|
||||||
PrivateDevices=yes
|
|
||||||
ProtectHostname=yes
|
|
||||||
ProtectClock=yes
|
|
||||||
ProtectKernelTunables=yes
|
|
||||||
ProtectKernelModules=yes
|
|
||||||
ProtectKernelLogs=yes
|
|
||||||
ProtectControlGroups=yes
|
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
||||||
RestrictNamespaces=yes
|
|
||||||
LockPersonality=yes
|
|
||||||
MemoryDenyWriteExecute=yes
|
|
||||||
RestrictRealtime=yes
|
|
||||||
RestrictSUIDSGID=yes
|
|
||||||
RemoveIPC=yes
|
|
||||||
|
|
||||||
SystemCallFilter=@system-service
|
|
||||||
SystemCallFilter=~@privileged @resources
|
|
||||||
SystemCallArchitectures=native
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
dependsOn = {
|
|
||||||
inherit db;
|
|
||||||
inherit web;
|
|
||||||
};
|
|
||||||
type = "systemd-unit";
|
|
||||||
};
|
|
||||||
|
|
||||||
haproxy = service: {
|
|
||||||
frontend = {
|
|
||||||
acl = {
|
|
||||||
acl_vaultwarden = "hdr_beg(host) vaultwarden.";
|
|
||||||
};
|
|
||||||
use_backend = "if acl_vaultwarden";
|
|
||||||
};
|
|
||||||
backend = {
|
|
||||||
# TODO: instead, we should generate target specific service https://hydra.nixos.org/build/203347995/download/2/manual/#idm140737322273072
|
|
||||||
servers = map (dist: {
|
|
||||||
name = "vaultwarden_${dist.properties.hostname}_1";
|
|
||||||
# TODO: should use the hostname
|
|
||||||
# address = "${dist.properties.hostname}:${builtins.toString ingress}";
|
|
||||||
address = "127.0.0.1:${builtins.toString ingress}";
|
|
||||||
resolvers = "default";
|
|
||||||
}) service;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
oauth2Proxy =
|
|
||||||
let
|
|
||||||
name = "${serviceName}Oauth2Proxy";
|
|
||||||
in customPkgs.mkOauth2Proxy {
|
|
||||||
inherit name;
|
|
||||||
serviceName = subdomain;
|
|
||||||
domain = utils.getDomain distribution name;
|
|
||||||
keycloakSubdomain = KeycloakService.subdomain;
|
|
||||||
keycloakDomain = utils.getDomain distribution "KeycloakService";
|
|
||||||
ingress = "127.0.0.1:${toString ssoIngress}";
|
|
||||||
egress = [ "http://127.0.0.1:${toString serviceIngress}" ];
|
|
||||||
realm = sso.realm;
|
|
||||||
allowed_roles = [ "user" "/admin|admin" ];
|
|
||||||
skip_auth_routes = [
|
|
||||||
"^/api"
|
|
||||||
"^/identity/connect/token"
|
|
||||||
"^/identity/accounts/prelogin"
|
|
||||||
];
|
|
||||||
inherit metricsPort;
|
|
||||||
keys = {
|
|
||||||
cookieSecret = "${serviceName}_oauth2proxy_cookiesecret";
|
|
||||||
clientSecret = "${serviceName}_oauth2proxy_clientsecret";
|
|
||||||
};
|
|
||||||
|
|
||||||
inherit distribution HaproxyService KeycloakService KeycloakCliService;
|
|
||||||
};
|
|
||||||
|
|
||||||
keycloakCliConfig = {
|
|
||||||
clients = {
|
|
||||||
vaultwarden = {
|
|
||||||
resourcesUris = {
|
|
||||||
adminPath = ["/admin/*"];
|
|
||||||
userPath = ["/*"];
|
|
||||||
};
|
|
||||||
access = {
|
|
||||||
admin = {
|
|
||||||
roles = [ "admin" ];
|
|
||||||
resources = [ "adminPath" ];
|
|
||||||
};
|
|
||||||
user = {
|
|
||||||
roles = [ "user" ];
|
|
||||||
resources = [ "userPath" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
deployKeys = domain: {
|
|
||||||
"${serviceName}_oauth2proxy_cookiesecret".text = ''
|
|
||||||
OAUTH2_PROXY_COOKIE_SECRET="${secret "${domain}/${subdomain}/${cookieSecretName}"}"
|
|
||||||
'';
|
|
||||||
"${serviceName}_oauth2proxy_clientsecret".text = ''
|
|
||||||
OAUTH2_PROXY_CLIENT_SECRET="${secret "${domain}/${subdomain}/${clientSecretName}"}"
|
|
||||||
'';
|
|
||||||
"${serviceName}_smtp_all".text = ''
|
|
||||||
SMTP_HOST="${secret "${domain}/mailgun.com/smtp_hostname"}"
|
|
||||||
SMTP_USERNAME="${secret "${domain}/mailgun.com/smtp_login"}"
|
|
||||||
SMTP_PASSWORD="${secret "${domain}/mailgun.com/password"}"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
smtp.keys.setup = "${serviceName}_smtp_all";
|
|
||||||
|
|
||||||
services = {
|
|
||||||
${db.name} = db;
|
|
||||||
${web.name} = web;
|
|
||||||
${service.name} = service;
|
|
||||||
${oauth2Proxy.name} = oauth2Proxy;
|
|
||||||
};
|
|
||||||
|
|
||||||
distribute = on: {
|
|
||||||
${db.name} = on;
|
|
||||||
${web.name} = on;
|
|
||||||
${service.name} = on;
|
|
||||||
${oauth2Proxy.name} = on;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, utils
|
|
||||||
}:
|
|
||||||
{ name
|
|
||||||
, path
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
inherit path;
|
|
||||||
|
|
||||||
pkg = stdenv.mkDerivation rec {
|
|
||||||
inherit name;
|
|
||||||
|
|
||||||
buildCommand =
|
|
||||||
let
|
|
||||||
dir = dirOf path;
|
|
||||||
base = baseNameOf path;
|
|
||||||
in ''
|
|
||||||
mkdir -p $out
|
|
||||||
ln -s ${pkgs.vaultwarden-vault}/share/vaultwarden/vault $out/${base}
|
|
||||||
|
|
||||||
echo "${dir}" > $out/.dysnomia-targetdir
|
|
||||||
|
|
||||||
cat > $out/.dysnomia-fileset <<FILESET
|
|
||||||
symlink $out/${base}
|
|
||||||
target .
|
|
||||||
FILESET
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
type = "fileset";
|
|
||||||
}
|
|
Loading…
Reference in a new issue