Compare commits
No commits in common. "main" and "proxy_images" have entirely different histories.
main
...
proxy_imag
81 changed files with 188 additions and 29784 deletions
|
@ -6,7 +6,6 @@
|
|||
./../shared/applications/server/nginx.nix
|
||||
./../shared/applications/server/postgresql.nix # INCLUDES DATABASE BACKUPS
|
||||
./../shared/applications/server/restic.nix # EXTERNAL BACKUP
|
||||
# ./../shared/applications/server/podman.nix
|
||||
./../shared/applications/state/postgresql.nix
|
||||
./../shared/applications/state/ssh.nix
|
||||
|
||||
|
@ -19,20 +18,14 @@
|
|||
./gerd/services/murmur.nix
|
||||
./gerd/services/hedgedoc.nix
|
||||
./gerd/services/cyberchef.nix
|
||||
./gerd/services/nextcloud.nix
|
||||
./gerd/services/nextcloud.nix
|
||||
./gerd/services/stalwart
|
||||
./gerd/services/wger
|
||||
./gerd/services/searx.nix
|
||||
./gerd/services/miniflux.nix
|
||||
./gerd/services/matrix
|
||||
./gerd/services/uptime-kuma.nix
|
||||
./gerd/services/rallly
|
||||
./gerd/services/notify
|
||||
./gerd/services/drasl.nix
|
||||
./gerd/services/drtvrss.nix
|
||||
./gerd/services/vikunja.nix
|
||||
|
||||
./gerd/services/monitoring
|
||||
./gerd/services/element.nix
|
||||
./gerd/services/matrix-synapse.nix
|
||||
];
|
||||
|
||||
networking.hostName = "gerd";
|
||||
|
@ -43,15 +36,12 @@
|
|||
disks = {
|
||||
disk = "/dev/sda";
|
||||
pools.rpool.datasets = {
|
||||
# zfs create -o quota=1G rpool/safe/svcs/service-name
|
||||
"safe/svcs/forgejo" = { mountpoint = "/srv/forgejo"; extra.options.quota = "5G"; };
|
||||
"safe/svcs/hedgedoc" = { mountpoint = "/srv/hedgedoc"; extra.options.quota = "5G"; };
|
||||
"safe/svcs/nextcloud" = { mountpoint = "/srv/nextcloud"; extra.options.quota = "5G"; };
|
||||
"safe/svcs/stalwart" = { mountpoint = "/srv/stalwart"; extra.options.quota = "5G"; };
|
||||
"safe/svcs/synapse" = { mountpoint = "/srv/synapse"; extra.options.quota = "5G"; };
|
||||
"safe/svcs/wger" = { mountpoint = "/srv/wger"; extra.options.quota = "5G"; };
|
||||
"safe/svcs/prometheus" = { mountpoint = "/srv/prometheus"; extra.options.quota = "5G"; };
|
||||
|
||||
"safe/svcs/postgresql" = { mountpoint = "/srv/postgresql"; extra.options.quota = "5G"; };
|
||||
"backup/postgresql" = { mountpoint = "/media/backup/postgresqlbackup"; extra.options.quota = "5G"; };
|
||||
};
|
||||
|
@ -64,7 +54,7 @@
|
|||
platforms.hetzner = {
|
||||
enable = true;
|
||||
network.address = [
|
||||
"65.108.221.240"
|
||||
"65.108.221.240/32"
|
||||
"2a01:4f9:c012:743e::1/64"
|
||||
];
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{ pkgs, config, ... }:
|
||||
{ config, ... }:
|
||||
|
||||
let
|
||||
svc_domain = "auth.${config.mine.shared.settings.domain}";
|
||||
|
@ -10,14 +10,6 @@ let
|
|||
in {
|
||||
services.authelia.instances.main = {
|
||||
enable = true;
|
||||
package = pkgs.authelia.override {
|
||||
authelia-web = pkgs.authelia.passthru.web.overrideAttrs (old: {
|
||||
postPatch = old.postPatch + ''
|
||||
substituteInPlace src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx \
|
||||
--replace-fail "const [rememberMe, setRememberMe] = useState(false)" "const [rememberMe, setRememberMe] = useState(true)"
|
||||
'';
|
||||
});
|
||||
};
|
||||
|
||||
environmentVariables.AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = config.age.secrets.lldap-bind-user-pass.path;
|
||||
environmentVariables.AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = config.age.secrets.authelia-smtp-password.path;
|
||||
|
@ -34,10 +26,6 @@ in {
|
|||
authelia_url = "https://${svc_domain}";
|
||||
} ];
|
||||
|
||||
# setup redis for sessions, otherwise it's in-memory, and everyone
|
||||
# has to login again each time authelia is restarted
|
||||
session.redis.host = "${config.services.redis.servers.authelia.unixSocket}";
|
||||
|
||||
server.address = "tcp://127.0.0.1:${builtins.toString port}";
|
||||
|
||||
# totp - disable for now, as it requires email server
|
||||
|
@ -93,18 +81,6 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
# setup redis for persisting session
|
||||
# across reboots
|
||||
services.redis.servers.authelia = {
|
||||
enable = true;
|
||||
user = authelia_user;
|
||||
};
|
||||
|
||||
# setup lldap user for authelia that can send emails
|
||||
services.lldap.provision.users = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
authelia = llib.mkProvisionUserSystem "authelia" config.age.secrets.authelia-smtp-password.path;
|
||||
});
|
||||
|
||||
services.nginx.virtualHosts."${svc_domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
|
|
@ -3,7 +3,4 @@
|
|||
./authelia.nix
|
||||
./authelia-nginx.nix
|
||||
];
|
||||
|
||||
# generate new authelia client secret like so
|
||||
# authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986
|
||||
}
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
sources = import ./../../../shared/sources;
|
||||
|
||||
flake-compat = sources.flake-compat;
|
||||
drasl = import flake-compat { src = sources.drasl; };
|
||||
|
||||
svc_domain = "drasl.${config.mine.shared.settings.domain}";
|
||||
port = 25585;
|
||||
|
||||
draslOIDCName = "Authelia";
|
||||
in {
|
||||
imports = [
|
||||
drasl.defaultNix.nixosModules.drasl
|
||||
];
|
||||
|
||||
services.drasl = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
ApplicationOwner = config.mine.shared.settings.brand;
|
||||
Domain = svc_domain;
|
||||
BaseURL = "https://${svc_domain}";
|
||||
|
||||
ListenAddress = "localhost:${builtins.toString port}";
|
||||
|
||||
# all ldap admins in group `drasl-admin` are default admins here
|
||||
DefaultAdmins = config.mine.shared.lib.ldap.mkScope (lconfig: llib: let
|
||||
admins = lib.forEach (
|
||||
lib.filter
|
||||
(v: lib.elem lconfig.groups.drasl_admin (v.groups or []))
|
||||
(lib.attrValues lconfig.provision.users)
|
||||
) (v: v.mail);
|
||||
in admins);
|
||||
|
||||
# allow importing players
|
||||
ImportExistingPlayer = {
|
||||
Allow = true;
|
||||
Nickname = "Mojang";
|
||||
AccountURL = "https://api.mojang.com";
|
||||
SessionURL = "https://sessionserver.mojang.com";
|
||||
SetSkinURL = "https://www.minecraft.net/msaprofile/mygames/editskin";
|
||||
RequireSkinVerification = false; # TODO: should maybe be changed to true in the future
|
||||
};
|
||||
|
||||
RegistrationExistingPlayer.Allow = true;
|
||||
|
||||
# only allow loging using OIDC
|
||||
CreateNewPlayer.Allow = true;
|
||||
RegistrationNewPlayer.Allow = true;
|
||||
AllowPasswordLogin = false;
|
||||
|
||||
# configure OIDC
|
||||
RegistrationOIDC = [{
|
||||
Name = draslOIDCName;
|
||||
Issuer = "https://${config.mine.shared.settings.authelia.domain}";
|
||||
ClientID = "drasl";
|
||||
# ClientSecret = "<gotten-from-env>";
|
||||
PKCE = true;
|
||||
RequireInvite = false;
|
||||
AllowChoosingPlayerName = true;
|
||||
}];
|
||||
};
|
||||
};
|
||||
|
||||
# secrets
|
||||
systemd.services.drasl.serviceConfig.EnvironmentFile = config.age.secrets.drasl-env.path;
|
||||
systemd.services.drasl.restartTriggers = [ config.age.secrets.drasl-env.path ]; # unsure if this works
|
||||
|
||||
# setup for oidc
|
||||
services.authelia.instances.main.settings.identity_providers.oidc.clients = [{
|
||||
client_id = "drasl";
|
||||
client_name = "Drasl";
|
||||
client_secret = "$pbkdf2-sha512$310000$x8USzEVE/HW7/tiYtgTFaA$POg.0gZuWfHTuO0Z2Dd1GZ.T2813IAG.nWnwOarHGBz7aCGI1rdRoaS7gZ9V6bnTWWiFL/lqk5NFoqdZn94neg";
|
||||
consent_mode = "implicit";
|
||||
redirect_uris = [ "${config.services.drasl.settings.BaseURL}/web/oidc-callback/${draslOIDCName}" ];
|
||||
scopes = [
|
||||
"openid"
|
||||
"profile"
|
||||
"email"
|
||||
];
|
||||
}];
|
||||
|
||||
# nginx
|
||||
services.nginx.virtualHosts."${svc_domain}" = let
|
||||
httpListenOn = "http://localhost:${builtins.toString port}";
|
||||
in config.mine.shared.lib.authelia.mkProtectedWebsite {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/" = config.mine.shared.lib.authelia.mkProtectedLocation {
|
||||
proxyPass = httpListenOn;
|
||||
};
|
||||
|
||||
# needed for clients to auth
|
||||
locations."/authlib-injector".proxyPass = httpListenOn;
|
||||
|
||||
# needed for server to auth
|
||||
locations."/auth".proxyPass = httpListenOn;
|
||||
locations."/account".proxyPass = httpListenOn;
|
||||
locations."/session".proxyPass = httpListenOn;
|
||||
locations."/services".proxyPass = httpListenOn;
|
||||
|
||||
# skins
|
||||
locations."/web/texture".proxyPass = httpListenOn;
|
||||
};
|
||||
|
||||
# persistence
|
||||
environment.persistence.root.directories = [
|
||||
{ directory = "/var/lib/private/drasl"; mode = "0700"; }
|
||||
];
|
||||
|
||||
# meta
|
||||
mine.shared.meta.drasl = rec {
|
||||
name = "Drasl";
|
||||
description = ''
|
||||
Yggdrasil-compatible API server for Minecraft, which can be used instead of the official Minecraft authentication server.
|
||||
This means that we do not require Mojangs servers, to authenticate with any server managed by ${config.mine.shared.settings.brand}.
|
||||
|
||||
It is possible to login with OIDC on Drasl, and then import your Mojang player into Drasl.
|
||||
'';
|
||||
url = "https://${svc_domain}";
|
||||
|
||||
package = let
|
||||
pkg = config.services.drasl.package;
|
||||
in {
|
||||
name = pkg.pname;
|
||||
version = pkg.version;
|
||||
meta = with lib; {
|
||||
description = "Yggdrasil-compatible API server for Minecraft";
|
||||
license = lib.licenses.gpl3Only;
|
||||
homepage = "https://github.com/unmojang/drasl";
|
||||
platforms = platforms.all;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# TODO(eyJhb): this should not be placed here
|
||||
mine.shared.meta.minecraft = rec {
|
||||
name = "Minecraft";
|
||||
description = ''We're running a vanilla Minecraft hosted externally by a member'';
|
||||
url = "mcvanilla.${config.mine.shared.settings.domain}";
|
||||
|
||||
package = let
|
||||
pkg = pkgs.minecraft-server;
|
||||
in {
|
||||
name = pkg.pname;
|
||||
version = "1.21.5";
|
||||
meta = pkg.meta;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
sources = import ./../../../shared/sources;
|
||||
|
||||
flake-compat = sources.flake-compat;
|
||||
drtvrss = import flake-compat { src = sources.drtvrss; };
|
||||
|
||||
svc_domain = "drtv.${config.mine.shared.settings.domain}";
|
||||
|
||||
port = 8125;
|
||||
in {
|
||||
imports = [
|
||||
# (builtins.trace drtvrss.defaultNix.nixosModules.x86_64-linux.default drtvrss.defaultNix.nixosModules.default)
|
||||
drtvrss.defaultNix.nixosModules.x86_64-linux.default
|
||||
];
|
||||
|
||||
services.drtvrss = {
|
||||
enable = true;
|
||||
host = "127.0.0.1:${builtins.toString port}";
|
||||
base_url = "https://${svc_domain}";
|
||||
klagemail = "rasmus@rend.al";
|
||||
recommended_shows = [ "matador_130149" ];
|
||||
};
|
||||
|
||||
systemd.services.drtvrss.confinement.enable = lib.mkForce false;
|
||||
|
||||
# nginx
|
||||
services.nginx.virtualHosts."${svc_domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/".proxyPass = "http://localhost:${builtins.toString port}";
|
||||
};
|
||||
|
||||
# meta
|
||||
mine.shared.meta.drtvrss = rec {
|
||||
name = "DRTV RSS";
|
||||
description = ''
|
||||
Alternative frontend for DRTV, whithout the requirement to login + the ability to generate RSS feeds of your favorite shows.
|
||||
'';
|
||||
url = "https://${svc_domain}";
|
||||
|
||||
package = {
|
||||
name = "drtvrss";
|
||||
version = "v0.0.1";
|
||||
meta = with lib; {
|
||||
description = "DRTVRSS";
|
||||
license = licenses.agpl3Only;
|
||||
homepage = "https://github.com/RasmusRendal/drtvrss";
|
||||
platforms = platforms.all;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -99,7 +99,6 @@ in {
|
|||
client_id = "forgejo";
|
||||
client_name = "Forgejo";
|
||||
client_secret = "$pbkdf2-sha512$310000$cOGtLwMHyfugAJCIiUUjfQ$ao7zC8QB1m8aTGNf1dxYbRAPivZ0G1eaJ4bNFVfJiTFZX06U5baBjT0emvoaeFHXMFbYHzorb2/8vxnY/D0b5Q";
|
||||
consent_mode = "implicit";
|
||||
redirect_uris = [ "https://${config.mine.shared.settings.forgejo.domain}/user/oauth2/${AUTHELIA_AUTH_NAME}/callback" ];
|
||||
scopes = [
|
||||
"openid"
|
||||
|
|
|
@ -5,7 +5,7 @@ import gql
|
|||
from gql.dsl import DSLQuery, DSLSchema, dsl_gql, DSLMutation
|
||||
from gql.transport.aiohttp import AIOHTTPTransport
|
||||
|
||||
from .utils import to_camelcase, to_snakecase, get_value
|
||||
from .utils import to_camelcase, to_snakecase
|
||||
|
||||
import logging
|
||||
|
||||
|
@ -100,11 +100,10 @@ class LLDAPGroups:
|
|||
def update(self, groupId: int, attrs: dict[str, str | list[str]]):
|
||||
insertAttributes: list[dict[str, str | list[str]]] = []
|
||||
for k, v in attrs.items():
|
||||
v = get_value(v)
|
||||
if isinstance(v, str):
|
||||
v = [v]
|
||||
|
||||
insertAttributes.append({"name": k, "value": v})
|
||||
insertAttributes.append({"name": to_snakecase(k), "value": v})
|
||||
|
||||
ds = DSLSchema(self._client.schema)
|
||||
query = dsl_gql(
|
|
@ -3,10 +3,8 @@
|
|||
|
||||
from typing import Any
|
||||
import subprocess
|
||||
import requests
|
||||
import secrets
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
import gql
|
||||
|
@ -22,25 +20,15 @@ from .attributes import LLDAPAttributes
|
|||
import logging
|
||||
|
||||
logging.basicConfig()
|
||||
logging.getLogger("bootstrap").setLevel(logging.DEBUG)
|
||||
logging.getLogger("lldapbootstrap").setLevel(logging.DEBUG)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
class LLDAP:
|
||||
def __init__(
|
||||
self,
|
||||
server_url: str,
|
||||
username: str,
|
||||
password: str,
|
||||
):
|
||||
if password.startswith("file:"):
|
||||
password = open(password[5:], "r").read().strip()
|
||||
|
||||
def __init__(self, server_url: str, auth_token: str):
|
||||
self._server_url: str = server_url
|
||||
|
||||
self._server_refresh_token: str | None = None
|
||||
self._simple_login(username, password)
|
||||
self._server_auth_token: str = auth_token
|
||||
|
||||
self._client: gql.Client = self._init_gql_client()
|
||||
|
||||
|
@ -49,35 +37,6 @@ class LLDAP:
|
|||
self._attrsUser = LLDAPAttributes(self._client, using_user_attributes=True)
|
||||
self._attrsGroup = LLDAPAttributes(self._client, using_group_attributes=True)
|
||||
|
||||
def _simple_login(self, username: str, password: str):
|
||||
r = requests.post(
|
||||
f"{self._server_url}/auth/simple/login",
|
||||
headers={"content-type": "application/json"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise Exception(f"failed to signin got response: {r.text}")
|
||||
|
||||
rjson = r.json()
|
||||
self._server_auth_token = rjson["token"]
|
||||
self._server_refresh_token = rjson["refreshToken"]
|
||||
|
||||
def _logout(self):
|
||||
if not self._server_refresh_token:
|
||||
return
|
||||
|
||||
r = requests.get(
|
||||
f"{self._server_url}/auth/logout",
|
||||
headers={"refresh-token": self._server_refresh_token},
|
||||
)
|
||||
print(r.text, r.status_code)
|
||||
|
||||
def _init_gql_client(self) -> gql.Client:
|
||||
# Select your transport with a defined url endpoint
|
||||
transport = AIOHTTPTransport(
|
||||
|
@ -310,8 +269,8 @@ class LLDAP:
|
|||
]
|
||||
)
|
||||
|
||||
def run(self, provision_file_path: str):
|
||||
data = json.load(open(provision_file_path, "r"))
|
||||
def run(self):
|
||||
data = json.load(open("test2.json", "r"))
|
||||
|
||||
self._run_ensure_attrs_user(self._attrsUser, data["user_attributes"])
|
||||
self._run_ensure_attrs_user(self._attrsGroup, data["group_attributes"])
|
||||
|
@ -320,26 +279,9 @@ class LLDAP:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
url = os.getenv("LLDAP_URL")
|
||||
if not url:
|
||||
raise Exception("No LLDAP_URL provided. please set")
|
||||
auth_token = os.getenv("LLDAP_TOKEN")
|
||||
if not auth_token:
|
||||
raise Exception("No LLDAP_TOKEN provided. please set")
|
||||
|
||||
auth_username = os.getenv("LLDAP_USERNAME")
|
||||
if not auth_username:
|
||||
raise Exception("No LLDAP_USERNAME provided. please set")
|
||||
|
||||
auth_password = os.getenv("LLDAP_PASSWORD")
|
||||
if not auth_password:
|
||||
raise Exception("No LLDAP_PASSWORD provided. please set")
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
raise Exception(
|
||||
"Please provide a JSON file containing the provisioning details"
|
||||
)
|
||||
|
||||
x = LLDAP(url, auth_username, auth_password)
|
||||
|
||||
try:
|
||||
x.run(sys.argv[1])
|
||||
finally:
|
||||
x._logout()
|
||||
x = LLDAP("https://ldap.fricloud.dk", auth_token)
|
||||
x.run()
|
|
@ -5,7 +5,7 @@ import gql
|
|||
from gql.dsl import DSLQuery, DSLSchema, dsl_gql, DSLMutation
|
||||
from gql.transport.aiohttp import AIOHTTPTransport
|
||||
|
||||
from .utils import to_camelcase, to_snakecase, get_value
|
||||
from .utils import to_camelcase, to_snakecase
|
||||
|
||||
import logging
|
||||
|
||||
|
@ -91,11 +91,10 @@ class LLDAPUsers:
|
|||
def update(self, userId: str, attrs: dict[str, str | list[str]]):
|
||||
insertAttributes: list[dict[str, str | list[str]]] = []
|
||||
for k, v in attrs.items():
|
||||
v = get_value(v)
|
||||
if isinstance(v, str):
|
||||
v = [v]
|
||||
|
||||
insertAttributes.append({"name": k, "value": v})
|
||||
insertAttributes.append({"name": to_snakecase(k), "value": v})
|
||||
|
||||
ds = DSLSchema(self._client.schema)
|
||||
query = dsl_gql(
|
10
machines/gerd/services/lldap/bootstrap/utils.py
Normal file
10
machines/gerd/services/lldap/bootstrap/utils.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import re
|
||||
|
||||
|
||||
def to_camelcase(s):
|
||||
s = re.sub(r"(_|-)+", " ", s).title().replace(" ", "").replace("*", "")
|
||||
return "".join([s[0].lower(), s[1:]])
|
||||
|
||||
|
||||
def to_snakecase(s):
|
||||
return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
|
|
@ -19,90 +19,8 @@ index 6f42473..b3746a1 100644
|
|||
&config,
|
||||
'';
|
||||
|
||||
whoamiPatch = pkgs.writeText "lldap-whoami.patch" ''
|
||||
diff --git a/server/src/infra/ldap_handler.rs b/server/src/infra/ldap_handler.rs
|
||||
index 7257c31..feda03c 100644
|
||||
--- a/server/src/infra/ldap_handler.rs
|
||||
+++ b/server/src/infra/ldap_handler.rs
|
||||
@@ -26,7 +26,7 @@ use ldap3_proto::proto::{
|
||||
LdapDerefAliases, LdapExtendedRequest, LdapExtendedResponse, LdapFilter, LdapModify,
|
||||
LdapModifyRequest, LdapModifyType, LdapOp, LdapPartialAttribute, LdapPasswordModifyRequest,
|
||||
LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, LdapSearchResultEntry,
|
||||
- LdapSearchScope,
|
||||
+ LdapSearchScope, OID_PASSWORD_MODIFY, OID_WHOAMI,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use tracing::{debug, instrument, warn};
|
||||
@@ -181,7 +181,7 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
|
||||
LdapPartialAttribute {
|
||||
atype: "supportedExtension".to_string(),
|
||||
// Password modification extension.
|
||||
- vals: vec![b"1.3.6.1.4.1.4203.1.11.1".to_vec()],
|
||||
+ vals: vec![OID_PASSWORD_MODIFY.as_bytes().to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "supportedControl".to_string(),
|
||||
@@ -204,6 +204,11 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
|
||||
atype: "isGlobalCatalogReady".to_string(),
|
||||
vals: vec![b"false".to_vec()],
|
||||
},
|
||||
+ LdapPartialAttribute {
|
||||
+ atype: "supportedExtension".to_string(),
|
||||
+ // whoami extension.
|
||||
+ vals: vec![OID_WHOAMI.as_bytes().to_vec()],
|
||||
+ },
|
||||
],
|
||||
})
|
||||
}
|
||||
@@ -413,16 +418,33 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
async fn do_extended_request(&mut self, request: &LdapExtendedRequest) -> Vec<LdapOp> {
|
||||
- match LdapPasswordModifyRequest::try_from(request) {
|
||||
- Ok(password_request) => self
|
||||
+ if let Ok(password_request) = LdapPasswordModifyRequest::try_from(request) {
|
||||
+ return self
|
||||
.do_password_modification(&password_request)
|
||||
.await
|
||||
- .unwrap_or_else(|e: LdapError| vec![make_extended_response(e.code, e.message)]),
|
||||
- Err(_) => vec![make_extended_response(
|
||||
- LdapResultCode::UnwillingToPerform,
|
||||
- format!("Unsupported extended operation: {}", &request.name),
|
||||
- )],
|
||||
+ .unwrap_or_else(|e: LdapError| vec![make_extended_response(e.code, e.message)]);
|
||||
}
|
||||
+
|
||||
+ if request.name == OID_WHOAMI {
|
||||
+ let dn = self
|
||||
+ .user_info
|
||||
+ .as_ref()
|
||||
+ .map(|user_info| {
|
||||
+ format!(
|
||||
+ "dn:uid={},ou=people,{}",
|
||||
+ user_info.user.clone().into_string(),
|
||||
+ self.ldap_info.base_dn_str
|
||||
+ )
|
||||
+ })
|
||||
+ .unwrap_or_default();
|
||||
+
|
||||
+ return vec![make_extended_response(LdapResultCode::Success, dn)];
|
||||
+ }
|
||||
+
|
||||
+ return vec![make_extended_response(
|
||||
+ LdapResultCode::UnwillingToPerform,
|
||||
+ format!("Unsupported extended operation: {}", &request.name),
|
||||
+ )];
|
||||
}
|
||||
|
||||
async fn handle_modify_change(
|
||||
'';
|
||||
|
||||
pkgLLDAPCli = pkgs.callPackage ./../../../../shared/pkgs/lldap-cli.nix {};
|
||||
in {
|
||||
imports = [
|
||||
./provision.nix
|
||||
];
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgLLDAPCli
|
||||
];
|
||||
|
@ -111,7 +29,7 @@ in {
|
|||
enable = true;
|
||||
|
||||
package = pkgs.lldap.overrideAttrs (old: {
|
||||
patches = old.patches ++ [ resetPasswordStartPatch whoamiPatch ];
|
||||
patches = old.patches ++ [ resetPasswordStartPatch ];
|
||||
});
|
||||
|
||||
settings = {
|
||||
|
@ -176,44 +94,29 @@ in {
|
|||
groupOfUniqueNames = "groupOfUniqueNames";
|
||||
};
|
||||
|
||||
provision = config.services.lldap.provision;
|
||||
|
||||
users = {
|
||||
admin = "admin";
|
||||
# bind = "bind_user";
|
||||
} // (lib.mapAttrs (n: v: v.user_id) config.services.lldap.provision.users);
|
||||
bind = "bind_user";
|
||||
};
|
||||
|
||||
groups = {
|
||||
admin = "lldap_admin";
|
||||
member = "base_member";
|
||||
password_manager = "lldap_password_manager";
|
||||
strict_readonly = "lldap_strict_readonly";
|
||||
# system = "system_service";
|
||||
# system_email = "system_email";
|
||||
} // (lib.mapAttrs (n: v: v.display_name) config.services.lldap.provision.groups);
|
||||
};
|
||||
|
||||
ou = {
|
||||
groups = "groups";
|
||||
users = "people";
|
||||
};
|
||||
|
||||
attr = let
|
||||
toCamelCase = w: let
|
||||
parts = lib.splitString "_" w;
|
||||
cap = lib.imap0 (i: v: if i == 0 then v else (lib.toUpper (lib.substring 0 1 v)) + (lib.substring 1 (lib.stringLength v) v));
|
||||
in lib.concatStrings (cap parts);
|
||||
in {
|
||||
attr = {
|
||||
uid = "uid";
|
||||
firstname = "givenName";
|
||||
lastname = "sn";
|
||||
email = "mail";
|
||||
avatar = "jpegPhoto";
|
||||
groupname = "cn";
|
||||
|
||||
# custom
|
||||
# member_email = "member_email";
|
||||
# mail_disk_quota = "mail_disk_quota";
|
||||
} // (lib.mapAttrs (n: v: toCamelCase v.name) config.services.lldap.provision.user_attributes);
|
||||
};
|
||||
|
||||
age_secret = config.age.secrets.lldap-bind-user-pass.path;
|
||||
};
|
||||
|
@ -243,45 +146,6 @@ in {
|
|||
|
||||
mkAnd = v: { type = "and"; values = v; };
|
||||
mkOr = v: { type = "or"; values = v; };
|
||||
|
||||
# mkProvision helpers for creating users
|
||||
mkProvisionEmail = name: "${name}@${config.mine.shared.settings.domain}";
|
||||
mkProvisionUserNormal = name: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
user_id = name;
|
||||
display_name = name; # required for nextcloud
|
||||
membermail = mkProvisionEmail name;
|
||||
mail = "env:EMAIL_${lib.toUpper name}";
|
||||
groups = [ lconfig.groups.member ];
|
||||
membermaildiskquota = 100*1024*1024; # mb
|
||||
nextcloudquota = 5*1024*1024; # mb
|
||||
});
|
||||
|
||||
mkProvisionUserSystem = name: password_file: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
user_id = name;
|
||||
membermail = mkProvisionEmail name;
|
||||
mail = mkProvisionEmail name;
|
||||
password = "file:${password_file}";
|
||||
groups = [ lconfig.groups.system_mail lconfig.groups.system_service ];
|
||||
membermaildiskquota = 10*1024*1024; # mb
|
||||
});
|
||||
|
||||
mkProvisionUserSystemExt = name: password_file: custom_attrs: lib.recursiveUpdate (config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
user_id = name;
|
||||
membermail = mkProvisionEmail name;
|
||||
password = "file:${password_file}";
|
||||
groups = [ lconfig.groups.system_mail lconfig.groups.system_service ];
|
||||
membermaildiskquota = 10*1024*1024; # mb
|
||||
})) custom_attrs;
|
||||
|
||||
mkProvisionUserAdmin = name: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
user_id = name;
|
||||
display_name = name; # required for nextcloud
|
||||
membermail = mkProvisionEmail name;
|
||||
mail = mkProvisionEmail name;
|
||||
groups = with lconfig.groups; [ admin nextcloud_admin grafana_admin drasl_admin member ];
|
||||
membermaildiskquota = 100*1024*1024; # mb
|
||||
nextcloudquota = 100*1024*1024; # mb
|
||||
});
|
||||
};
|
||||
|
||||
mine.shared.meta.lldap = {
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import logging
|
||||
import re
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def to_camelcase(s):
|
||||
s = re.sub(r"(_|-)+", " ", s).title().replace(" ", "").replace("*", "")
|
||||
return "".join([s[0].lower(), s[1:]])
|
||||
|
||||
|
||||
def to_snakecase(s):
|
||||
return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
|
||||
|
||||
|
||||
def get_value(s):
|
||||
if not isinstance(s, str):
|
||||
return s
|
||||
|
||||
if s.startswith("file:"):
|
||||
filepath = s[len("file:") :]
|
||||
logger.debug(f"reading from file '{filepath}'")
|
||||
return open(filepath, "r").read().strip()
|
||||
|
||||
if s.startswith("env:"):
|
||||
envkey = s.strip()[len("env:") :]
|
||||
logger.debug(f"reading from envvar '{envkey}'")
|
||||
return os.getenv(envkey)
|
||||
|
||||
logger.debug(f"using original value")
|
||||
return s
|
|
@ -1,167 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) types;
|
||||
|
||||
cfg = config.services.lldap;
|
||||
format = pkgs.formats.json { };
|
||||
|
||||
# helpers
|
||||
_configFile = {
|
||||
user_attributes = lib.mapAttrsToList (n: v: v) cfg.provision.user_attributes;
|
||||
group_attributes = lib.mapAttrsToList (n: v: v) cfg.provision.group_attributes;
|
||||
users = lib.mapAttrsToList (n: v: v) cfg.provision.users;
|
||||
groups = lib.mapAttrsToList (n: v: v) cfg.provision.groups;
|
||||
# users = lib.mapAttrsToList (n: v: v // {
|
||||
# user_id = if v ? user_id then v.user_id else n;
|
||||
# }) cfg.users;
|
||||
# groups = lib.mapAttrsToList (n: v: v // {
|
||||
# display_name = if v ? display_name then v.display_name else n;
|
||||
# }) cfg.groups;
|
||||
};
|
||||
configFile = format.generate "lldap-declarative.json" _configFile;
|
||||
|
||||
# opts
|
||||
optsAttributes = { name, config, ... }: {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "The name of the attribute";
|
||||
};
|
||||
|
||||
attributeType = lib.mkOption {
|
||||
type = types.enum [ "STRING" "INTEGER" "JPEG_PHOTO" "DATE_TIME" ];
|
||||
description = "Type of the attribute";
|
||||
};
|
||||
|
||||
isList = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Is this attribute a list (multiple values for this attribute)";
|
||||
};
|
||||
|
||||
isEditable = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Should the user be able to edit this value?";
|
||||
};
|
||||
|
||||
isVisible = lib.mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Should the user be able to see this value?";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
optsUser = { name, config, ... }: {
|
||||
freeformType = format.type;
|
||||
options = {
|
||||
user_id = lib.mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "The name of the user";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
optsGroup = { name, config, ... }: {
|
||||
freeformType = format.type;
|
||||
options = {
|
||||
display_name = lib.mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "The display name of the group";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.lldap = {
|
||||
provisionUsername = lib.mkOption {
|
||||
type = types.str;
|
||||
description = "Username to use when signing into lldap";
|
||||
};
|
||||
|
||||
provisionPasswordFile = lib.mkOption {
|
||||
type = types.path;
|
||||
description = "Path for the password file to authenticate the user";
|
||||
};
|
||||
|
||||
provision = {
|
||||
group_attributes = lib.mkOption {
|
||||
type = types.attrsOf (types.submodule optsAttributes);
|
||||
default = {};
|
||||
};
|
||||
|
||||
user_attributes = lib.mkOption {
|
||||
type = types.attrsOf (types.submodule optsAttributes);
|
||||
default = {};
|
||||
};
|
||||
|
||||
users = lib.mkOption {
|
||||
type = types.attrsOf (types.submodule optsUser);
|
||||
default = {};
|
||||
example = {
|
||||
user1 = {
|
||||
password = "env:LLDAP_USER1_PASSWORD";
|
||||
mail = "something@something.dk";
|
||||
|
||||
foo = "value for user attribute foo";
|
||||
bar = "value for user attribute bar";
|
||||
groups = [ "group1" "group2" ];
|
||||
};
|
||||
user2 = { user_id = "superuserawesome"; };
|
||||
};
|
||||
};
|
||||
|
||||
groups = lib.mkOption {
|
||||
type = types.attrsOf (types.submodule optsGroup);
|
||||
default = {};
|
||||
example = {
|
||||
base_member = {
|
||||
foo = "value for group attribute foo";
|
||||
bar = "value for group attribute bar";
|
||||
};
|
||||
system = {
|
||||
display_name = "system_service - override display_name";
|
||||
};
|
||||
testgroup = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.enable && cfg.provision != {}) {
|
||||
systemd.services.lldapsetup = {
|
||||
description = "setup lldap declaratively";
|
||||
wantedBy = [ config.systemd.services.lldap.name "multi-user.target" ];
|
||||
after = [ config.systemd.services.lldap.name ];
|
||||
|
||||
environment = {
|
||||
LLDAP_URL = "${cfg.settings.http_url}:${builtins.toString cfg.settings.http_port}";
|
||||
LLDAP_USERNAME = cfg.provisionUsername;
|
||||
LLDAP_PASSWORD = "file:${cfg.provisionPasswordFile}";
|
||||
};
|
||||
|
||||
path = with pkgs; [
|
||||
lldap
|
||||
];
|
||||
|
||||
script = let
|
||||
pythonEnv = pkgs.python3.withPackages(ps: with ps; [ gql aiohttp requests ]);
|
||||
pythonDir = pkgs.runCommand "lldap-bootstrap" {} ''
|
||||
mkdir -p $out/bootstrap
|
||||
cp -a ${./bootstrap}/. $out/bootstrap
|
||||
'';
|
||||
in ''
|
||||
cd ${pythonDir}
|
||||
${pythonEnv}/bin/python -m bootstrap.main ${configFile}
|
||||
'';
|
||||
};
|
||||
systemd.services.lldap.restartTriggers = [ configFile ];
|
||||
};
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./module
|
||||
];
|
||||
|
||||
services.lldap = {
|
||||
provisionUsername = "admin";
|
||||
provisionPasswordFile = config.age.secrets.lldap-admin-user-pass.path;
|
||||
|
||||
provision = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
# users
|
||||
users = {
|
||||
# bind user
|
||||
bind = {
|
||||
user_id = "bind_user";
|
||||
groups = [ lconfig.groups.password_manager lconfig.groups.strict_readonly ];
|
||||
};
|
||||
|
||||
# system users - defined in each service
|
||||
# should not be done here
|
||||
|
||||
# admin users
|
||||
admin = llib.mkProvisionUserAdmin "admin";
|
||||
eyjhb = llib.mkProvisionUserAdmin "eyjhb";
|
||||
rasmus = llib.mkProvisionUserAdmin "rasmus";
|
||||
|
||||
# normal users
|
||||
user1 = llib.mkProvisionUserNormal "thief420";
|
||||
testusername = (llib.mkProvisionUserNormal "testusername") // { mail = "testusername@fricloud.dk"; };
|
||||
};
|
||||
|
||||
# groups
|
||||
groups = {
|
||||
"base_member" = {};
|
||||
"system_service" = {};
|
||||
"system_mail" = {};
|
||||
"nextcloud_admin" = {};
|
||||
"drasl_admin" = {};
|
||||
"grafana_admin" = {};
|
||||
};
|
||||
|
||||
# attributes
|
||||
group_attributes = {
|
||||
group_foo = {
|
||||
attributeType = "STRING";
|
||||
isEditable = true;
|
||||
isVisible = true;
|
||||
};
|
||||
};
|
||||
user_attributes = {
|
||||
membermail = {
|
||||
attributeType = "STRING";
|
||||
isEditable = false;
|
||||
isVisible = true;
|
||||
};
|
||||
membermaildiskquota = {
|
||||
attributeType = "INTEGER";
|
||||
};
|
||||
nextcloudquota = {
|
||||
attributeType = "INTEGER";
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
systemd.services.lldapsetup.serviceConfig.EnvironmentFile = config.age.secrets.lldap-user-emails-env.path;
|
||||
}
|
|
@ -56,6 +56,7 @@ in {
|
|||
max_upload_size = max_upload_size;
|
||||
|
||||
# only authenticated media
|
||||
# TODO: Should default to true at some point
|
||||
enable_authenticated_media = true;
|
||||
|
||||
# retentien policies
|
||||
|
@ -74,12 +75,6 @@ in {
|
|||
allowed_lifetime_min = "1d";
|
||||
allowed_lifetime_max = "1y";
|
||||
};
|
||||
|
||||
|
||||
# automatically forget room on leave/kick/ban, and
|
||||
# purge from db after X time
|
||||
forget_rooms_on_leave = true;
|
||||
forgotten_room_retention_period = "28d";
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -128,7 +123,6 @@ in {
|
|||
client_id = "synapse";
|
||||
client_name = "Synapse";
|
||||
client_secret = "$pbkdf2-sha512$310000$SmE9y.LA9lnzxNWL6CeWQA$zcrum.Rst9xQy/MKBI5i.UiUdSjx/F0ak65Z3vYk0w7/GMWIqXaW3GnE7bJQw6nHi5eZ2uhKHtW/DKp2TDVhbQ";
|
||||
consent_mode = "implicit";
|
||||
redirect_uris = [ "https://${svc_domain}/_synapse/client/oidc/callback" ];
|
||||
scopes = [
|
||||
"openid"
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
imports = [
|
||||
./matrix-synapse.nix
|
||||
./element.nix
|
||||
./housecleaning.nix
|
||||
];
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
# delete empty directories
|
||||
# - https://github.com/element-hq/synapse/issues/7690
|
||||
# - https://github.com/matrix-org/synapse/issues/7690
|
||||
systemd.services.matrix-synapse.preStart =
|
||||
''${pkgs.findutils}/bin/find ${config.services.matrix-synapse.dataDir}/media_store -empty -type d -delete'';
|
||||
|
||||
systemd.timers."matrix-synapse-housecleaning-empty-dirs" = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "Mon *-*-* 04:00:00";
|
||||
Unit = config.systemd.services.matrix-synapse.name;
|
||||
};
|
||||
};
|
||||
|
||||
# vacuum database
|
||||
systemd.services."matrix-synapse-psql-vacuum" = let
|
||||
psqlUser = config.systemd.services.postgresql.serviceConfig.User;
|
||||
dbName = config.services.matrix-synapse.settings.database.args.database;
|
||||
in {
|
||||
serviceConfig.User = psqlUser;
|
||||
serviceConfig.RemainAfterExit = "yes";
|
||||
|
||||
script = ''${config.services.postgresql.package}/bin/psql -d ${dbName} -c "vacuum full"'';
|
||||
};
|
||||
|
||||
systemd.timers."matrix-synapse-housecleaning-vacuum-db" = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "Mon *-*-* 04:00:00";
|
||||
Unit = config.systemd.services.matrix-synapse-psql-vacuum.name;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -3,16 +3,13 @@
|
|||
let
|
||||
svc_domain = "miniflux.${config.mine.shared.settings.domain}";
|
||||
port = 6466;
|
||||
|
||||
listenOn = "localhost:${builtins.toString port}";
|
||||
httpListenOn = "http://${listenOn}";
|
||||
in {
|
||||
services.miniflux = {
|
||||
enable = true;
|
||||
|
||||
config = {
|
||||
# listen only on localhost
|
||||
LISTEN_ADDR = listenOn;
|
||||
LISTEN_ADDR = "localhost:${builtins.toString port}";
|
||||
|
||||
# setup the correct baseurl
|
||||
BASE_URL = "https://${svc_domain}";
|
||||
|
@ -22,11 +19,12 @@ in {
|
|||
DISABLE_LOCAL_AUTH = "true";
|
||||
|
||||
# use auth proxy
|
||||
# TODO: This should be configureable
|
||||
AUTH_PROXY_HEADER = config.mine.shared.lib.authelia.protectedHeaders.username;
|
||||
AUTH_PROXY_USER_CREATION = "true";
|
||||
|
||||
# For privacy, proxy images instead of hotlinking
|
||||
MEDIA_PROXY_RESOURCE_TYPES = "image,audio,video";
|
||||
MEDIA_PROXY_RESOURCE_TYPES = "image";
|
||||
MEDIA_PROXY_MODE = "all";
|
||||
};
|
||||
};
|
||||
|
@ -37,18 +35,10 @@ in {
|
|||
enableACME = true;
|
||||
|
||||
locations."/" = config.mine.shared.lib.authelia.mkProtectedLocation {
|
||||
proxyPass = httpListenOn;
|
||||
proxyPass = "http://localhost:${builtins.toString port}";
|
||||
};
|
||||
|
||||
# allow API for mobile apps etc.
|
||||
locations."/v1".proxyPass = httpListenOn;
|
||||
|
||||
# allow sharing
|
||||
locations."/share".proxyPass = httpListenOn;
|
||||
|
||||
# used for sharing
|
||||
# TODO: would be nice if nginx could serve this instead
|
||||
locations."~* \.(js|jpg|png|css)$".proxyPass = httpListenOn;
|
||||
locations."/v1".proxyPass = "http://localhost:${builtins.toString port}";
|
||||
};
|
||||
|
||||
# meta
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
imports = [
|
||||
./grafana.nix
|
||||
./prometheus.nix
|
||||
|
||||
./mon-postgres.nix
|
||||
./mon-stalwart.nix
|
||||
./mon-authelia.nix
|
||||
./mon-matrix-synapse.nix
|
||||
./mon-zfs.nix
|
||||
./mon-miniflux.nix
|
||||
./mon-hedgedoc.nix
|
||||
./mon-forgejo.nix
|
||||
./mon-uptime-kuma.nix
|
||||
./mon-searx.nix
|
||||
./mon-nextcloud.nix
|
||||
./mon-node-exporter.nix
|
||||
];
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
let
|
||||
svc_domain = "grafana.${config.mine.shared.settings.domain}";
|
||||
|
||||
auth_domain = config.mine.shared.settings.authelia.domain;
|
||||
|
||||
grafana_user = config.systemd.services.grafana.serviceConfig.User;
|
||||
in {
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings = {
|
||||
server = {
|
||||
http_addr = "127.0.0.1";
|
||||
http_port = 3010;
|
||||
root_url = "https://${svc_domain}";
|
||||
};
|
||||
|
||||
# only allow signun with oauth
|
||||
auth.disable_login_form = true;
|
||||
|
||||
"auth.generic_oauth" = {
|
||||
enabled = true;
|
||||
name = "Authelia";
|
||||
icon = "signin";
|
||||
client_id = "grafana";
|
||||
client_secret = "$__file{${config.age.secrets.grafana-authelia-secret.path}}";
|
||||
scopes = "openid profile email groups";
|
||||
empty_scopes = false;
|
||||
auth_url = "https://${auth_domain}/api/oidc/authorization";
|
||||
token_url = "https://${auth_domain}/api/oidc/token";
|
||||
api_url = "https://${auth_domain}/api/oidc/userinfo";
|
||||
login_attribute_path = "preferred_username";
|
||||
groups_attribute_path = "groups";
|
||||
name_attribute_path = "name";
|
||||
use_pkce = true;
|
||||
|
||||
role_attribute_path = config.mine.shared.lib.ldap.mkScope (lconfig: llib:
|
||||
"contains(groups, '${lconfig.groups.grafana_admin}') && 'Admin' || contains(groups, 'editor') && 'Editor' || 'Viewer'"
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
provision = {
|
||||
enable = true;
|
||||
|
||||
# dashboards.settings.providers = [{
|
||||
# name = "my dashboards";
|
||||
# options.path = "/etc/grafana-dashboards";
|
||||
# }];
|
||||
|
||||
datasources.settings.datasources = [
|
||||
{
|
||||
name = "Prometheus";
|
||||
type = "prometheus";
|
||||
url = "http://${config.services.prometheus.listenAddress}:${toString config.services.prometheus.port}";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# authelia
|
||||
services.authelia.instances.main.settings.identity_providers.oidc.clients = [{
|
||||
client_id = "grafana";
|
||||
client_name = "Grafana";
|
||||
client_secret = "$pbkdf2-sha512$310000$81MV1.67njuS/5H2UvVsnA$vaNO3/tzVA76Jho4ngS.xFjDuYn1sDn/9qo7cD0ueMnVvzaoJj00ND5wCGzVSUnvLuxNE/enC1K5r7xKAe/Hrg";
|
||||
consent_mode = "implicit";
|
||||
redirect_uris = [ "https://${svc_domain}/login/generic_oauth" ];
|
||||
scopes = [
|
||||
"openid"
|
||||
"email"
|
||||
"profile"
|
||||
"groups"
|
||||
];
|
||||
}];
|
||||
|
||||
environment.persistence.root.directories = [
|
||||
config.services.grafana.dataDir
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"Z ${config.services.grafana.dataDir} 0770 ${grafana_user} ${grafana_user} -"
|
||||
];
|
||||
|
||||
age.secrets.grafana-authelia-secret.owner = grafana_user;
|
||||
|
||||
services.nginx.virtualHosts."${svc_domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/".proxyPass = "http://localhost:${builtins.toString config.services.grafana.settings.server.http_port}";
|
||||
};
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
services.authelia.instances.main.settings = {
|
||||
telemetry.metrics = {
|
||||
enabled = true;
|
||||
};
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "authelia";
|
||||
static_configs = [{
|
||||
targets = [ (lib.removePrefix "tcp://" config.services.authelia.instances.main.settings.telemetry.metrics.address) ];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
services.forgejo.settings.metrics.ENABLED = true;
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "forgejo";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${builtins.toString config.services.forgejo.settings.server.HTTPPORT}" ];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
services.hedgedoc.settings = {
|
||||
# enabled by default anyways
|
||||
# TODO(eyJhb): disable exposing this to the WORLD
|
||||
enableStatsApi = true;
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "hedgedoc";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${builtins.toString config.services.hedgedoc.settings.port}"];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
let
|
||||
metrics_port = 9734;
|
||||
in {
|
||||
services.matrix-synapse = {
|
||||
settings = {
|
||||
enable_metrics = true;
|
||||
listeners = [
|
||||
{
|
||||
port = metrics_port;
|
||||
type = "metrics";
|
||||
bind_addresses = [ "localhost" ];
|
||||
tls = false;
|
||||
resources = [];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "matrix-synapse";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${builtins.toString metrics_port}"];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
services.miniflux.config = {
|
||||
METRICS_COLLECTOR = 1;
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "miniflux";
|
||||
static_configs = [{
|
||||
targets = [ config.services.miniflux.config.LISTEN_ADDR ];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
# occ bin
|
||||
occ = config.services.nextcloud.occ + "/bin/nextcloud-occ";
|
||||
|
||||
nextcloudSetupServerinfoToken = pkgs.writeShellScript "nextcloud-setup-serverinfo-token.sh" ''
|
||||
# set serverinfo_token
|
||||
SERVERINFO_TOKEN="$(cat $CREDENTIALS_DIRECTORY/nextcloud-serverinfo-token)"
|
||||
${occ} config:app:set serverinfo token --value "$SERVERINFO_TOKEN" > /dev/null 2>&1
|
||||
'';
|
||||
in {
|
||||
systemd.services.nextcloud-setup = {
|
||||
# runs this after all the main nextcloud-setup stuff
|
||||
script = lib.mkAfter ''
|
||||
${nextcloudSetupServerinfoToken}
|
||||
'';
|
||||
|
||||
# setup credentials for service
|
||||
serviceConfig.LoadCredential = [
|
||||
"nextcloud-serverinfo-token:${config.age.secrets.nextcloud-serverinfo-token.path}"
|
||||
];
|
||||
};
|
||||
|
||||
services.prometheus.exporters.nextcloud = {
|
||||
enable = true;
|
||||
listenAddress = "localhost";
|
||||
tokenFile = config.age.secrets.nextcloud-serverinfo-token.path;
|
||||
url = let
|
||||
scheme = if config.services.nextcloud.https then "https" else "http";
|
||||
in "${scheme}://${config.services.nextcloud.hostName}";
|
||||
};
|
||||
|
||||
# setup permissions
|
||||
age.secrets.nextcloud-serverinfo-token.owner = config.services.prometheus.exporters.nextcloud.user;
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "nextcloud";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${builtins.toString config.services.prometheus.exporters.nextcloud.port}" ];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
services.prometheus.exporters.node = {
|
||||
enable = true;
|
||||
listenAddress = "localhost";
|
||||
|
||||
enabledCollectors = [
|
||||
"cpu"
|
||||
"filesystem"
|
||||
"systemd"
|
||||
];
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "node-exporter";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${builtins.toString config.services.prometheus.exporters.node.port}"];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
services.prometheus.exporters.postgres = {
|
||||
enable = true;
|
||||
listenAddress = "localhost";
|
||||
runAsLocalSuperUser = true;
|
||||
|
||||
extraFlags = let
|
||||
extraQuery = pkgs.writeText "prometehus-postgres-query.yaml" ''
|
||||
pg_database:
|
||||
query: "SELECT pg_database.datname, pg_database_size(pg_database.datname) as size FROM pg_database"
|
||||
metrics:
|
||||
- datname:
|
||||
usage: "LABEL"
|
||||
description: "Name of the database"
|
||||
- size:
|
||||
usage: "GAUGE"
|
||||
description: "Disk space used by the database"
|
||||
'';
|
||||
in [
|
||||
"--extend.query-path=${extraQuery}"
|
||||
];
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "postgres";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${toString config.services.prometheus.exporters.postgres.port}" ];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
services.searx.settings.general.open_metrics = "thisreallydoesnotmatterasitisnotaccessiblefromoutsideofthisserver";
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "searx";
|
||||
basic_auth.username = "canbeanything";
|
||||
basic_auth.password = config.services.searx.settings.general.open_metrics;
|
||||
static_configs = [{
|
||||
targets = [ config.services.searx.uwsgiConfig.http ];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
services.stalwart-mail.settings = {
|
||||
metrics.prometheus.enable = true;
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "stalwart";
|
||||
metrics_path = "/metrics/prometheus";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${toString config.mine.shared.settings.mail.ports.http_management}" ];
|
||||
}];
|
||||
metric_relabel_configs = [{
|
||||
source_labels = [ "__name__" ];
|
||||
target_label = "__name__";
|
||||
replacement = "stalwart_$1";
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
{
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "uptime-kuma";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${builtins.toString config.services.uptime-kuma.settings.PORT}" ];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
services.prometheus.exporters.zfs = {
|
||||
enable = true;
|
||||
listenAddress = "localhost";
|
||||
|
||||
extraFlags = [ "--collector.dataset-snapshot" ];
|
||||
};
|
||||
|
||||
services.prometheus.scrapeConfigs = [
|
||||
{
|
||||
job_name = "zfs";
|
||||
static_configs = [{
|
||||
targets = [ "localhost:${toString config.services.prometheus.exporters.zfs.port}" ];
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
{ config, ... }:
|
||||
|
||||
let
|
||||
prometheus_user = config.systemd.services.prometheus.serviceConfig.User;
|
||||
|
||||
fullDataDirPath = "/var/lib/${config.services.prometheus.stateDir}";
|
||||
|
||||
filesetPath = config.mine.zfsMounts."rpool/safe/svcs/prometheus";
|
||||
in {
|
||||
services.prometheus = {
|
||||
enable = true;
|
||||
globalConfig.scrape_interval = "10s";
|
||||
globalConfig.scrape_timeout = "10s";
|
||||
listenAddress = "localhost";
|
||||
|
||||
# default is 15 days, we just set it to 14 to be explicit
|
||||
retentionTime = "14d";
|
||||
};
|
||||
|
||||
fileSystems."${filesetPath}".neededForBoot = true;
|
||||
environment.persistence."${filesetPath}".directories = [
|
||||
fullDataDirPath
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"Z ${fullDataDirPath} 0770 ${prometheus_user} ${prometheus_user} -"
|
||||
];
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
let
|
||||
svc_domain = "nextcloud.${config.mine.shared.settings.domain}";
|
||||
|
||||
default_storage_quota = "1mb";
|
||||
default_storage_quota = "100MB";
|
||||
|
||||
# place data into own zfs dataset
|
||||
stateDir = config.mine.zfsMounts."rpool/safe/svcs/nextcloud";
|
||||
|
@ -49,7 +49,7 @@ let
|
|||
ldapGroupFilter = config.mine.shared.lib.ldap.mkFilter (lconfig: llib:
|
||||
llib.mkAnd [
|
||||
(llib.mkOC lconfig.oc.groupOfUniqueNames)
|
||||
(llib.mkOr [ "cn=${lconfig.groups.nextcloud_admin}" "cn=${lconfig.groups.member}"])
|
||||
(llib.mkOr [ "cn=${lconfig.groups.admin}" "cn=${lconfig.groups.member}"])
|
||||
]
|
||||
);
|
||||
ldapGroupFilterGroups = "admin;user";
|
||||
|
@ -59,8 +59,6 @@ let
|
|||
ldapUserFilterMode = 1;
|
||||
ldapExpertUsernameAttr = config.mine.shared.settings.ldap.attr.uid;
|
||||
ldapConfigurationActive = 1;
|
||||
ldapQuotaDefault = 1;
|
||||
ldapQuotaAttribute = config.mine.shared.settings.ldap.attr.nextcloudquota;
|
||||
};
|
||||
ldap_commands = lib.mapAttrsToList (n: v: "${occ} ldap:set-config $NEW_CONFIG_ID ${n} '${builtins.toString v}'") ldap_settings;
|
||||
in pkgs.writeShellScript "nextcloud-add-ldap.sh" ''
|
||||
|
@ -86,7 +84,7 @@ let
|
|||
done
|
||||
|
||||
# promote ldap admin group to admins
|
||||
${occ} ldap:promote-group ${config.mine.shared.settings.ldap.groups.nextcloud_admin} --yes -n
|
||||
${occ} ldap:promote-group ${config.mine.shared.settings.ldap.groups.admin} --yes -n
|
||||
'';
|
||||
|
||||
# script for resetting nextcloud admin password on each startup
|
||||
|
@ -160,7 +158,7 @@ in {
|
|||
config.dbtype = "pgsql";
|
||||
|
||||
# settings
|
||||
settings = rec {
|
||||
settings = {
|
||||
# open connect/oidc
|
||||
oidc_login_provider_url = "https://${config.mine.shared.settings.authelia.domain}";
|
||||
oidc_login_client_id = AUTHELIA_AUTH_NAME;
|
||||
|
@ -178,25 +176,9 @@ in {
|
|||
};
|
||||
oidc_login_scope = "openid profile email groups";
|
||||
oidc_login_code_challenge_method = "S256";
|
||||
|
||||
# mail
|
||||
mail_from_address = "nextcloud";
|
||||
mail_smtpmode = "smtp";
|
||||
mail_sendmailmode = "smtp";
|
||||
mail_domain = "${config.mine.shared.settings.domain}";
|
||||
mail_smtphost = "${config.mine.shared.settings.mail.domain_smtp}";
|
||||
mail_smtpport = config.mine.shared.settings.mail.ports.submissions;
|
||||
mail_smtpsecure = "ssl";
|
||||
mail_smtpname = mail_from_address;
|
||||
# mail_smtppassword = "defined-in-the-secrets-file-and-in-a-separate-file-for-lldap";
|
||||
};
|
||||
};
|
||||
|
||||
# setup lldap user for nextcloud that can send emails
|
||||
services.lldap.provision.users = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
nextcloud = llib.mkProvisionUserSystem "nextcloud" config.age.secrets.nextcloud-smtp-pass.path;
|
||||
});
|
||||
|
||||
systemd.services.nextcloud-setup = {
|
||||
# runs this after all the main nextcloud-setup stuff
|
||||
script = lib.mkAfter ''
|
||||
|
@ -221,7 +203,6 @@ in {
|
|||
client_id = AUTHELIA_AUTH_NAME;
|
||||
client_name = "Nextcloud";
|
||||
client_secret = "$pbkdf2-sha512$310000$kLNQ/1A.uasSN4g8q94jUQ$8OKNUNNumHCh8dVG5/QWys7u.y1guqFXlrL.bMm7/HKTsWhpib/W.8qlU6VU7V1Be/h14Y.fJi3RLvbkEdo2kA";
|
||||
consent_mode = "implicit";
|
||||
redirect_uris = [ "https://${svc_domain}/apps/oidc_login/oidc" ];
|
||||
scopes = [
|
||||
"openid"
|
||||
|
|
|
@ -1,414 +0,0 @@
|
|||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell --pure -i python3 -p "python3.withPackages (ps: with ps; [ flask apprise mnemonic wtforms jq ])"
|
||||
from typing import Any
|
||||
import apprise
|
||||
from flask import Flask, request
|
||||
from mnemonic import Mnemonic
|
||||
import sqlite3
|
||||
import os
|
||||
import jq
|
||||
import json
|
||||
|
||||
app = Flask(__name__)
|
||||
app.url_map.strict_slashes = False
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger("werkzeug")
|
||||
log.setLevel(logging.ERROR)
|
||||
|
||||
ENV_PREFIX = "NOTIFIER_"
|
||||
|
||||
|
||||
def getenv(key: str, default: Any = None) -> Any:
|
||||
v = os.getenv(ENV_PREFIX + key, default)
|
||||
if not v:
|
||||
exit(f"{ENV_PREFIX+key} must be specified!")
|
||||
|
||||
return v
|
||||
|
||||
|
||||
CONFIG_URL = getenv("URL", "http://127.0.0.1")
|
||||
CONFIG_PORT = int(getenv("PORT", 8080))
|
||||
|
||||
CONFIG_DATABASE_PATH = getenv("DATABASE_PATH", "notifications.db")
|
||||
|
||||
CONFIG_MATRIX_BOT_NAME = getenv("MATRIX_BOT_NAME", "unset")
|
||||
CONFIG_MATRIX_BOT_TOKEN = getenv("MATRIX_BOT_TOKEN")
|
||||
CONFIG_MATRIX_HOST = getenv("MATRIX_HOST")
|
||||
|
||||
CONFIG_PROXY_AUTH_USERNAME_HEADER = getenv("PROXY_AUTH_USERNAME_HEADER", "Remote-User")
|
||||
|
||||
CONFIG_MAIL_USERNAME = getenv("MAIL_USERNAME")
|
||||
CONFIG_MAIL_PASSWORD = getenv("MAIL_PASSWORD")
|
||||
CONFIG_MAIL_DOMAIN = getenv("MAIL_DOMAIN")
|
||||
CONFIG_MAIL_HOST = getenv("MAIL_HOST")
|
||||
CONFIG_MAIL_PORT = int(getenv("MAIL_PORT", "465"))
|
||||
CONFIG_MAIL_MODE = getenv("MAIL_MODE", "ssl")
|
||||
|
||||
script_example = rf"""#!/usr/bin/env bash
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell --pure -i python3 -p "python3.withPackages (ps: with ps; [ requests ])"
|
||||
import requests
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("--title", default="", help="Subject/title of the notification")
|
||||
parser.add_argument("--body", default="", help="`-` for stdin")
|
||||
parser.add_argument("--jq", default=".", help="Jq filter to use")
|
||||
parser.add_argument("--type", default="matrix", help="mail or matrix")
|
||||
parser.add_argument("--token", help="token to use")
|
||||
parser.add_argument("--token-file", help="file to read token from")
|
||||
parser.add_argument("--url", default="{CONFIG_URL}/notify", help="Notify endpoint")
|
||||
args = parser.parse_args()
|
||||
|
||||
token: str = args.token
|
||||
if args.token_file:
|
||||
token = open(args.token_file, "r").read().strip()
|
||||
if not token:
|
||||
exit("No token or tokenfile specified, or empty")
|
||||
|
||||
data = args.body
|
||||
if data == "-" and not sys.stdin.isatty():
|
||||
data = "\n".join(sys.stdin.readlines())
|
||||
|
||||
headers = {{"Authorization": f"Bearer {{token}}"}}
|
||||
params = {{
|
||||
"jq": args.jq,
|
||||
"type": args.type,
|
||||
}}
|
||||
if args.title:
|
||||
params["title"] = args.title
|
||||
|
||||
req = requests.post(args.url, headers=headers, params=params, data=data)
|
||||
exit(not req.status_code == 200)
|
||||
"""
|
||||
|
||||
script_example_with_token = script_example.replace(
|
||||
'--token"',
|
||||
'--token", default="||TOKEN||"',
|
||||
)
|
||||
|
||||
|
||||
def get_db():
|
||||
con = sqlite3.connect(CONFIG_DATABASE_PATH)
|
||||
cur = con.cursor()
|
||||
cur.execute(
|
||||
"CREATE TABLE IF NOT EXISTS default_room(username TEXT PRIMARY KEY, room_id TEXT NOT NULL)"
|
||||
)
|
||||
cur.execute(
|
||||
"CREATE TABLE IF NOT EXISTS tokens(username TEXT PRIMARY KEY, token TEXT NOT NULL)"
|
||||
)
|
||||
|
||||
return con
|
||||
|
||||
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
def index():
|
||||
username = request.headers.get(CONFIG_PROXY_AUTH_USERNAME_HEADER)
|
||||
if not username:
|
||||
return ("Not authenticated", 401)
|
||||
|
||||
# handle post stuff
|
||||
if request.method == "POST":
|
||||
action = request.form.get("action", "").lower()
|
||||
if "token" in action:
|
||||
generate_token_for_user(username)
|
||||
elif "room id" in action:
|
||||
roomid = request.form.get("room_id")
|
||||
if not roomid:
|
||||
return ("Room Id cannot be empty", 400)
|
||||
set_user_default_matrix_room(username, roomid)
|
||||
|
||||
con = get_db()
|
||||
cur = con.cursor()
|
||||
res = cur.execute(
|
||||
"SELECT token FROM tokens WHERE username = ?", (username,)
|
||||
).fetchone()
|
||||
|
||||
token: str = ""
|
||||
if res:
|
||||
token = res[0]
|
||||
|
||||
res = cur.execute(
|
||||
"SELECT room_id FROM default_room WHERE username = ?", (username,)
|
||||
).fetchone()
|
||||
|
||||
room_id: str = ""
|
||||
if res:
|
||||
room_id = res[0]
|
||||
|
||||
# hack to make users confirm it
|
||||
generate_token_name: str = "tmpaction"
|
||||
generate_token_value: str = "Generate Token"
|
||||
if request.form.get(generate_token_name):
|
||||
generate_token_name = "action"
|
||||
generate_token_value = "Generate Token. Are you sure?"
|
||||
|
||||
tmpl = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Notification Service</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container my-5">
|
||||
<h1>Hello {username}!</h1>
|
||||
<div class="col-lg-8 px-0">
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Default Matrix Room ID</label>
|
||||
<input type="text" name="room_id" value="{room_id}" placeholder="!yREJWHUMJhGROiHbtu:fricloud.dk" class="form-control">
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary" name="action" value="Set Default Room ID">
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Token (dashes are optional)</label>
|
||||
<input type="text" value="{token}" placeholder="token-not-generated" readonly class="form-control" onclick="this.select();" >
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary" name="{generate_token_name}" value="{generate_token_value}">
|
||||
</form>
|
||||
<hr>
|
||||
<p>The token can be given as a URL parameter 'token', as a auth header `Authorization: Bearer token`, HTTPAuth where username is ignored, and token is the password.
|
||||
Can also be given a a path, e.g. {CONFIG_URL}/{token}.
|
||||
</p>
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Quick URL</label>
|
||||
<input type="text" value="curl {CONFIG_URL}/notify/{token.replace("-", "")}/mymessage" class="form-control" onclick="this.select();" >
|
||||
</div>
|
||||
<hr>
|
||||
<p>
|
||||
This notification service has support for both matrix and email.
|
||||
Matrix notifications will be sent from {CONFIG_MATRIX_BOT_NAME}, be sure to invite them to the room/space if it is private.
|
||||
If using email, they will only be sent to your member email, and can't be sent anywhere else.
|
||||
You'll receive them from {CONFIG_MAIL_USERNAME}@{CONFIG_MAIL_DOMAIN}.
|
||||
</p>
|
||||
<hr>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Param</th>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col">Default</th>
|
||||
<th scope="col">Example</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">title</th>
|
||||
<td>Title of notification</td>
|
||||
<td>Notification</td>
|
||||
<td>Compilation Finished!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">body</th>
|
||||
<td>Body of notification</td>
|
||||
<td>empty</td>
|
||||
<td>Compilation result: success (default is nothing)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">type</th>
|
||||
<td>type of notification</td>
|
||||
<td>matrix</td>
|
||||
<td>matrix</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">room_id</th>
|
||||
<td>Matrix room ID</td>
|
||||
<td>default room</td>
|
||||
<td>!yREJWHUMJhGROiHbtu:fricloud.dk or #na-offtopic:rend.al</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">token</th>
|
||||
<td>Authorization Token</td>
|
||||
<td>empty</td>
|
||||
<td>enable-trade-decide or enabletradedecide</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">jq</th>
|
||||
<td>jq filter</td>
|
||||
<td>.</td>
|
||||
<td>[.[].commits | .name ] | join("\\n")</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
|
||||
bash alias
|
||||
<pre class="border"><code>notify() {{ curl "{CONFIG_URL}/notify/{token}" -g --data-urlencode "body=${{1:-}}"; }}</code></pre>
|
||||
curl
|
||||
<pre class="border"><code>curl "{CONFIG_URL}/notify" -H "Authorization: Bearer {token}"</code></pre>
|
||||
curl w/ specific body/title
|
||||
<pre class="border"><code>curl "{CONFIG_URL}/notify?title=MyTitle&body=MyBody" -H "Authorization: Bearer {token}"</code></pre>
|
||||
Python
|
||||
<pre class="border"><code>{script_example}</code></pre>
|
||||
Python w/ <b>hardcoded token (DO NOT SHARE)</b>
|
||||
<pre class="border"><code>{script_example_with_token}</code></pre>
|
||||
|
||||
Nix Python Script <b>HARDCODED TOKEN (DO NOT SHARE)</b>
|
||||
<pre class="border"><code>pkgs.writers.writePython3Bin "notify" {{
|
||||
libraries = with pkgs.python3Packages; [ requests ];
|
||||
doCheck = false;
|
||||
}} ''{script_example_with_token}'';
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2>Notes</h2>
|
||||
<p>
|
||||
<b>jq</b> is very powerful, and can easily be used to turn webhook data into useful information in a notification.
|
||||
Just append your `jq=<url_encoded_query>`, to your notification URL, and then watch the magic.
|
||||
Below is an example for doing it with Forgejo, when new commits are pushed.
|
||||
</p>
|
||||
|
||||
<pre class="border"><code>(.total_commits | tostring)
|
||||
+ " commits pushed to "
|
||||
+ .repository.full_name
|
||||
+ "\n\n" +
|
||||
# format commits
|
||||
([.commits.[]
|
||||
| "- "
|
||||
+ (.message | gsub("[\n\t]"; ""))
|
||||
+ " (" + .author.name + ")" ]
|
||||
| join("\n"))
|
||||
+ "\n\n"
|
||||
+ "Changes: " + .compare_url</code></pre>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""".replace(
|
||||
r"||TOKEN||", token
|
||||
)
|
||||
return tmpl
|
||||
|
||||
|
||||
@app.route("/notify", methods=["GET", "POST"], defaults={"token": "", "body": ""})
|
||||
@app.route("/notify/<token>", methods=["GET", "POST"], defaults={"body": ""})
|
||||
@app.route("/notify/<token>/<body>", methods=["GET", "POST"])
|
||||
def send_notification(token: str, body: str):
|
||||
# default to this
|
||||
ntype = request.args.get("type", "matrix")
|
||||
title = request.args.get("title", "Notification")
|
||||
body = request.args.get("body", request.get_data().decode("utf-8") or body)
|
||||
if not body:
|
||||
body = " "
|
||||
|
||||
if request.authorization:
|
||||
token = request.authorization.token or request.authorization.password
|
||||
|
||||
if not token:
|
||||
return (
|
||||
"No token found, please either specify with Authorization: Bearer <token>, HTTPBasic or in the URL path",
|
||||
401,
|
||||
)
|
||||
|
||||
if (
|
||||
request.method == "POST"
|
||||
and "json" in request.headers.get("content-type", "")
|
||||
or is_json(body)
|
||||
):
|
||||
try:
|
||||
body = jq.compile(request.args.get("jq", ".")).input_text(body).first()
|
||||
except Exception:
|
||||
return ("Unable to compile JQ, please ensure it is correct", 400)
|
||||
|
||||
if not isinstance(body, str):
|
||||
body = json.dumps(body, indent=4)
|
||||
|
||||
con = get_db()
|
||||
cur = con.cursor()
|
||||
res = cur.execute(
|
||||
"SELECT username FROM tokens WHERE REPLACE(token, '-', '') = ? OR token = ? ",
|
||||
(token, token),
|
||||
).fetchone()
|
||||
|
||||
if not res:
|
||||
return ("Access denied - invalid token", 401)
|
||||
|
||||
username = res[0]
|
||||
|
||||
if ntype not in ["matrix", "mail"]:
|
||||
return ("Invalid type, only matrix or mail allowed", 400)
|
||||
|
||||
apobj = apprise.Apprise()
|
||||
if ntype == "matrix":
|
||||
# try to get a room_id
|
||||
room_id = request.args.get("room_id")
|
||||
if not room_id:
|
||||
res = cur.execute(
|
||||
"SELECT room_id FROM default_room WHERE username = ?", (username,)
|
||||
).fetchone()
|
||||
if res:
|
||||
room_id = res[0]
|
||||
|
||||
if not room_id:
|
||||
return ("No room_id specified, and no default saved", 400)
|
||||
|
||||
apobj.add(f"matrixs://{CONFIG_MATRIX_BOT_TOKEN}@{CONFIG_MATRIX_HOST}/{room_id}")
|
||||
else:
|
||||
apobj.add(
|
||||
f"mailto://{CONFIG_MAIL_USERNAME}:{CONFIG_MAIL_PASSWORD}@{CONFIG_MAIL_HOST}:{CONFIG_MAIL_PORT}?mode={CONFIG_MAIL_MODE}&from={CONFIG_MAIL_USERNAME}@{CONFIG_MAIL_DOMAIN}&to={username}@{CONFIG_MAIL_DOMAIN}"
|
||||
)
|
||||
|
||||
sent_notification = apobj.notify(
|
||||
title=title,
|
||||
body=body,
|
||||
)
|
||||
|
||||
if not send_notification:
|
||||
return ("Unable to send notification", 500)
|
||||
return ("Notification sent!", 200)
|
||||
|
||||
|
||||
def generate_token_for_user(username: str):
|
||||
token = generate_token()
|
||||
con = get_db()
|
||||
cur = con.cursor()
|
||||
t = cur.execute(
|
||||
"INSERT INTO tokens (username, token) VALUES (?, ?) ON CONFLICT (username) DO UPDATE SET token = ?",
|
||||
(
|
||||
username,
|
||||
token,
|
||||
token,
|
||||
),
|
||||
)
|
||||
con.commit()
|
||||
|
||||
|
||||
def set_user_default_matrix_room(username: str, roomid: str):
|
||||
con = get_db()
|
||||
cur = con.cursor()
|
||||
t = cur.execute(
|
||||
"INSERT INTO default_room (username, room_id) VALUES (?, ?) ON CONFLICT (username) DO UPDATE SET room_id = ?",
|
||||
(
|
||||
username,
|
||||
roomid,
|
||||
roomid,
|
||||
),
|
||||
)
|
||||
con.commit()
|
||||
|
||||
|
||||
def generate_token(num_words: int = 3) -> str:
|
||||
mnemo = Mnemonic("english")
|
||||
words = mnemo.generate(strength=256)
|
||||
return "-".join(words.split(" ")[0:num_words])
|
||||
|
||||
|
||||
def is_json(potential_json: str) -> bool:
|
||||
try:
|
||||
json.loads(potential_json)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="127.0.0.1", port=CONFIG_PORT)
|
|
@ -1,90 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
svc_domain = "notify.${config.mine.shared.settings.domain}";
|
||||
port = 5055;
|
||||
ldap_user = "notification";
|
||||
|
||||
stateDirName = "notify";
|
||||
stateDir = "/var/lib/${stateDirName}";
|
||||
in {
|
||||
systemd.services.notifify = {
|
||||
description = "notifications for members";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "networking.target" ];
|
||||
|
||||
environment = {
|
||||
NOTIFIER_URL = "https://${svc_domain}";
|
||||
NOTIFIER_PORT = builtins.toString port;
|
||||
|
||||
NOTIFIER_DATABASE_PATH = "${stateDir}/notify.db";
|
||||
|
||||
# NOTIFIER_MATRIX_BOT_TOKEN = "";
|
||||
NOTIFIER_MATRIX_BOT_NAME = "@${ldap_user}:${config.mine.shared.settings.domain}";
|
||||
NOTIFIER_MATRIX_HOST = config.mine.shared.settings.matrix-synapse.domain;
|
||||
|
||||
NOTIFIER_PROXY_AUTH_USERNAME_HEADER = config.mine.shared.lib.authelia.protectedHeaders.username;
|
||||
|
||||
NOTIFIER_MAIL_USERNAME = ldap_user;
|
||||
# NOTIFIER_MAIL_PASSWORD = "";
|
||||
NOTIFIER_MAIL_DOMAIN = config.mine.shared.settings.domain;
|
||||
NOTIFIER_MAIL_HOST = config.mine.shared.settings.mail.domain;
|
||||
NOTIFIER_MAIL_PORT = builtins.toString config.mine.shared.settings.mail.ports.submissions;
|
||||
|
||||
# production
|
||||
FLASK_ENV = "production";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
EnvironmentFile = [ config.age.secrets.notify-env.path ];
|
||||
|
||||
StateDirectory = stateDirName;
|
||||
|
||||
DynamicUser = true;
|
||||
|
||||
ExecStart = let
|
||||
pythonEnv = pkgs.python3.withPackages(ps: with ps; [ flask apprise mnemonic wtforms jq ]);
|
||||
in "${pythonEnv}/bin/python ${./app.py}";
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
|
||||
# setup notification user
|
||||
services.lldap.provision.users = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
"${ldap_user}" = llib.mkProvisionUserSystem ldap_user config.age.secrets.notify-ldap-pass.path;
|
||||
});
|
||||
|
||||
# persistent files
|
||||
environment.persistence.root.directories = [
|
||||
{ directory = "/var/lib/private/${stateDirName}"; mode = "0700"; }
|
||||
];
|
||||
|
||||
# nginx
|
||||
services.nginx.virtualHosts."${svc_domain}" = config.mine.shared.lib.authelia.mkProtectedWebsite {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/" = config.mine.shared.lib.authelia.mkProtectedLocation {
|
||||
proxyPass = "http://localhost:${builtins.toString port}";
|
||||
};
|
||||
locations."/notify".proxyPass = "http://localhost:${builtins.toString port}";
|
||||
};
|
||||
|
||||
# metada
|
||||
mine.shared.meta.notify = {
|
||||
name = "Notification Service";
|
||||
description = "This website you are looking at right now, which is our members website.";
|
||||
url = "https://${svc_domain}";
|
||||
|
||||
package = {
|
||||
name = "notify-website";
|
||||
version = "v0.0.1";
|
||||
meta = with lib; {
|
||||
description = "Notification website for ${config.mine.shared.settings.domain}";
|
||||
license = licenses.free;
|
||||
homepage = "https://git.fricloud.dk/fricloud/server-configs/src/branch/main/machines/gerd/services/notify/app.py";
|
||||
platforms = platforms.all;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
BODY="$1"
|
||||
TITLE=${2:-Notification}
|
||||
JQ_EXPR=${3:-.}
|
||||
TYPE=${4:-matrix}
|
||||
# TOKEN="$(cat ~/.config/notify/token)"
|
||||
# TOKEN="$(cat /run/agenix/notify-token)"
|
||||
TOKEN="$(cat token)"
|
||||
URL="https://notify.fricloud.dk/notify"
|
||||
# URL="||URL||"
|
||||
|
||||
# get stdin if needed
|
||||
if [ "$BODY" = "-" ]; then
|
||||
BODY="$(cat -)"
|
||||
fi
|
||||
|
||||
# make request
|
||||
curl -H "Authorization: Bearer $TOKEN" "$URL" \
|
||||
--get \
|
||||
--data-urlencode "title=$TITLE" \
|
||||
--data-urlencode "body=$BODY" \
|
||||
--data-urlencode "jq=$JQ_EXPR" \
|
||||
--data-urlencode "type=$TYPE"
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
svc_name = "rallly";
|
||||
svc_domain = "${svc_name}.${config.mine.shared.settings.domain}";
|
||||
|
||||
psqlSocket = "/run/postgresql";
|
||||
|
||||
user = "rallly";
|
||||
port = 7384;
|
||||
|
||||
ralllyPkgsOrig = pkgs.callPackage ./../../../../shared/pkgs/rallly {};
|
||||
ralllyPkgs = ralllyPkgsOrig.overrideAttrs (old: {
|
||||
patches = (old.patches or []) ++ [
|
||||
./patches/remove-login-register.patch
|
||||
];
|
||||
});
|
||||
in {
|
||||
# setup rallly service
|
||||
systemd.services.rallly = {
|
||||
description = "rallly";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "networking.target" ];
|
||||
|
||||
# configuration
|
||||
environment = let
|
||||
rallly-prisma-engines = ralllyPkgs.passthru.rallly-prisma-engines;
|
||||
in rec {
|
||||
HOSTNAME = "localhost";
|
||||
PORT = builtins.toString port;
|
||||
DATABASE_URL = "postgresql://${user}@localhost/${user}?host=${psqlSocket}";
|
||||
NEXT_PUBLIC_BASE_URL = "https://${svc_domain}";
|
||||
NEXTAUTH_URL = NEXT_PUBLIC_BASE_URL;
|
||||
# SECRET_PASSWORD = "specified-in-env";
|
||||
|
||||
# limit signup even further
|
||||
ALLOWED_EMAILS = "*@${config.mine.shared.settings.domain}";
|
||||
|
||||
# email
|
||||
SUPPORT_EMAIL = "${svc_name}@${config.mine.shared.settings.domain}";
|
||||
SMTP_HOST = config.mine.shared.settings.mail.domain_smtp;
|
||||
SMTP_PORT = builtins.toString config.mine.shared.settings.mail.ports.submissions;
|
||||
SMTP_SECURE = "true";
|
||||
SMTP_USER = svc_name;
|
||||
# SMTP_PWD = "specified-in-env";
|
||||
|
||||
|
||||
# OIDC
|
||||
OIDC_NAME = "Authelia";
|
||||
OIDC_DISCOVERY_URL = "https://${config.mine.shared.settings.authelia.domain}/.well-known/openid-configuration";
|
||||
OIDC_CLIENT_ID = "rallly";
|
||||
# OIDC_CLIENT_SECRET = "specified-in-env";
|
||||
|
||||
# prisma things (database will not work without, needs to match version in rallly deps as well)
|
||||
PRISMA_SCHEMA_ENGINE_BINARY = "${rallly-prisma-engines}/bin/schema-engine";
|
||||
PRISMA_QUERY_ENGINE_BINARY = "${rallly-prisma-engines}/bin/query-engine";
|
||||
PRISMA_QUERY_ENGINE_LIBRARY = "${rallly-prisma-engines}/lib/libquery_engine.node";
|
||||
PRISMA_INTROSPECTION_ENGINE_BINARY = "${rallly-prisma-engines}/bin/introspection-engine";
|
||||
PRISMA_FMT_BINARY = "${rallly-prisma-engines}/bin/prisma-fmt";
|
||||
};
|
||||
|
||||
# add, otherwise we get warnings
|
||||
path = [ pkgs.openssl ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStartPre = [
|
||||
# clear cache on each boot, otherwise we might have
|
||||
# issues when updating it.
|
||||
"${pkgs.findutils}/bin/find -L /var/cache/${svc_name} -mindepth 1 -delete"
|
||||
|
||||
# run db migration each boot
|
||||
"${ralllyPkgs}/bin/rallly-prisma migrate deploy"
|
||||
];
|
||||
ExecStart = "${ralllyPkgs}/bin/rallly";
|
||||
|
||||
# secret configurations
|
||||
EnvironmentFile = [ config.age.secrets.rallly-env.path ];
|
||||
|
||||
CacheDirectory = svc_name;
|
||||
CacheDirectoryMode = "0750";
|
||||
|
||||
User = user;
|
||||
DynamicUser = true;
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
|
||||
# setup postgresql
|
||||
services.postgresql = {
|
||||
ensureDatabases = [ user ];
|
||||
ensureUsers = [{
|
||||
name = user;
|
||||
ensureDBOwnership = true;
|
||||
}];
|
||||
};
|
||||
|
||||
# setup ldap user for email
|
||||
services.lldap.provision.users = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
"${svc_name}" = llib.mkProvisionUserSystem "${svc_name}" config.age.secrets.rallly-ldap-pass.path;
|
||||
});
|
||||
|
||||
# authelia
|
||||
services.authelia.instances.main.settings.identity_providers.oidc.clients = [{
|
||||
client_id = "rallly";
|
||||
client_name = "Rallly";
|
||||
client_secret = "$pbkdf2-sha512$310000$KB4UqeuVr86lEOoISSE92w$i2YGpz3wRwceiRfYnMUhZ0MboutkDPPYVWnXqiw6tUt./mgZ5kfV1ES.kcdsHhMdavhCrJfWvVTPQRJKImuUrQ";
|
||||
consent_mode = "implicit";
|
||||
redirect_uris = [ "https://${svc_domain}/api/auth/callback/oidc" ];
|
||||
scopes = [
|
||||
"openid"
|
||||
"email"
|
||||
"profile"
|
||||
];
|
||||
}];
|
||||
|
||||
# nginx
|
||||
services.nginx.virtualHosts."${svc_domain}" = config.mine.shared.lib.authelia.mkProtectedWebsite {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:${builtins.toString port}";
|
||||
};
|
||||
|
||||
# try to disable registration
|
||||
locations."/api/trpc/auth.requestRegistration" = {
|
||||
root = pkgs.writeTextDir "index.html" ''
|
||||
NO REGISTRATION!!
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# meta information!
|
||||
mine.shared.meta.rallly = {
|
||||
name = "Rallly";
|
||||
description = ''Rallly is an open-source scheduling and collaboration tool designed to make organizing events and meetings easier. Please do not try to use the register or normal login, only try to sign in using the SSO method. '';
|
||||
url = "https://${svc_domain}";
|
||||
|
||||
package = let
|
||||
pkg = ralllyPkgs;
|
||||
in {
|
||||
name = pkg.pname;
|
||||
version = pkg.version;
|
||||
meta = pkg.meta;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
diff --git a/apps/web/src/app/[locale]/(auth)/login/login-form.tsx b/apps/web/src/app/[locale]/(auth)/login/login-form.tsx
|
||||
index d4a2adcf..8137a790 100644
|
||||
--- a/apps/web/src/app/[locale]/(auth)/login/login-form.tsx
|
||||
+++ b/apps/web/src/app/[locale]/(auth)/login/login-form.tsx
|
||||
@@ -159,45 +159,7 @@ export function LoginForm() {
|
||||
}
|
||||
})}
|
||||
>
|
||||
- <div className="mb-1 text-2xl font-bold">{t("login")}</div>
|
||||
- <p className="mb-4 text-gray-500">
|
||||
- {t("stepSummary", {
|
||||
- current: 1,
|
||||
- total: 2,
|
||||
- })}
|
||||
- </p>
|
||||
- <fieldset className="mb-2.5">
|
||||
- <label htmlFor="email" className="mb-1 text-gray-500">
|
||||
- {t("email")}
|
||||
- </label>
|
||||
- <Input
|
||||
- className="w-full"
|
||||
- id="email"
|
||||
- size="lg"
|
||||
- error={!!formState.errors.email}
|
||||
- autoFocus={true}
|
||||
- disabled={formState.isSubmitting}
|
||||
- placeholder={t("emailPlaceholder")}
|
||||
- {...register("email", { validate: validEmail })}
|
||||
- />
|
||||
- {formState.errors.email?.message ? (
|
||||
- <div className="mt-2 text-sm text-rose-500">
|
||||
- {formState.errors.email.message}
|
||||
- </div>
|
||||
- ) : null}
|
||||
- </fieldset>
|
||||
<div className="flex flex-col gap-2">
|
||||
- <Button
|
||||
- loading={formState.isSubmitting}
|
||||
- type="submit"
|
||||
- size="lg"
|
||||
- variant="primary"
|
||||
- className=""
|
||||
- >
|
||||
- {t("loginWith", {
|
||||
- provider: t("email"),
|
||||
- })}
|
||||
- </Button>
|
||||
{error === "OAuthAccountNotLinked" ? (
|
||||
<Alert icon={AlertTriangleIcon} variant="destructive">
|
||||
<AlertTitle>
|
||||
@@ -216,12 +178,6 @@ export function LoginForm() {
|
||||
) : null}
|
||||
{alternativeLoginMethods.length > 0 ? (
|
||||
<>
|
||||
- <div className="relative my-4">
|
||||
- <hr className="border-grey-500 absolute top-1/2 w-full border-t" />
|
||||
- <span className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 transform bg-white px-2 text-center text-xs uppercase text-gray-400">
|
||||
- {t("or", { defaultValue: "Or" })}
|
||||
- </span>
|
||||
- </div>
|
||||
<div className="grid gap-2.5">
|
||||
{alternativeLoginMethods.map((method, i) => (
|
||||
<Button size="lg" key={i} onClick={method.login}>
|
||||
diff --git a/apps/web/src/app/[locale]/(auth)/login/page.tsx b/apps/web/src/app/[locale]/(auth)/login/page.tsx
|
||||
index 10caefed..28d6c85a 100644
|
||||
--- a/apps/web/src/app/[locale]/(auth)/login/page.tsx
|
||||
+++ b/apps/web/src/app/[locale]/(auth)/login/page.tsx
|
||||
@@ -13,16 +13,6 @@ export default async function LoginPage({ params }: { params: Params }) {
|
||||
<AuthCard>
|
||||
<LoginForm />
|
||||
</AuthCard>
|
||||
- <div className="mt-4 pt-4 text-center text-gray-500 sm:text-base">
|
||||
- <Trans
|
||||
- t={t}
|
||||
- i18nKey="notRegistered"
|
||||
- defaults="Don't have an account? <a>Register</a>"
|
||||
- components={{
|
||||
- a: <Link href="/register" className="text-link" />,
|
||||
- }}
|
||||
- />
|
||||
- </div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -33,8 +33,8 @@ in {
|
|||
|
||||
# meta
|
||||
mine.shared.meta.searx = {
|
||||
name = "searXNG";
|
||||
description = "We host our own searXNG, use it to search the web!";
|
||||
name = "searX";
|
||||
description = "We host our own searX, use it to search the web!";
|
||||
url = "https://${svc_domain}";
|
||||
|
||||
package = let
|
||||
|
|
|
@ -50,10 +50,7 @@ in {
|
|||
filter = let
|
||||
_mkFilter = attrs: ph: config.mine.shared.lib.ldap.mkFilter (lconfig: llib:
|
||||
llib.mkAnd [
|
||||
(llib.mkOr [
|
||||
(llib.mkGroup lconfig.groups.member)
|
||||
(llib.mkGroup lconfig.groups.system_mail)
|
||||
])
|
||||
(llib.mkGroup lconfig.groups.member)
|
||||
(llib.mkOr (lib.forEach attrs (v: llib.mkSearch v ph)))
|
||||
]
|
||||
);
|
||||
|
@ -61,21 +58,22 @@ in {
|
|||
attrs = config.mine.shared.settings.ldap.attr // { emailAlias = "mailAlias"; emailList = "mailList"; };
|
||||
in {
|
||||
name = _mkFilter [ attrs.uid ] "?";
|
||||
email = _mkFilter [ attrs.membermail ] "?";
|
||||
email = _mkFilter [ attrs.email attrs.emailAlias attrs.emailList ] "?";
|
||||
verify = _mkFilter [ attrs.email attrs.emailAlias ] "*?*";
|
||||
expand = _mkFilter [ attrs.emailList ] "?";
|
||||
domains = _mkFilter [ attrs.email attrs.emailAlias ] "*@?";
|
||||
};
|
||||
|
||||
attributes = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
name = lconfig.attr.uid;
|
||||
# name = lconfig.attr.member_mail;
|
||||
description = lconfig.attr.firstname;
|
||||
email = lconfig.attr.membermail;
|
||||
quota = lconfig.attr.membermaildiskquota;
|
||||
attributes = {
|
||||
name = "uid";
|
||||
class = "objectClass";
|
||||
description = "givenName";
|
||||
secret = "uid";
|
||||
groups = "memberOf";
|
||||
# we dont have access to this in lldap
|
||||
# secret = lconfig.attr.stalwart_secret;
|
||||
});
|
||||
|
||||
email = "mail";
|
||||
# email-alias = "mailAlias";
|
||||
# quota = "diskQuota";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
@ -148,10 +146,8 @@ in {
|
|||
|
||||
# need to change group to stalwart-mail for cert + add nginx to stalwart-mail group to do HTTP ACME
|
||||
users.users.nginx.extraGroups = [ stalwart_group ];
|
||||
security.acme.certs."${svc_domain}" = {
|
||||
group = stalwart_group;
|
||||
reloadServices = [ config.systemd.services.stalwart-mail.name ];
|
||||
};
|
||||
security.acme.certs."${svc_domain}".group = stalwart_group;
|
||||
|
||||
|
||||
# setup secrets for stalwart
|
||||
# setup access to ldap bind user credential
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
svc_domain = "uptime-kuma.${config.mine.shared.settings.domain}";
|
||||
in {
|
||||
services.uptime-kuma = {
|
||||
enable = true;
|
||||
appriseSupport = true;
|
||||
|
||||
package = pkgs.uptime-kuma.overrideAttrs (old: rec {
|
||||
pname = "uptime-kuma";
|
||||
version = "2.0.0-dev";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "M1CK431";
|
||||
repo = "uptime-kuma";
|
||||
rev = "5a16af40fdddcaa61d197242840344804a246d01";
|
||||
hash = "sha256-W7ieVrfm/SZU/MNB7dJW3V3vq0RBrAJVqv0gK7H4Xik=";
|
||||
};
|
||||
npmDepsHash = "sha256-Q2u6ClG6g8yoGvSJ/LGlKTL4XkJGWY+DAojpM1xBwQ0=";
|
||||
npmDeps = pkgs.fetchNpmDeps {
|
||||
inherit src;
|
||||
name = "${pname}-${version}-npm-deps";
|
||||
hash = npmDepsHash;
|
||||
};
|
||||
patches = [
|
||||
(pkgs.writeText "uptime-kuma-database-writeable.patch" ''
|
||||
diff --git a/server/database.js b/server/database.js
|
||||
index 3374aff9..9e890d28 100644
|
||||
--- a/server/database.js
|
||||
+++ b/server/database.js
|
||||
@@ -221,6 +221,7 @@ class Database {
|
||||
if (! fs.existsSync(Database.sqlitePath)) {
|
||||
log.info("server", "Copying Database");
|
||||
fs.copyFileSync(Database.templatePath, Database.sqlitePath);
|
||||
+ fs.chmodSync(Database.path, 0o640);
|
||||
}
|
||||
|
||||
const Dialect = require("knex/lib/dialects/sqlite3/index.js");
|
||||
'')
|
||||
# TODO(eyJhb): do we really want this?
|
||||
(pkgs.writeText "uptime-kuma-disable-metrics-auth.patch" ''
|
||||
diff --git a/server/server.js b/server/server.js
|
||||
index db58ae82..d650a42a 100644
|
||||
--- a/server/server.js
|
||||
+++ b/server/server.js
|
||||
@@ -292,7 +292,7 @@ let needSetup = false;
|
||||
|
||||
// Prometheus API metrics /metrics
|
||||
// With Basic Auth using the first user's username/password
|
||||
- app.get("/metrics", apiAuth, prometheusAPIMetrics());
|
||||
+ app.use("/metrics", prometheusAPIMetrics());
|
||||
|
||||
app.use("/", expressStaticGzip("dist", {
|
||||
enableBrotli: true,
|
||||
'')
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
# setup persistence
|
||||
environment.persistence.root.directories = [
|
||||
{ directory = "/var/lib/private/uptime-kuma"; mode = "0700"; }
|
||||
];
|
||||
|
||||
|
||||
# setup ldap user for email
|
||||
services.lldap.provision.users = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
uptime-kuma = llib.mkProvisionUserSystem "uptime-kuma" config.age.secrets.uptime-kuma-ldap-pass.path;
|
||||
});
|
||||
|
||||
# nginx
|
||||
services.nginx.virtualHosts."${svc_domain}" = config.mine.shared.lib.authelia.mkProtectedWebsite {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/" = config.mine.shared.lib.authelia.mkProtectedLocation {
|
||||
proxyPass = "http://localhost:${builtins.toString config.services.uptime-kuma.settings.PORT}";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
mine.shared.meta.uptime-kuma = {
|
||||
name = "Uptime Kuma";
|
||||
description = ''Fancy self-hosted monitoring tool, which supports VARIOUS methods of monitoring, as well as getting notifications. Multiple users is not officially support, so reach out to admins, and they will create a user for you. Abuse will NOT be tolerated. We have a SMTP account associated with Uptime Kuma, ask for details on how to use this (you're also allowed to to your own member email).'';
|
||||
url = "https://${svc_domain}";
|
||||
|
||||
package = let
|
||||
pkg = config.services.uptime-kuma.package;
|
||||
in {
|
||||
name = pkg.pname;
|
||||
version = pkg.version;
|
||||
meta = pkg.meta;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
svc_domain = "vikunja.${config.mine.shared.settings.domain}";
|
||||
vikunjaOIDCName = "authelia";
|
||||
in {
|
||||
services.vikunja = {
|
||||
enable = true;
|
||||
|
||||
package = pkgs.vikunja.overrideAttrs (old: {
|
||||
# TODO(eyJhb): remove once vikunja updates past 0.24.6
|
||||
# https://github.com/go-vikunja/vikunja/issues/623
|
||||
patches = (old.patches or []) ++ [
|
||||
(pkgs.writeText "vikunja-clientsecret-envvar.patch" ''
|
||||
diff --git a/pkg/modules/auth/openid/providers.go b/pkg/modules/auth/openid/providers.go
|
||||
index 5e14c1b31..d9a5215c1 100644
|
||||
--- a/pkg/modules/auth/openid/providers.go
|
||||
+++ b/pkg/modules/auth/openid/providers.go
|
||||
@@ -17,6 +17,8 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
+ "fmt"
|
||||
+ "os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -139,6 +141,10 @@ func getProviderFromMap(pi map[string]interface{}) (provider *Provider, err erro
|
||||
Scope: scope,
|
||||
}
|
||||
|
||||
+ if clientSecret, ok := os.LookupEnv(fmt.Sprintf("VIKUNJA_AUTH_OPENID_PROVIDERS_%s_CLIENTSECRET", strings.ToUpper(provider.Name))); ok {
|
||||
+ provider.ClientSecret = clientSecret
|
||||
+ }
|
||||
+
|
||||
cl, is := pi["clientid"].(int)
|
||||
if is {
|
||||
provider.ClientID = strconv.Itoa(cl)
|
||||
'')
|
||||
];
|
||||
});
|
||||
|
||||
frontendScheme = "https";
|
||||
frontendHostname = svc_domain;
|
||||
|
||||
database = {
|
||||
type = "postgres";
|
||||
host = "/run/postgresql";
|
||||
};
|
||||
|
||||
environmentFiles = [
|
||||
config.age.secrets.vikunja-env.path
|
||||
];
|
||||
|
||||
settings = {
|
||||
service.enableregistration = false;
|
||||
auth.local.enabled = false;
|
||||
|
||||
auth.openid = {
|
||||
enabled = true;
|
||||
providers = [{
|
||||
key = "authelia";
|
||||
name = vikunjaOIDCName;
|
||||
clientid = "vikunja";
|
||||
authurl = "https://${config.mine.shared.settings.authelia.domain}";
|
||||
clientsecret = "not-used-but-needs-to-be-set";
|
||||
}];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# setup for oidc
|
||||
services.authelia.instances.main.settings.identity_providers.oidc.clients = [{
|
||||
client_id = "vikunja";
|
||||
client_name = "Vikunja";
|
||||
client_secret = "$pbkdf2-sha512$310000$GjslCZ8GAperXUFzmFGslA$QsQHK.HbuvMIiH5Q2vnM1cYR5N.yNjc6RDNU0RBnqVpJjySvjZBQa1dteceTNtvgQz7hXPlnSpRzKTGYj/k.Hw";
|
||||
consent_mode = "implicit";
|
||||
redirect_uris = [ "https://${svc_domain}/auth/openid/${vikunjaOIDCName}" ];
|
||||
scopes = [
|
||||
"openid"
|
||||
"profile"
|
||||
"email"
|
||||
];
|
||||
}];
|
||||
|
||||
# persistence
|
||||
environment.persistence.root.directories = [
|
||||
{ directory = "/var/lib/private/vikunja"; mode = "0700"; }
|
||||
];
|
||||
|
||||
# setup postgresql
|
||||
services.postgresql = let
|
||||
user = config.services.vikunja.database.user;
|
||||
in {
|
||||
ensureDatabases = [ user ];
|
||||
ensureUsers = [{
|
||||
name = user;
|
||||
ensureDBOwnership = true;
|
||||
}];
|
||||
};
|
||||
|
||||
# nginx
|
||||
services.nginx.virtualHosts."${svc_domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/".proxyPass = "http://localhost:${builtins.toString config.services.vikunja.port}";
|
||||
};
|
||||
|
||||
# meta
|
||||
mine.shared.meta.vikunja = rec {
|
||||
name = "Vikunja";
|
||||
description = ''
|
||||
The to-do app to organize your life.
|
||||
'';
|
||||
url = "https://${svc_domain}";
|
||||
|
||||
package = let
|
||||
pkg = config.services.vikunja.package;
|
||||
in {
|
||||
name = pkg.pname;
|
||||
version = pkg.version;
|
||||
meta = pkg.meta;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
{ config, lib, ... }:
|
||||
{ config, ... }:
|
||||
|
||||
let
|
||||
svc_domain = "wger.${config.mine.shared.settings.domain}";
|
||||
|
@ -18,52 +18,30 @@ in {
|
|||
|
||||
# wger specific settings
|
||||
wgerSettings = {
|
||||
EMAIL_FROM = "wger Workout Manager <wger@${config.mine.shared.settings.domain}>";
|
||||
EMAIL_FROM = "wger Workout Manager <wger@${svc_domain}>";
|
||||
|
||||
# use authelia for authentication (disable guest users + regisration)
|
||||
AUTH_PROXY_HEADER = config.mine.shared.lib.authelia.protectedHeaders.username;
|
||||
ALLOW_GUEST_USERS = false;
|
||||
ALLOW_REGISTRATION = false;
|
||||
};
|
||||
|
||||
# django specific settings
|
||||
djangoSettings = let
|
||||
headerToDjangoHeader = v: "HTTP_" + (lib.toUpper ((lib.replaceStrings [ "-" ] [ "_" ] v)));
|
||||
in rec {
|
||||
djangoSettings = rec {
|
||||
# setup site stuff
|
||||
SITE_URL = "https://${svc_domain}";
|
||||
CSRF_TRUSTED_ORIGINS = [ "https://${svc_domain}" ];
|
||||
ALLOWED_HOSTS = [ svc_domain ];
|
||||
|
||||
# proxy auth
|
||||
AUTH_PROXY_HEADER = headerToDjangoHeader config.mine.shared.lib.authelia.protectedHeaders.username;
|
||||
AUTH_PROXY_USER_EMAIL_HEADER = headerToDjangoHeader config.mine.shared.lib.authelia.protectedHeaders.email;
|
||||
AUTH_PROXY_USER_NAME_HEADER = headerToDjangoHeader config.mine.shared.lib.authelia.protectedHeaders.name;
|
||||
AUTH_PROXY_TRUSTED_IPS = [ "127.0.0.1" ];
|
||||
AUTH_PROXY_CREATE_UNKNOWN_USER = true;
|
||||
|
||||
# setup email
|
||||
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend";
|
||||
EMAIL_HOST = config.mine.shared.settings.mail.domain_smtp;
|
||||
EMAIL_PORT = config.mine.shared.settings.mail.ports.submissions;
|
||||
EMAIL_USE_SSL = true;
|
||||
EMAIL_HOST_USER = "wger";
|
||||
EMAIL_HOST_PASSWORD = "file:${config.age.secrets.wger-ldap-pass.path}";
|
||||
EMAIL_HOST_PASSWORD = "$EMAIL_HOST_PASSWORD";
|
||||
EMAIL_FROM_ADDRESS = config.services.wger.wgerSettings.EMAIL_FROM;
|
||||
EMAIL_PAGE_DOMAIN = SITE_URL;
|
||||
|
||||
# LOGGING = {
|
||||
# version = 1;
|
||||
# disable_existing_loggers = false;
|
||||
# formatters.simple.format = "%(levelname)s %(asctime)s %(module)s %(message)s";
|
||||
# handlers.console = {
|
||||
# level = "DEBUG";
|
||||
# class = "logging.StreamHandler";
|
||||
# formatter = "simple";
|
||||
# };
|
||||
# loggers."" = {
|
||||
# handlers = ["console"];
|
||||
# level = "DEBUG";
|
||||
# };
|
||||
# };
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -84,14 +62,6 @@ in {
|
|||
locations."/api".proxyPass = "http://localhost:${builtins.toString port}";
|
||||
};
|
||||
|
||||
# setup lldap user for wger that can send emails
|
||||
services.lldap.provision.users = config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
|
||||
wger = llib.mkProvisionUserSystem "wger" config.age.secrets.wger-ldap-pass.path;
|
||||
});
|
||||
|
||||
# setup permissions
|
||||
age.secrets.wger-ldap-pass.owner = config.services.wger.user;
|
||||
|
||||
# metadata
|
||||
mine.shared.meta.wger = {
|
||||
name = "Wger";
|
||||
|
|
|
@ -1,104 +1,65 @@
|
|||
{
|
||||
lib
|
||||
, python
|
||||
, fetchFromGitHub
|
||||
, buildPythonPackage
|
||||
, callPackage
|
||||
, writeText
|
||||
, fetchpatch
|
||||
# build systems
|
||||
, hatchling
|
||||
# deps
|
||||
, bleach
|
||||
, celery
|
||||
, django-crispy-bootstrap5
|
||||
, django
|
||||
, django-activity-stream ? callPackage ./django-activity-stream.nix {}
|
||||
, django-axes
|
||||
, django-compressor
|
||||
, django-cors-headers
|
||||
, django-crispy-forms
|
||||
, django-email-verification ? callPackage ./django-email-verification.nix {}
|
||||
, django-environ
|
||||
, django-filter
|
||||
, django-formtools
|
||||
, django-prometheus
|
||||
, django-recaptcha ? callPackage ./django-recaptcha.nix {}
|
||||
, django-simple-history
|
||||
, django-sortedm2m ? callPackage ./django-sortedm2m.nix {}
|
||||
, django-storages
|
||||
, djangorestframework
|
||||
, djangorestframework-simplejwt
|
||||
, drf-spectacular
|
||||
, easy-thumbnails
|
||||
, flower
|
||||
, fontawesomefree
|
||||
, icalendar
|
||||
, invoke
|
||||
, openfoodfacts ? callPackage ./openfoodfacts.nix {}
|
||||
, pillow
|
||||
, reportlab
|
||||
, requests
|
||||
, tqdm
|
||||
, tzdata
|
||||
# extra deps
|
||||
, redis
|
||||
, django-redis
|
||||
, drf-spectacular-sidecar
|
||||
, django-bootstrap-breadcrumbs ? callPackage ./django-bootstrap-breadcrumbs.nix {}
|
||||
, psycopg2
|
||||
lib,
|
||||
python3,
|
||||
fetchFromGitHub,
|
||||
callPackage,
|
||||
writeText,
|
||||
fetchpatch,
|
||||
}:
|
||||
|
||||
let
|
||||
frontend = callPackage ./frontend.nix {};
|
||||
in buildPythonPackage rec {
|
||||
in python3.pkgs.buildPythonPackage rec {
|
||||
pname = "wger";
|
||||
version = "2.3";
|
||||
version = "unstable-2024-12-30";
|
||||
pyproject = true;
|
||||
|
||||
# src = fetchFromGitHub {
|
||||
# owner = "wger-project";
|
||||
# repo = "wger";
|
||||
# rev = version;
|
||||
# hash = "sha256-riJyVl0/GwAGkcHVzkJc666owPk1E4ca8DV5qTjEbjk=";
|
||||
# };
|
||||
# TMP: until it's merged
|
||||
src = fetchFromGitHub {
|
||||
owner = "eyJhb";
|
||||
owner = "wger-project";
|
||||
repo = "wger";
|
||||
rev = "proxyauthheaderv2";
|
||||
hash = "sha256-9GMU7CSMKcgBFYrUh6m9LFiJQ7XLkhaJ8EPt+FSZFqY=";
|
||||
rev = "30871d621fa6e732f07bd33d4112b99539974e5f";
|
||||
hash = "sha256-WcycWbzKug8vUfNnUDhvgmj1kUCpT1P1YJBfdIC1H9g=";
|
||||
};
|
||||
# src = /tmp/wger;
|
||||
|
||||
build-system = [
|
||||
hatchling
|
||||
python3.pkgs.hatchling
|
||||
];
|
||||
|
||||
patches = [
|
||||
./patches/pyproject.patch
|
||||
./patches/manage.patch
|
||||
./patches/exercises-no-gifs.patch
|
||||
|
||||
# adds support for proxy auth header
|
||||
(fetchpatch {
|
||||
url = "https://github.com/wger-project/wger/pull/1859/commits/d46d469fa802890d7162b07c098802810fc8417c.patch";
|
||||
sha256 = "sha256-D+3FmiSokJe9iSJz7ZbRzS+kuP3yV64XhKnQ4Oh5x8c=";
|
||||
})
|
||||
];
|
||||
|
||||
propagatedBuildInputs = [
|
||||
# dependencies = with python3.pkgs; [
|
||||
propagatedBuildInputs = with python3.pkgs; [
|
||||
bleach
|
||||
celery
|
||||
django-crispy-bootstrap5
|
||||
django
|
||||
django-activity-stream
|
||||
# django-activity-stream
|
||||
(python3.pkgs.callPackage ./django-activity-stream.nix {})
|
||||
django-axes
|
||||
django-compressor
|
||||
django-cors-headers
|
||||
django-crispy-forms
|
||||
django-email-verification
|
||||
# django-email-verification
|
||||
(python3.pkgs.callPackage ./django-email-verification.nix {})
|
||||
django-environ
|
||||
django-filter
|
||||
django-formtools
|
||||
django-prometheus
|
||||
django-recaptcha
|
||||
# django-recaptcha
|
||||
(python3.pkgs.callPackage ./django-recaptcha.nix {})
|
||||
django-simple-history
|
||||
django-sortedm2m
|
||||
# django-sortedm2m
|
||||
(python3.pkgs.callPackage ./django-sortedm2m.nix {})
|
||||
django-storages
|
||||
djangorestframework
|
||||
djangorestframework-simplejwt
|
||||
|
@ -108,7 +69,8 @@ in buildPythonPackage rec {
|
|||
fontawesomefree
|
||||
icalendar
|
||||
invoke
|
||||
openfoodfacts
|
||||
# openfoodfacts
|
||||
(python3.pkgs.callPackage ./openfoodfacts.nix {})
|
||||
pillow
|
||||
reportlab
|
||||
requests
|
||||
|
@ -119,7 +81,7 @@ in buildPythonPackage rec {
|
|||
redis
|
||||
django-redis
|
||||
drf-spectacular-sidecar
|
||||
django-bootstrap-breadcrumbs
|
||||
(python3.pkgs.callPackage ./django-bootstrap-breadcrumbs.nix {})
|
||||
psycopg2
|
||||
];
|
||||
|
||||
|
@ -140,7 +102,7 @@ in buildPythonPackage rec {
|
|||
'';
|
||||
in ''
|
||||
# copy over static yarn things
|
||||
# cp -a ${frontend}/static/yarn $out/${python.sitePackages}/wger/core/static
|
||||
# cp -a ${frontend}/static/yarn $out/${python3.sitePackages}/wger/core/static
|
||||
cp -a ${frontend}/static/yarn wger/core/static
|
||||
|
||||
python3 -m wger create-settings -s $PWD/tmp_settings.py
|
||||
|
@ -148,18 +110,18 @@ in buildPythonPackage rec {
|
|||
mkdir tmpstatic
|
||||
pushd tmpstatic
|
||||
|
||||
static=. WGER_SETTINGS=../tmp_settings.py python ../manage.py collectstatic --no-input
|
||||
static=. WGER_SETTINGS=../tmp_settings.py python ../manage.py compress --force
|
||||
static=. WGER_SETTINGS=../tmp_settings.py python3 ../manage.py collectstatic --no-input
|
||||
static=. WGER_SETTINGS=../tmp_settings.py python3 ../manage.py compress --force
|
||||
|
||||
popd
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
rm -rf $out/${python.sitePackages}/wger/core/static
|
||||
cp -a tmpstatic $out/${python.sitePackages}/wger/core/static
|
||||
rm -rf $out/${python3.sitePackages}/wger/core/static
|
||||
cp -a tmpstatic $out/${python3.sitePackages}/wger/core/static
|
||||
|
||||
mkdir $out/share
|
||||
cp -a $out/${python.sitePackages}/wger/core/static $out/share
|
||||
cp -a $out/${python3.sitePackages}/wger/core/static $out/share
|
||||
'';
|
||||
|
||||
pythonImportsCheck = [
|
||||
|
|
|
@ -7,7 +7,7 @@ let
|
|||
|
||||
defaultUser = "wger";
|
||||
|
||||
wgerpkgs = pkgs.python3Packages.callPackage ./default.nix {};
|
||||
wgerpkgs = pkgs.callPackage ./default.nix {};
|
||||
|
||||
# generate settings files
|
||||
settingsFormat = pkgs.formats.json {};
|
||||
|
@ -23,8 +23,6 @@ let
|
|||
for k, v in json.load(f).items():
|
||||
if isinstance(v, str) and v.startswith("$"):
|
||||
v = os.environ[v[1:]]
|
||||
if isinstance(v, str) and v.startswith("file:"):
|
||||
v = open(v[5:], "r").read().strip()
|
||||
|
||||
globals()[k] = v
|
||||
|
||||
|
@ -32,8 +30,6 @@ let
|
|||
for k, v in json.load(f).items():
|
||||
if isinstance(v, str) and v.startswith("$"):
|
||||
v = os.environ[v[1:]]
|
||||
if isinstance(v, str) and v.startswith("file:"):
|
||||
v = open(v[5:], "r").read().strip()
|
||||
|
||||
WGER_SETTINGS[k] = v
|
||||
'';
|
||||
|
@ -210,7 +206,7 @@ in
|
|||
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||
gunicorn
|
||||
# TODO: fix this, it should work with cfg.package
|
||||
(ps.callPackage ./default.nix {})
|
||||
(pkgs.python3Packages.callPackage ./default.nix {})
|
||||
]);
|
||||
in ''
|
||||
# initial setup
|
||||
|
@ -220,7 +216,7 @@ in
|
|||
|
||||
# run server
|
||||
# ${cfg.package}/bin/wger start -s ${settingsFile}
|
||||
PYTHONPATH="${pythonEnv}/${pkgs.python3.sitePackages}:${settingsFileDir}" ${pythonEnv}/bin/gunicorn wger.wsgi:application --bind ${cfg.address}:${builtins.toString cfg.port}
|
||||
PYTHONPATH="${pythonEnv}/${pkgs.python3.sitePackages}:${settingsFileDir}" ${pythonEnv}/bin/gunicorn wger.wsgi:application --reload --bind ${cfg.address}:${builtins.toString cfg.port}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
diff --git a/pyproject.toml b/pyproject.toml
|
||||
index 354f492f6..7163c4ffe 100644
|
||||
index f10460b1e..62377bd9c 100644
|
||||
--- a/pyproject.toml
|
||||
+++ b/pyproject.toml
|
||||
@@ -37,6 +37,8 @@ Changelog = "https://wger.readthedocs.io/en/latest/changelog.html"
|
||||
@@ -35,7 +35,8 @@ Issues = "https://github.com/wger-project/wger/issues"
|
||||
Changelog = "https://wger.readthedocs.io/en/latest/changelog.html"
|
||||
|
||||
[project.scripts]
|
||||
wger = "wger.__main__:main"
|
||||
-wger = "wger.__main__:main"
|
||||
+wger = "wger.tasks:main"
|
||||
+manage = "wger.manage:main"
|
||||
+
|
||||
|
||||
[tool.setuptools]
|
||||
include-package-data = false
|
||||
@@ -53,6 +55,8 @@ universal = 1
|
||||
# path = "wger/__init__.py"
|
||||
# expression = "get_version()"
|
||||
@@ -47,6 +48,9 @@ dependencies = { file = ["requirements.txt"] }
|
||||
[tool.distutils.bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
+[tool.hatch.build.targets.wheel.force-include]
|
||||
+"wger/settings_global.py" = "wger/settings_global.py"
|
||||
+
|
||||
|
||||
[tool.ruff]
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
|
|
|
@ -1,6 +1,2 @@
|
|||
# Fricloud Server Configuration!
|
||||
Bla bla bla, something better at some point, big TODO.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
group = "secrets-lldap-bind-user-pass";
|
||||
mode = "0440";
|
||||
};
|
||||
lldap-user-emails-env.file = ./lldap/user-emails-env.age;
|
||||
lldap-bind-user-pass-hedgedoc-env.file = ./lldap/bind-user-pass-hedgedoc-env.age;
|
||||
|
||||
# mumble
|
||||
|
@ -33,8 +32,6 @@
|
|||
# nextcloud
|
||||
nextcloud-admin-pass.file = ./nextcloud/admin-pass.age;
|
||||
nextcloud-secrets.file = ./nextcloud/secrets.age;
|
||||
nextcloud-smtp-pass.file = ./nextcloud/smtp-pass.age;
|
||||
nextcloud-serverinfo-token.file = ./nextcloud/serverinfo-token.age;
|
||||
|
||||
# stalwart
|
||||
stalwart-admin-fallback-password.file = ./stalwart/admin-fallback-password.age;
|
||||
|
@ -44,7 +41,6 @@
|
|||
|
||||
# wger
|
||||
wger-env.file = ./wger/env.age;
|
||||
wger-ldap-pass.file = ./wger/ldap-pass.age;
|
||||
|
||||
# restic
|
||||
restic-env.file = ./restic/env.age;
|
||||
|
@ -52,26 +48,6 @@
|
|||
|
||||
# searx
|
||||
searx-env.file = ./searx/env.age;
|
||||
|
||||
# uptime-kuma
|
||||
uptime-kuma-ldap-pass.file = ./uptime-kuma/ldap-pass.age;
|
||||
|
||||
# rallly
|
||||
rallly-ldap-pass.file = ./rallly/ldap-pass.age;
|
||||
rallly-env.file = ./rallly/env.age;
|
||||
|
||||
# notify
|
||||
notify-ldap-pass.file = ./notify/ldap-pass.age;
|
||||
notify-env.file = ./notify/env.age;
|
||||
|
||||
# grafana
|
||||
grafana-authelia-secret.file = ./grafana/authelia-secret.age;
|
||||
|
||||
# drasl
|
||||
drasl-env.file = ./drasl/env.age;
|
||||
|
||||
# vikunja
|
||||
vikunja-env.file = ./vikunja/env.age;
|
||||
};
|
||||
|
||||
users.groups.secrets-lldap-bind-user-pass = {};
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 QSDXqg ebgFyJS5wy6KlYd+FCwIr8E8f9BGXVS+buEXS/+h0xE
|
||||
VsUaM8sZzvhBidHvmhlf8VBEHTWmY1+R/5gKF3BWQyA
|
||||
-> X25519 zKGYd4fUcN0WOm29enxa9sdu2IASPyjZ2RMpH8AAMAA
|
||||
tbZofRRRnTKaKwI5GBw3gf0gvIsWcH3mv8jr4v9Okd4
|
||||
-> ssh-ed25519 n8n9DQ u2gNkt7dggt++rZGevmIKVjX73M9v04opNq2YuAynD8
|
||||
7YHtRXzVmD1LQeJtcWnSsKKUAL/DKTxfGDFUTC+nNMM
|
||||
-> ssh-ed25519 BTp6UA fESw3HOP8rvsUgeDKm+BCT5h5HnMbzjlrzU6en6mfGo
|
||||
Bz0BOmOgDz3wrSaHz7eDe1Y70dpzuRLOdjALmCN14UA
|
||||
--- vDJkQ31TTcesjWK6t5LNIjPQp3d10i2NRU1lITQDZEI
|
||||
%ü~Òf¶éÁѲ¿qÓ?Gk…ÉF±<46>þç±yª<79>Çâ7ÛÜ“(»¾<<3C>U±åE¯ãH%,ð¹@o€½¤c
|
||||
ìd“Áö=?zS†ÛPŒl¯þµ~…JÌôVØ[JúŽ;”-ßþvfHÙ¢©ô•
|
Binary file not shown.
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 QSDXqg mcA7aWulfqHTARfxzs9ECZaJRMZKLxZgl4uYXrsL6Tk
|
||||
IOKrdtTiG/Wc8qQb5zip1F3B4BHAGkEw8hjz22UY80k
|
||||
-> X25519 kqD2VC9Vw/2rrd/C1TR5He/78anx3UYXNbjs0vNXCz4
|
||||
ZYenf1LK+YAlil/oiZIfGGyaK9S6pt8LLpCbmlaKn9s
|
||||
-> ssh-ed25519 n8n9DQ PlW/1TA71RhclXIC2RlKUUOnqOq3qWy8yshqgM3Nu10
|
||||
2j6c3UjFc/RJJrqeWIezHx53DcPHFPi5a8WXnyqkXhU
|
||||
-> ssh-ed25519 BTp6UA n2idpPd9RFDbzvD2svo3A0NU7kx1nUEYzwFs0gpxn3Q
|
||||
/4F5l1dXBvF0nWXvT8nxPPCAxB4heeUMSBrGMY3gfng
|
||||
--- 7xw3+Ket2jYmH8wsoG2ivWUYLkyoR0et5FELrn+zzMo
|
||||
9XzvèäJºEŠó«‘y⺈†è}\šÙ©‰ï\xÓºeè”11ûõ¯ƒô7XÒÑb%„á Õ˜.…ïj‰!‹Ä6œšBÃ[/ÆÀx!8Àâ‹ÕÔÿÿÍ´¤'2ŠvRúž§4W:]k
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 QSDXqg nLdvh4Rh7NRfCpubsUOaSTwL+uQYa9jpiWWHmq8tBzo
|
||||
jIgDAQZAmpoOqShDWMZZC3m/go+DImfYbg+gOlbbLu4
|
||||
-> X25519 jJ3QUtYdo6FM/xncqZeJMg5JJh2PKhe8rDw46ZrbqWo
|
||||
uoDuPBJDplDoRiJGi2NFNJqDlo/fRGUqPiD0Jk6AX1c
|
||||
-> ssh-ed25519 n8n9DQ +3vT7Jfx+kUFbHbEAWFN0hiDn0c0m+65brjuM5M4HRI
|
||||
+jGGD9trmPr0BV2Ev1PvcdTAbzEyrHtHGleuheuYrIY
|
||||
-> ssh-ed25519 BTp6UA Da7JqYJiJToDKhRelrwbXCj35URUi9T/Zzr0fLAZX1A
|
||||
Kyi0O0Wog/VYlnCezm9qyxHiEU606kVHZfp17NKxXQk
|
||||
--- 2t7lCNkYh/E4RyFx7sAtup5z9z/UFcxvk4XHhfJK+4I
|
||||
òÊ¡
"<22>V˜%µnê¢ú]«„þ·ÁhQYŽs¡Y‚9ÎY®^€rã®ÔÑ6lƒ6*@G{vœRf÷°I–Ðù7
|
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 QSDXqg sB2scYcZRgd5E/6R27PdImZnUWkJupnHczQQYGZsCD0
|
||||
FzInWenN2BSOcxXo+vfTEUuMyRfyAuGqyFSp+ByC/U4
|
||||
-> X25519 a+pbMWxxfI9QYkoXQFojS0FD8WnF+uyGXVlW2zZvaxo
|
||||
xVZQXcCrW7GIPDa6/n+Zh+Iua5wLO886cgn0aFOzGNQ
|
||||
-> ssh-ed25519 n8n9DQ LCG5mLWudd8IIFPB/N2ZjnWmNvClDq4p1VSsmxhVdHA
|
||||
Lv4a9eDu7pZhgYdYziXnIXZEovk0zlkRoweH5zIRW8s
|
||||
-> ssh-ed25519 BTp6UA dQBcqLui7exxZYoPEtOFtDUFXQUhpxf/XOCkrR1fCSI
|
||||
ysZfoPv2q6E0dbHxgdsPqCXs2d7ZUMbTJbma3y285E8
|
||||
--- VhWbBMj96n+u7SuNCMRhI0NF1q4g0yGbyDFL6u5ivrk
|
||||
“„Ú<LûS¨ô¢3øZGò"aÌ„•ZBÉ.Q•¤Ä ½Ã+‡Ôª„<C2AA>½.¹`z$SÑY_“r×ê߉µWГµñVf«™·€Þ¢ÀßÐɰzĺ<]Bß<<3C> »”å‰à;^É^²°®o€*å0úa0ë<7F>‚.6ˆ=k¬Gæê~HÁêŠfb¯0û¦vâÓ 7_¦Äá™~FÎdœÇ•
|
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 QSDXqg zhjn7qGDqDLTSqEDpiY1x5hHaH288k84Gdd0X8GDMio
|
||||
90o18HnPHL1/kBDDVLS3nzGFtk+R4u4kKZd/l5IrkOM
|
||||
-> X25519 BiI48P3ND0r5r8uQNMBj5tRhzqFLvdG/eT0cI8HJ21Q
|
||||
ezuOpM12szomaVkj9K4KyCEoIlzxWti22hWsYtWPuic
|
||||
-> ssh-ed25519 n8n9DQ SlOFCYQM2ANAdx9gijm7/WLQ5/0jOhj5BTxYZe+s4SU
|
||||
nFHlb96Pq3YAKJ6ug7jvbZIykc9nup6MxRd4iEfOPyo
|
||||
-> ssh-ed25519 BTp6UA Ud6jj4XFF5FrBotbDpdjLkvKz/cZckQEBXn3IaV55m0
|
||||
xRIZrraNuGszKWhDLlJW5JswPbtkT1oUvdwPNbm3Bus
|
||||
--- fxKZwnhoQA3D2XfZ7zEAGrQBEmL0VS3f9JykUuvcTeA
|
||||
bÿ\Ð;šKTG‹ßš´NÙsÀi@ó>«]f‘ðq üXpŒtL#ånàÈÝÒrì¨<C3AC>;hIž?F0=°a–
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -28,7 +28,6 @@ in
|
|||
"lldap/admin-user-pass.age".publicKeys = defaultAccess;
|
||||
"lldap/bind-user-pass.age".publicKeys = defaultAccess;
|
||||
"lldap/bind-user-pass-hedgedoc-env.age".publicKeys = defaultAccess;
|
||||
"lldap/user-emails-env.age".publicKeys = defaultAccess;
|
||||
|
||||
# mumble
|
||||
"murmur/env.age".publicKeys = defaultAccess;
|
||||
|
@ -43,8 +42,6 @@ in
|
|||
# nextcloud
|
||||
"nextcloud/admin-pass.age".publicKeys = defaultAccess;
|
||||
"nextcloud/secrets.age".publicKeys = defaultAccess;
|
||||
"nextcloud/smtp-pass.age".publicKeys = defaultAccess;
|
||||
"nextcloud/serverinfo-token.age".publicKeys = defaultAccess;
|
||||
|
||||
# mailserver/stalwart
|
||||
"stalwart/admin-fallback-password.age".publicKeys = defaultAccess;
|
||||
|
@ -54,7 +51,6 @@ in
|
|||
|
||||
# wger
|
||||
"wger/env.age".publicKeys = defaultAccess;
|
||||
"wger/ldap-pass.age".publicKeys = defaultAccess;
|
||||
|
||||
# restic
|
||||
"restic/env.age".publicKeys = defaultAccess;
|
||||
|
@ -62,24 +58,4 @@ in
|
|||
|
||||
# searx
|
||||
"searx/env.age".publicKeys = defaultAccess;
|
||||
|
||||
# uptime-kuma
|
||||
"uptime-kuma/ldap-pass.age".publicKeys = defaultAccess;
|
||||
|
||||
# rallly
|
||||
"rallly/ldap-pass.age".publicKeys = defaultAccess;
|
||||
"rallly/env.age".publicKeys = defaultAccess;
|
||||
|
||||
# notify
|
||||
"notify/ldap-pass.age".publicKeys = defaultAccess;
|
||||
"notify/env.age".publicKeys = defaultAccess;
|
||||
|
||||
# grafana
|
||||
"grafana/authelia-secret.age".publicKeys = defaultAccess;
|
||||
|
||||
# drasl
|
||||
"drasl/env.age".publicKeys = defaultAccess;
|
||||
|
||||
# vikunja
|
||||
"vikunja/env.age".publicKeys = defaultAccess;
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,12 +1,11 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 QSDXqg 1g79p7fDXhx/jHR4Z6PY4MsJyITD84/bimvr0jRcgCQ
|
||||
41z4XNjkwik7rdj9UdJs/ZR+gUGa+rTOXFEQz50UWlo
|
||||
-> X25519 XzyBVMh7elt7LdkzGAG1qz5kiKAZIeFHJeYVhYCh+gY
|
||||
cbEWc7hdQ7ddoBnFRUFYzvunIGn/tNMckaEao7Lcxw4
|
||||
-> ssh-ed25519 n8n9DQ bl0lknR3pVULG/2mRe7rtb6oFjBgr5zVayHM8Oc0dCM
|
||||
gYkyO5PNrzwDMqcS5s5RnXH7kLIw5IdYB9qLzXTraX0
|
||||
-> ssh-ed25519 BTp6UA D36+NXsMYDzeqgFbMTsdTuKWgHbQUcTw0jheGX3ndmw
|
||||
pTtYYl2jLimVJdGAKhRqgcBg6tpg0LOvNnY+QoRgomk
|
||||
--- lsPTx+ZG4T1MFVjue9ceTeieyEI99LF0D9RNBTnZG08
|
||||
ÿƒ¨
|
||||
-¾w¶ðf²Brî6Îö´ßH^»»ÇõÄéPÉEÌ󨞜¬ßà3×B-áû-‚ót?³nd^ë7¬có/[z‡f˜xŠëû÷{ ji1A°BŸÛ¢7Âø*ú
|
||||
-> ssh-ed25519 QSDXqg KGoB/V0cCAZsfVmoLDmA5Xs2HOHqjg54TYqixYQduEw
|
||||
sqDb6QnEbwEncAbxKLRLkjCQIwMLBTNMVcejFOwhZWM
|
||||
-> X25519 o64XZRaiK7ZEquTMmXTyhpdArawiuXC+5W5seFrJclY
|
||||
qTLXrNGMTPAXs5EzMuCiQ07Ho2LT1KTku2f1AlCHPlk
|
||||
-> ssh-ed25519 n8n9DQ a8ESfbksuY++k52UJwTKJtb4/aiYzQqUgyYqfug5oyA
|
||||
bZygFOW6YSg83CmZRpsNDux+UgOxCfja1eQ/R4NyLXM
|
||||
-> ssh-ed25519 BTp6UA yFBZAlGtHV98t6UA8QbELjOW/Pu6KYVPjbXFvijl9m0
|
||||
+eobFp5YNBsr2+10Huimwypn3S4/lc7zoX5Ldko9mhA
|
||||
--- g7w825LgydJlmyZiqnIL0ofUsTn+e47rFmSG8ft6Qqg
|
||||
!lï•:^çÄÙƒ}R&X‚º^_ã213·-éŒË£0ÅnBþ–<C3BE>DK€æ&Ù©Dþ:¾^½ÒUwÃÌóŸ
8(£‡ä X‡¾QZsÖªŒ<C2AA>â^(CÂ!ÍìÊ$ ™Üöý×(‹wÎ8t“ô¾<C3B4>Ñ!Úç²±Ð̈ït;¥ÃNgÚÛ§ˆ<C2A7>Ž[²f+Ù‚Q°
|
|
@ -1,11 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 QSDXqg uYPdHX2B6acTsvvU49DtkE6seTek1FT/+WIeexfKIDI
|
||||
koEouYTBn6/VencZc4HmooytCR7zcdrSL/77ScL47yQ
|
||||
-> X25519 sTTQGj2XPszrqQrUSiXVYlbQsxGYLn9Ee46isVezMQI
|
||||
1obJbY9FMAmCPlPWdaJdCjYm9Z01WdiGv72Dy9NTZNM
|
||||
-> ssh-ed25519 n8n9DQ 5+yZMCzJNRvpLYWDOof40LSVX31DdawJfKzjPhwiUy8
|
||||
bUUVYjDaJ3kvB/gc6wAjLX090YGG4VigulNwc3kioMo
|
||||
-> ssh-ed25519 BTp6UA 2WxuEBEe12Bx0hJaLmfrJhN5HKLZIQtpzekTprvTdTc
|
||||
LdxlDEXOGsYgBB8p+qj/Twv7F1RK6W3DXiM+cwBvU+o
|
||||
--- RdzYVsyAfuRcvj9nz+f57KbfNV+MG5EkIzDmBbzlT1A
|
||||
—ù6T®ñ…ŠMåò*ÞÂtqƒr‹³eIù–/µØ`q¦aÜ<>Áýû-p0ÕJb0ðQ³$~qÁ‹zɃôŽ(
|
|
@ -10,58 +10,47 @@ let
|
|||
-out "$out/ca.pem" -keyout "$out/ca.key"
|
||||
'';
|
||||
in {
|
||||
# block all /metrics endpoints
|
||||
options.services.nginx.virtualHosts = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule {
|
||||
config.locations."/metrics" = lib.mkDefault {
|
||||
extraConfig = "deny all;";
|
||||
};
|
||||
});
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
recommendedOptimisation = true;
|
||||
recommendedTlsSettings = true;
|
||||
recommendedGzipSettings = true;
|
||||
# recommendedBrotliSettings = true;
|
||||
recommendedProxySettings = true;
|
||||
|
||||
# only allow PFS-enabled ciphers with AES256
|
||||
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
|
||||
|
||||
# disable access logs
|
||||
commonHttpConfig= ''
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# setup a default site
|
||||
virtualHosts.default = {
|
||||
default = lib.mkDefault true;
|
||||
addSSL = true;
|
||||
|
||||
sslCertificateKey = "${snakeOilCa}/ca.key";
|
||||
sslCertificate = "${snakeOilCa}/ca.pem";
|
||||
|
||||
root = pkgs.writeTextDir "index.html" ''
|
||||
<html>
|
||||
<head>
|
||||
<title>Nothing to see</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Like I said, nothing to see here</p>
|
||||
</body>
|
||||
</html>
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
recommendedOptimisation = true;
|
||||
recommendedTlsSettings = true;
|
||||
recommendedGzipSettings = true;
|
||||
# recommendedBrotliSettings = true;
|
||||
recommendedProxySettings = true;
|
||||
|
||||
# only allow PFS-enabled ciphers with AES256
|
||||
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
|
||||
|
||||
# disable access logs
|
||||
commonHttpConfig= ''
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# setup a default site
|
||||
virtualHosts.default = {
|
||||
default = lib.mkDefault true;
|
||||
addSSL = true;
|
||||
|
||||
sslCertificateKey = "${snakeOilCa}/ca.key";
|
||||
sslCertificate = "${snakeOilCa}/ca.pem";
|
||||
|
||||
root = pkgs.writeTextDir "index.html" ''
|
||||
<html>
|
||||
<head>
|
||||
<title>Nothing to see</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Like I said, nothing to see here</p>
|
||||
</body>
|
||||
</html>
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [80 443];
|
||||
allowedUDPPorts = [443];
|
||||
};
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [80 443];
|
||||
allowedUDPPorts = [443];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
{
|
||||
services.restic = {
|
||||
# enable = true;
|
||||
|
||||
backups.main = {
|
||||
repository = "b2:situla-${config.mine.shared.settings.brand_lower}:.";
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
services = {
|
||||
openssh = {
|
||||
|
@ -17,10 +15,6 @@
|
|||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
vim
|
||||
];
|
||||
|
||||
nix = {
|
||||
settings.auto-optimise-store = true;
|
||||
gc = {
|
||||
|
|
|
@ -10,15 +10,9 @@ in {
|
|||
default = {};
|
||||
};
|
||||
|
||||
# config = {
|
||||
# mine.zfsMounts = let
|
||||
# zfsFilesystems = lib.filterAttrs (_: v: v.fsType == "zfs") config.fileSystems;
|
||||
# in lib.mapAttrs' (_: v: lib.nameValuePair v.device v.mountPoint) zfsFilesystems;
|
||||
# };
|
||||
|
||||
# TODO: fix this better. We just do this, so we do not rely on fileSystems, otherwise we cannot
|
||||
# use this with impermanence
|
||||
config = {
|
||||
mine.zfsMounts = lib.mapAttrs' (n: v: lib.nameValuePair ("rpool/" + n) v.mountpoint) config.mine.disks.pools.rpool.datasets;
|
||||
mine.zfsMounts = let
|
||||
zfsFilesystems = lib.filterAttrs (_: v: v.fsType == "zfs") config.fileSystems;
|
||||
in lib.mapAttrs' (_: v: lib.nameValuePair v.device v.mountPoint) zfsFilesystems;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
diff --git a/flake.nix b/flake.nix
|
||||
index f6cfa25..68358a8 100644
|
||||
--- a/flake.nix
|
||||
+++ b/flake.nix
|
||||
@@ -101,6 +101,7 @@
|
||||
in {
|
||||
options.services.drasl = {
|
||||
enable = mkEnableOption (lib.mdDoc ''drasl'');
|
||||
+ package = mkPackageOption { drasl = self.defaultPackage.${pkgs.system}; } "drasl" {};
|
||||
settings = mkOption {
|
||||
type = format.type;
|
||||
default = {};
|
||||
@@ -115,10 +116,9 @@
|
||||
wantedBy = ["multi-user.target"];
|
||||
|
||||
serviceConfig = let
|
||||
- pkg = self.defaultPackage.${pkgs.system};
|
||||
config = format.generate "config.toml" cfg.settings;
|
||||
in {
|
||||
- ExecStart = "${pkg}/bin/drasl -config ${config}";
|
||||
+ ExecStart = "${cfg.package}/bin/drasl -config ${config}";
|
||||
DynamicUser = true;
|
||||
StateDirectory = "drasl";
|
||||
Restart = "always";
|
|
@ -1,14 +0,0 @@
|
|||
diff --git a/config.go b/config.go
|
||||
index 24e17b5..11194e6 100644
|
||||
--- a/config.go
|
||||
+++ b/config.go
|
||||
@@ -393,6 +393,9 @@ func CleanConfig(config *Config) error {
|
||||
return fmt.Errorf("Duplicate RegistrationOIDC Name: %s", oidcConfig.Name)
|
||||
}
|
||||
oidcNames.Add(oidcConfig.Name)
|
||||
+ envkey := fmt.Sprintf("DRASL_REGISTRATION_OIDC_%s_CLIENT_SECRET", strings.ToUpper(oidcConfig.Name))
|
||||
+ envvalue := strings.TrimSpace(Getenv(envkey, oidcConfig.ClientSecret))
|
||||
+ oidcConfig.ClientSecret = envvalue
|
||||
oidcConfig.Issuer, err = cleanURL(
|
||||
fmt.Sprintf("RegistrationOIDC %s Issuer", oidcConfig.Name),
|
||||
mo.Some("https://idm.example.com/oauth2/openid/drasl"),
|
|
@ -1,126 +0,0 @@
|
|||
{
|
||||
lib
|
||||
, buildNpmPackage
|
||||
, fetchFromGitHub
|
||||
, yarn
|
||||
, nodejs_20
|
||||
, inter # icons
|
||||
# , prisma-old ? null
|
||||
, oldpkgs ? (import (builtins.fetchTarball {
|
||||
url = "https://github.com/NixOS/nixpkgs/archive/ab7b6889ae9d484eed2876868209e33eb262511d.tar.gz";
|
||||
}) {})
|
||||
, rallly-prisma ? oldpkgs.prisma
|
||||
, rallly-prisma-engines ? oldpkgs.prisma-engines
|
||||
}:
|
||||
|
||||
|
||||
buildNpmPackage rec {
|
||||
pname = "rallly";
|
||||
version = "3.11.2";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "lukevella";
|
||||
repo = "rallly";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-ej6Y0ouiheoH6dSBWsSIW6qt9UvsLh9ODDQA5Fqt3zs=";
|
||||
};
|
||||
|
||||
nodejs = nodejs_20;
|
||||
|
||||
npmDepsHash = "sha256-sAs1DhegfI1YbE/Xy2Jzjx1RKYOUgc1Ww7hLL7+f8ZU=";
|
||||
|
||||
patches = [
|
||||
./font.patch
|
||||
];
|
||||
|
||||
postPatch = ''
|
||||
cp ${./package-lock.json} package-lock.json
|
||||
'';
|
||||
|
||||
SKIP_ENV_VALIDATION = 1;
|
||||
NEXT_PUBLIC_SELF_HOSTED = "true";
|
||||
NEXT_PUBLIC_APP_VERSION = version;
|
||||
|
||||
preBuild = ''
|
||||
# We have to pass and bake in the Ollama URL into the package
|
||||
|
||||
# Replace the googleapis.com Inter font with a local copy from nixpkgs
|
||||
cp "${inter}/share/fonts/truetype/InterVariable.ttf" apps/web/Inter.ttf
|
||||
|
||||
cp -a packages/database/prisma .
|
||||
${rallly-prisma}/bin/prisma generate
|
||||
'';
|
||||
|
||||
postBuild = ''
|
||||
# Add a shebang to the server js file, then patch the shebang to use a nixpkgs nodejs binary.
|
||||
sed -i '1s|^|#!/usr/bin/env node\n|' apps/web/.next/standalone/apps/web/server.js
|
||||
patchShebangs ./apps/web/.next/standalone/apps/web/server.js
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
cd apps/web
|
||||
|
||||
mkdir -p $out/{share,bin}
|
||||
|
||||
cp -r .next/standalone $out/share/rallly
|
||||
cp -a public/. $out/share/rallly/apps/web/public
|
||||
|
||||
mkdir -p $out/share/rallly/.next
|
||||
cp -r .next/static $out/share/rallly/apps/web/.next/static
|
||||
|
||||
cp -r ../../prisma $out/share/rallly/apps/web
|
||||
|
||||
chmod +x $out/share/rallly/apps/web/server.js
|
||||
|
||||
# This patch must be applied here, as it's patching the `dist` directory
|
||||
# of NextJS. Without this, homepage-dashboard errors when trying to
|
||||
# write its prerender cache.
|
||||
#
|
||||
# This patch ensures that the cache implementation respects the env
|
||||
# variable `NIXPKGS_HOMEPAGE_CACHE_DIR`, which is set by default in the
|
||||
# wrapper below.
|
||||
(cd "$out" && patch -p1 <${./prerender_cache_path.patch})
|
||||
|
||||
# we set a default port to support "nix run ..."
|
||||
makeWrapper $out/share/rallly/apps/web/server.js $out/bin/rallly \
|
||||
--set-default NIXPKGS_RALLLY_CACHE_DIR /var/cache/rallly
|
||||
|
||||
makeWrapper ${rallly-prisma}/bin/prisma $out/bin/rallly-prisma \
|
||||
--chdir $out/share/rallly/apps/web/prisma \
|
||||
--append-flags --schema=./schema.prisma
|
||||
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
# TODO(eyJhb): openssl is needed, but I can't figure out
|
||||
# how to add it best...?
|
||||
nativeBuildInputs = [
|
||||
(yarn.override {nodejs = nodejs_20; })
|
||||
rallly-prisma
|
||||
];
|
||||
|
||||
passthru = {
|
||||
inherit rallly-prisma rallly-prisma-engines;
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
homepage = "https://rallly.co/";
|
||||
changelog = "https://github.com/lukevella/rallly/releases/tag/v${version}";
|
||||
description = "Rallly is an open-source scheduling and collaboration tool designed to make organizing events and meetings easier";
|
||||
longDescription = ''
|
||||
Rallly is an open-source scheduling and collaboration tool
|
||||
designed to make organizing events and meetings easier.
|
||||
Schedule group meetings with friends, colleagues and teams.
|
||||
Create meeting polls to find the best date and time to organize an event
|
||||
based on your participants' availability. Save time and avoid back-and-forth emails.
|
||||
'';
|
||||
license = licenses.agpl3Plus;
|
||||
maintainers = with maintainers; [
|
||||
eyjhb
|
||||
];
|
||||
mainProgram = "rallly";
|
||||
};
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
diff --git a/apps/web/src/app/[locale]/layout.tsx b/apps/web/src/app/[locale]/layout.tsx
|
||||
index ee7d143c..d70ecdfd 100644
|
||||
--- a/apps/web/src/app/[locale]/layout.tsx
|
||||
+++ b/apps/web/src/app/[locale]/layout.tsx
|
||||
@@ -4,16 +4,13 @@ import "../../style.css";
|
||||
import languages from "@rallly/languages";
|
||||
import { Toaster } from "@rallly/ui/toaster";
|
||||
import { Viewport } from "next";
|
||||
-import { Inter } from "next/font/google";
|
||||
+import localFont from "next/font/local";
|
||||
import React from "react";
|
||||
|
||||
import { TimeZoneChangeDetector } from "@/app/[locale]/timezone-change-detector";
|
||||
import { Providers } from "@/app/providers";
|
||||
|
||||
-const inter = Inter({
|
||||
- subsets: ["latin"],
|
||||
- display: "swap",
|
||||
-});
|
||||
+const inter = localFont({ src: './../../../Inter.ttf' });
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return Object.keys(languages).map((locale) => ({ locale }));
|
||||
diff --git a/apps/web/src/pages/_app.tsx b/apps/web/src/pages/_app.tsx
|
||||
index b3d67d0e..16ee65a1 100644
|
||||
--- a/apps/web/src/pages/_app.tsx
|
||||
+++ b/apps/web/src/pages/_app.tsx
|
||||
@@ -6,7 +6,7 @@ import { TooltipProvider } from "@rallly/ui/tooltip";
|
||||
import { domMax, LazyMotion } from "framer-motion";
|
||||
import { NextPage } from "next";
|
||||
import { AppProps } from "next/app";
|
||||
-import { Inter } from "next/font/google";
|
||||
+import localFont from "next/font/local";
|
||||
import Head from "next/head";
|
||||
import { SessionProvider, signIn, useSession } from "next-auth/react";
|
||||
import React from "react";
|
||||
@@ -19,10 +19,7 @@ import { trpc } from "@/utils/trpc/client";
|
||||
|
||||
import { NextPageWithLayout } from "../types";
|
||||
|
||||
-const inter = Inter({
|
||||
- subsets: ["latin"],
|
||||
- display: "swap",
|
||||
-});
|
||||
+const inter = localFont({ src: './../../Inter.ttf' });
|
||||
|
||||
type AppPropsWithLayout = AppProps & {
|
||||
Component: NextPageWithLayout;
|
26944
shared/pkgs/rallly/package-lock.json
generated
26944
shared/pkgs/rallly/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,21 +0,0 @@
|
|||
diff --git a/share/rallly/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js b/share/rallly/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js
|
||||
index f3be830..fdafb45 100755
|
||||
--- a/share/rallly/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js
|
||||
+++ b/share/rallly/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js
|
||||
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "default", {
|
||||
});
|
||||
const _lrucache = /*#__PURE__*/ _interop_require_default(require("next/dist/compiled/lru-cache"));
|
||||
const _path = /*#__PURE__*/ _interop_require_default(require("../../../shared/lib/isomorphic/path"));
|
||||
+var path = require('node:path');
|
||||
const _constants = require("../../../lib/constants");
|
||||
function _interop_require_default(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
@@ -22,7 +23,7 @@ class FileSystemCache {
|
||||
constructor(ctx){
|
||||
this.fs = ctx.fs;
|
||||
this.flushToDisk = ctx.flushToDisk;
|
||||
- this.serverDistDir = ctx.serverDistDir;
|
||||
+ this.serverDistDir = path.join(process.env.NIXPKGS_RALLLY_CACHE_DIR, "rallly");
|
||||
this.appDir = !!ctx._appDir;
|
||||
this.pagesDir = !!ctx._pagesDir;
|
||||
this.revalidatedTags = ctx.revalidatedTags;
|
|
@ -8,11 +8,11 @@ in sources // {
|
|||
src = sources.nixpkgs;
|
||||
name = "nixpkgs-patched";
|
||||
patches = [
|
||||
# # tmp - lldap: 0.5.1-unstable-2024-10-30 -> 0.6.1
|
||||
# (pkgs.fetchpatch {
|
||||
# url = "https://github.com/NixOS/nixpkgs/pull/359835.patch";
|
||||
# sha256 = "sha256-2C9l4v9MaUJyiaB+kslTsSjsqTZ7RlcfMNlRzZblMik=";
|
||||
# })
|
||||
# tmp - lldap: 0.5.1-unstable-2024-10-30 -> 0.6.1
|
||||
(pkgs.fetchpatch {
|
||||
url = "https://github.com/NixOS/nixpkgs/pull/359835.patch";
|
||||
sha256 = "sha256-2C9l4v9MaUJyiaB+kslTsSjsqTZ7RlcfMNlRzZblMik=";
|
||||
})
|
||||
# tmp - stalwart-mail.webadmin: pin wasm-bindgen-cli version
|
||||
# (pkgs.fetchpatch {
|
||||
# url = "https://github.com/NixOS/nixpkgs/pull/353360.patch";
|
||||
|
@ -20,14 +20,4 @@ in sources // {
|
|||
# })
|
||||
];
|
||||
};
|
||||
|
||||
drasl = pkgs.applyPatches {
|
||||
src = sources.drasl;
|
||||
name = "drasl-patched";
|
||||
patches = [
|
||||
./../patches/drasl-flakes-nix-add-option-package.patch
|
||||
./../patches/drasl-registration-oidc-env.patch
|
||||
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
"homepage": "https://matrix.to/#/#agenix:nixos.org",
|
||||
"owner": "ryantm",
|
||||
"repo": "agenix",
|
||||
"rev": "e600439ec4c273cf11e06fe4d9d906fb98fa097c",
|
||||
"sha256": "006ngydiykjgqs85cl19h9klq8kaqm5zs0ng51dnwy7nzgqxzsdr",
|
||||
"rev": "f6291c5935fdc4e0bef208cfc0dcab7e3f7a1c41",
|
||||
"sha256": "1x8nd8hvsq6mvzig122vprwigsr3z2skanig65haqswn7z7amsvg",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/ryantm/agenix/archive/e600439ec4c273cf11e06fe4d9d906fb98fa097c.tar.gz",
|
||||
"url": "https://github.com/ryantm/agenix/archive/f6291c5935fdc4e0bef208cfc0dcab7e3f7a1c41.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
},
|
||||
"disko": {
|
||||
|
@ -17,41 +17,10 @@
|
|||
"homepage": "",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "51d33bbb7f1e74ba5f9d9a77357735149da99081",
|
||||
"sha256": "0fg2ym4kc1pcayfg4jka742512r8nackwl8w1syxvg82yasixnjc",
|
||||
"rev": "2814a5224a47ca19e858e027f7e8bff74a8ea9f1",
|
||||
"sha256": "1ayxw37arc92frzq0080w7kixdmqbq4jm8a19nrgivb70ra1mqys",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/nix-community/disko/archive/51d33bbb7f1e74ba5f9d9a77357735149da99081.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
},
|
||||
"drasl": {
|
||||
"sha256": "08fxv66qx5a8q52ci0hw2yvxx14a3mdsds5i79brxc1hilxiaksw",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/unmojang/drasl/archive/v3.0.0.tar.gz",
|
||||
"url_template": "https://github.com/unmojang/drasl/archive/<version>.tar.gz",
|
||||
"version": "v3.0.0"
|
||||
},
|
||||
"drtvrss": {
|
||||
"branch": "main",
|
||||
"description": "RSS feeds for DRTV",
|
||||
"homepage": null,
|
||||
"owner": "RasmusRendal",
|
||||
"repo": "drtvrss",
|
||||
"rev": "1234121a3f615d80bc18107768182fb43df0bbac",
|
||||
"sha256": "0yxarbbsj4giyszc8pf64d0gy9qsld9skgdxxfgygrgk2wspycnc",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/RasmusRendal/drtvrss/archive/1234121a3f615d80bc18107768182fb43df0bbac.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
},
|
||||
"flake-compat": {
|
||||
"branch": "master",
|
||||
"description": null,
|
||||
"homepage": null,
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"sha256": "19d2z6xsvpxm184m41qrpi1bplilwipgnzv9jy17fgw421785q1m",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz",
|
||||
"url": "https://github.com/nix-community/disko/archive/2814a5224a47ca19e858e027f7e8bff74a8ea9f1.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
},
|
||||
"impermanence": {
|
||||
|
@ -60,10 +29,10 @@
|
|||
"homepage": "",
|
||||
"owner": "nix-community",
|
||||
"repo": "impermanence",
|
||||
"rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170",
|
||||
"sha256": "04l16szln2x0ajq2x799krb53ykvc6vm44x86ppy1jg9fr82161c",
|
||||
"rev": "3ed3f0eaae9fcc0a8331e77e9319c8a4abd8a71a",
|
||||
"sha256": "1k30ig9b5bx51f0y617yvcn61bgpahf8r0i55mnl3hy6nqjbfw07",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/nix-community/impermanence/archive/4b3e914cdf97a5b536a889e939fb2fd2b043a170.tar.gz",
|
||||
"url": "https://github.com/nix-community/impermanence/archive/3ed3f0eaae9fcc0a8331e77e9319c8a4abd8a71a.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
},
|
||||
"nixpkgs": {
|
||||
|
@ -72,10 +41,10 @@
|
|||
"homepage": null,
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef",
|
||||
"sha256": "09dahi81cn02gnzsc8a00n945dxc18656ar0ffx5vgxjj1nhgsvy",
|
||||
"rev": "ac35b104800bff9028425fec3b6e8a41de2bbfff",
|
||||
"sha256": "1fbj7shlmviilmgz5z2gp59j6xwgdr01jfh75qhixx06kib4305p",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/b024ced1aac25639f8ca8fdfc2f8c4fbd66c48ef.tar.gz",
|
||||
"url": "https://github.com/NixOS/nixpkgs/archive/ac35b104800bff9028425fec3b6e8a41de2bbfff.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue