test(ntfy-sh): hack! direct import waiting for nix-pkgs
Some checks failed
Run pre-commit on all files / pre-commit (pull_request) Failing after 25s
Build all the nodes / ap01 (pull_request) Successful in 41s
Build all the nodes / netaccess01 (pull_request) Successful in 25s
Build all the nodes / netcore00 (pull_request) Successful in 24s
Build all the nodes / bridge01 (pull_request) Successful in 48s
Build all the nodes / netcore01 (pull_request) Successful in 25s
Build all the nodes / netcore02 (pull_request) Successful in 27s
Build all the nodes / geo01 (pull_request) Successful in 56s
Build all the nodes / geo02 (pull_request) Successful in 58s
Check meta / check_dns (pull_request) Successful in 16s
Build all the nodes / hypervisor02 (pull_request) Successful in 1m2s
Check meta / check_meta (pull_request) Successful in 18s
Build all the nodes / hypervisor03 (pull_request) Successful in 1m5s
Check workflows / check_workflows (pull_request) Successful in 20s
Build all the nodes / hypervisor01 (pull_request) Successful in 1m6s
Build all the nodes / cof02 (pull_request) Successful in 1m9s
Build all the nodes / lab-router01 (pull_request) Successful in 1m5s
Build all the nodes / build01 (pull_request) Successful in 1m13s
Build all the nodes / iso (pull_request) Successful in 1m11s
Run pre-commit on all files / pre-commit (push) Failing after 32s
Build the shell / build-shell (pull_request) Successful in 32s
Build all the nodes / compute01 (pull_request) Successful in 1m20s
Build all the nodes / tower01 (pull_request) Successful in 51s
Build all the nodes / rescue01 (pull_request) Successful in 58s
Build all the nodes / web02 (pull_request) Successful in 53s
Build all the nodes / vault01 (pull_request) Successful in 1m2s
Build all the nodes / krz01 (pull_request) Successful in 1m42s
Build all the nodes / web03 (pull_request) Successful in 1m2s
Build all the nodes / web01 (pull_request) Successful in 1m10s
Build all the nodes / storage01 (pull_request) Successful in 1m36s
Some checks failed
Run pre-commit on all files / pre-commit (pull_request) Failing after 25s
Build all the nodes / ap01 (pull_request) Successful in 41s
Build all the nodes / netaccess01 (pull_request) Successful in 25s
Build all the nodes / netcore00 (pull_request) Successful in 24s
Build all the nodes / bridge01 (pull_request) Successful in 48s
Build all the nodes / netcore01 (pull_request) Successful in 25s
Build all the nodes / netcore02 (pull_request) Successful in 27s
Build all the nodes / geo01 (pull_request) Successful in 56s
Build all the nodes / geo02 (pull_request) Successful in 58s
Check meta / check_dns (pull_request) Successful in 16s
Build all the nodes / hypervisor02 (pull_request) Successful in 1m2s
Check meta / check_meta (pull_request) Successful in 18s
Build all the nodes / hypervisor03 (pull_request) Successful in 1m5s
Check workflows / check_workflows (pull_request) Successful in 20s
Build all the nodes / hypervisor01 (pull_request) Successful in 1m6s
Build all the nodes / cof02 (pull_request) Successful in 1m9s
Build all the nodes / lab-router01 (pull_request) Successful in 1m5s
Build all the nodes / build01 (pull_request) Successful in 1m13s
Build all the nodes / iso (pull_request) Successful in 1m11s
Run pre-commit on all files / pre-commit (push) Failing after 32s
Build the shell / build-shell (pull_request) Successful in 32s
Build all the nodes / compute01 (pull_request) Successful in 1m20s
Build all the nodes / tower01 (pull_request) Successful in 51s
Build all the nodes / rescue01 (pull_request) Successful in 58s
Build all the nodes / web02 (pull_request) Successful in 53s
Build all the nodes / vault01 (pull_request) Successful in 1m2s
Build all the nodes / krz01 (pull_request) Successful in 1m42s
Build all the nodes / web03 (pull_request) Successful in 1m2s
Build all the nodes / web01 (pull_request) Successful in 1m10s
Build all the nodes / storage01 (pull_request) Successful in 1m36s
This commit is contained in:
parent
0af6150e4e
commit
7d917bbc30
3 changed files with 235 additions and 1 deletions
|
@ -39,6 +39,7 @@
|
|||
"extranix"
|
||||
"openbao"
|
||||
"forgejo-multiuser-nix-runners"
|
||||
"ntfy-sh"
|
||||
])
|
||||
++ [
|
||||
"${sources.agenix}/modules/age.nix"
|
||||
|
@ -55,7 +56,6 @@
|
|||
"services/systemd-notify"
|
||||
"services/victorialogs"
|
||||
"services/victoriametrics"
|
||||
"services/ntfy-sh"
|
||||
]
|
||||
++ nodeMeta.nix-modules
|
||||
));
|
||||
|
|
148
modules/nixos/ntfy-sh/default.nix
Normal file
148
modules/nixos/ntfy-sh/default.nix
Normal file
|
@ -0,0 +1,148 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib)
|
||||
mapAttrsToList
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
xor
|
||||
;
|
||||
inherit (lib.types)
|
||||
attrsOf
|
||||
enum
|
||||
listOf
|
||||
nullOr
|
||||
path
|
||||
str
|
||||
submodule
|
||||
;
|
||||
|
||||
cfg = config.services.ntfy-sh.accessControl;
|
||||
|
||||
inherit (config.services.ntfy-sh) settings;
|
||||
|
||||
acl_file = (pkgs.formats.json { }).generate "acl.json" { inherit (cfg) access users; };
|
||||
|
||||
ntfy-acl = pkgs.substituteAll {
|
||||
inherit (pkgs) python3;
|
||||
inherit acl_file;
|
||||
|
||||
user_db = settings.auth-file;
|
||||
|
||||
name = "ntfy-acl";
|
||||
src = ./ntfy-acl.py;
|
||||
dir = "bin";
|
||||
isExecutable = true;
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
options.services.ntfy-sh.accessControl = {
|
||||
enable = mkEnableOption "declarative management of users and acl for ntfy.sh";
|
||||
|
||||
users = mkOption {
|
||||
type = attrsOf (submodule {
|
||||
options = {
|
||||
role = mkOption {
|
||||
type = enum [
|
||||
"admin"
|
||||
"user"
|
||||
];
|
||||
description = "Role of the user.";
|
||||
default = "user";
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = nullOr path;
|
||||
description = ''
|
||||
Path to a file containing the password of the user.
|
||||
|
||||
Conflicts with `hashedPassword`.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
hashedPassword = mkOption {
|
||||
type = nullOr str;
|
||||
description = ''
|
||||
Hashed password of the user.
|
||||
|
||||
Conflicts with `passwordFile`.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
});
|
||||
description = ''
|
||||
Attribute set defining users of the ntfy.sh instance.
|
||||
'';
|
||||
default = { };
|
||||
};
|
||||
|
||||
access = mkOption {
|
||||
type = listOf (submodule {
|
||||
options = {
|
||||
username = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
A USERNAME is an existing user, as created with ntfy user add (see users and roles),
|
||||
or the anonymous user everyone or *, which represents clients that access the API
|
||||
without username/password.
|
||||
'';
|
||||
default = "*";
|
||||
};
|
||||
topic = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
A TOPIC is either a specific topic name (e.g. mytopic, or phil_alerts),
|
||||
or a wildcard pattern that matches any number of topics (e.g. alerts_* or ben-*).
|
||||
Only the wildcard character * is supported.
|
||||
It stands for zero to any number of characters.
|
||||
'';
|
||||
};
|
||||
permission = mkOption {
|
||||
type = enum [
|
||||
"rw"
|
||||
"ro"
|
||||
"wo"
|
||||
"none"
|
||||
];
|
||||
description = ''
|
||||
Permission for this access.
|
||||
- rw: Allows publishing messages to the given topic, as well as subscribing and reading messages
|
||||
- ro: Allows only subscribing and reading messages, but not publishing to the topic
|
||||
- wo: Allows only publishing to the topic, but not subscribing to it
|
||||
- none: Allows neither publishing nor subscribing to a topic
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
description = "List of access control rules.";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = mapAttrsToList (name: user: {
|
||||
assertion = xor (user.hashedPassword != null) (user.passwordFile != null);
|
||||
message = ''
|
||||
Exactly one of `services.ntfy-sh.accessControl.users.<name>.hashedPassword`
|
||||
and `services.ntfy-sh.accessControl.users.<name>.passwordFile`
|
||||
is required for `name` = `${name}`.
|
||||
'';
|
||||
}) cfg.users;
|
||||
|
||||
services.ntfy-sh.settings = {
|
||||
enable-signup = false;
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"f /var/lib/ntfy-sh/.acl-path 0600 ${config.services.ntfy-sh.user} ${config.services.ntfy-sh.group} - -"
|
||||
];
|
||||
systemd.services.ntfy-sh.preStart = "${ntfy-acl}/bin/ntfy-acl";
|
||||
};
|
||||
}
|
86
modules/nixos/ntfy-sh/ntfy-acl.py
Normal file
86
modules/nixos/ntfy-sh/ntfy-acl.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
#!@python3@/bin/python
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import subprocess
|
||||
|
||||
|
||||
def ntfy(*args: str, env=None):
|
||||
subprocess.run(["ntfy"] + list(args), env=env).check_returncode()
|
||||
|
||||
|
||||
def create_user(u: str, role: str, passwordFile: str, hashedPassword: str):
|
||||
# Create the user with the required role and password
|
||||
if passwordFile != None:
|
||||
with open(passwordFile) as pwd_fp:
|
||||
env = {"NTFY_PASSWORD": pwd_fp.read().strip()}
|
||||
|
||||
ntfy("user", "add", f"--role={role}", u, env=env)
|
||||
else:
|
||||
env = {"NTFY_PASSWORD": hashedPassword}
|
||||
|
||||
ntfy("user", "add", f"--role={role}", u, env=env)
|
||||
# HACK: add does not supports hashedPassword entry
|
||||
ntfy("user", "change-pass-hash", u, env=env)
|
||||
|
||||
def update_user(u: str, role: str, passwordFile: str, hashedPassword: str):
|
||||
# Update the user with the required role and password
|
||||
if passwordFile != None:
|
||||
with open(passwordFile) as pwd_fp:
|
||||
env = {"NTFY_PASSWORD": pwd_fp.read().strip()}
|
||||
|
||||
ntfy("user", "change-pass", u, env=env)
|
||||
else:
|
||||
env = {"NTFY_PASSWORD": hashedPassword}
|
||||
|
||||
ntfy("user", "change-pass-hash", u, env=env)
|
||||
|
||||
ntfy("user", "change-role", u, role)
|
||||
|
||||
|
||||
# Compare the ACL file path to the one used to get the actual data
|
||||
try:
|
||||
with open("/var/lib/ntfy-sh/.acl-path") as acl_path_fp:
|
||||
acl_path: str = acl_path_fp.read().strip()
|
||||
except OSError:
|
||||
print("[!] Cannot open .acl-path")
|
||||
exit(1)
|
||||
|
||||
if acl_path == "@acl_file@":
|
||||
print("[-] Unchanged ACL file, exiting")
|
||||
exit(0)
|
||||
else:
|
||||
print("[+] ACL file has changed, updating data")
|
||||
|
||||
# Get the wanted state
|
||||
with open("@acl_file@") as acl_fp:
|
||||
acl_data = json.load(acl_fp)
|
||||
|
||||
# Connect to the db to recover the list of current users
|
||||
with sqlite3.connect("@user_db@") as con:
|
||||
c = con.cursor()
|
||||
existing_users: set[str] = set(c.execute("SELECT user FROM user")) - {"*"}
|
||||
|
||||
wanted_users: set[str] = set(acl_data["users"].keys())
|
||||
|
||||
# Delete extraneous users
|
||||
for user in existing_users - wanted_users:
|
||||
ntfy("user", "del", user)
|
||||
|
||||
# Create new users
|
||||
for user in wanted_users - existing_users:
|
||||
create_user(user, **acl_data["users"][user])
|
||||
|
||||
# Update existing users
|
||||
for user in existing_users & wanted_users:
|
||||
update_user(user, **acl_data["users"][user])
|
||||
|
||||
# Reset ACL rules
|
||||
ntfy("access", "--reset")
|
||||
|
||||
for rule in acl_data["access"]:
|
||||
ntfy("access", rule["user"], rule["topic"], rule["permission"])
|
||||
|
||||
# Write the new ACL file path
|
||||
with open("/var/lib/ntfy-sh/.acl-path", "w") as f:
|
||||
f.write("@acl_file@")
|
Loading…
Add table
Add a link
Reference in a new issue