Compare commits
59 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f9df7a63c5 | ||
|
159c365382 | ||
242fd429d8 | |||
baa7464e8c | |||
|
8695f46699 | ||
|
5c47834299 | ||
|
94e08fd2f0 | ||
|
5c65f7f922 | ||
|
9fd8d7b900 | ||
|
02720387a4 | ||
|
054143d378 | ||
|
88673b582e | ||
|
98408e1970 | ||
|
313a0b6939 | ||
|
46cb83674b | ||
|
062a3c0a12 | ||
|
570b41ac5d | ||
|
06f5d3a5fb | ||
|
5749b1cf66 | ||
|
629f8f02d7 | ||
|
d1cb03f213 | ||
|
e6d22c1063 | ||
|
f160a3ebb6 | ||
|
3a0792caec | ||
|
d4d8236501 | ||
|
68feacc010 | ||
|
3121b57181 | ||
|
51bb437838 | ||
|
ff3ea0735c | ||
|
6c33d36fc5 | ||
|
e7af4737c1 | ||
|
97f9561631 | ||
|
02b66db30a | ||
|
5da6057228 | ||
|
913a4a0b26 | ||
|
50fee64475 | ||
|
73597c4cdb | ||
|
68779da243 | ||
|
57a5740f6f | ||
|
7cde74ce97 | ||
|
4fd55efab9 | ||
|
efb5711551 | ||
|
96e004a541 | ||
|
2f2993ac16 | ||
|
80ef4fabc8 | ||
|
878c83a8eb | ||
|
cbbed221c9 | ||
|
43e6fe30f0 | ||
|
791012f209 | ||
|
7313bb8344 | ||
|
ce4d807d53 | ||
|
7e94a0e80c | ||
|
41bb5bdb39 | ||
|
a93382acee | ||
|
008c7ebfb9 | ||
|
29a638943f | ||
|
ac813a2554 | ||
|
f8c9599661 | ||
|
6970d8c410 |
70 changed files with 2372 additions and 179 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 eyJhb and the Fricloud.dk contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -5,6 +5,7 @@ set -ex
|
||||||
USERNAME="root"
|
USERNAME="root"
|
||||||
IP="gerd.fricloud.dk"
|
IP="gerd.fricloud.dk"
|
||||||
NIXPKGS=$(nix build --impure --json --expr '(import ./shared/sources).nixpkgs' | jq -r '.[].outputs.out')
|
NIXPKGS=$(nix build --impure --json --expr '(import ./shared/sources).nixpkgs' | jq -r '.[].outputs.out')
|
||||||
|
NIXPKGS=$(nix eval --impure --json --expr '(import ./shared/sources/default.nix).nixpkgs.outPath' | jq -r)
|
||||||
|
|
||||||
export NIX_PATH="nixpkgs=$NIXPKGS"
|
export NIX_PATH="nixpkgs=$NIXPKGS"
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,14 @@
|
||||||
|
|
||||||
./../shared/applications/server/acme.nix
|
./../shared/applications/server/acme.nix
|
||||||
./../shared/applications/server/nginx.nix
|
./../shared/applications/server/nginx.nix
|
||||||
./../shared/applications/server/postgresql.nix
|
./../shared/applications/server/postgresql.nix # INCLUDES DATABASE BACKUPS
|
||||||
|
./../shared/applications/server/restic.nix # EXTERNAL BACKUP
|
||||||
./../shared/applications/state/postgresql.nix
|
./../shared/applications/state/postgresql.nix
|
||||||
./../shared/applications/state/ssh.nix
|
./../shared/applications/state/ssh.nix
|
||||||
|
|
||||||
./gerd/services/fricloud-website.nix
|
./gerd/services/fricloud-website.nix
|
||||||
./gerd/services/member-website
|
./gerd/services/member-website
|
||||||
./gerd/services/lldap.nix
|
./gerd/services/lldap
|
||||||
./gerd/services/authelia
|
./gerd/services/authelia
|
||||||
./gerd/services/forgejo
|
./gerd/services/forgejo
|
||||||
./gerd/services/teeworlds.nix
|
./gerd/services/teeworlds.nix
|
||||||
|
@ -19,6 +20,9 @@
|
||||||
./gerd/services/cyberchef.nix
|
./gerd/services/cyberchef.nix
|
||||||
./gerd/services/nextcloud.nix
|
./gerd/services/nextcloud.nix
|
||||||
./gerd/services/stalwart
|
./gerd/services/stalwart
|
||||||
|
./gerd/services/wger
|
||||||
|
./gerd/services/searx.nix
|
||||||
|
./gerd/services/miniflux.nix
|
||||||
|
|
||||||
./gerd/services/element.nix
|
./gerd/services/element.nix
|
||||||
./gerd/services/matrix-synapse.nix
|
./gerd/services/matrix-synapse.nix
|
||||||
|
@ -37,6 +41,7 @@
|
||||||
"safe/svcs/nextcloud" = { mountpoint = "/srv/nextcloud"; 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/stalwart" = { mountpoint = "/srv/stalwart"; extra.options.quota = "5G"; };
|
||||||
"safe/svcs/synapse" = { mountpoint = "/srv/synapse"; 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/postgresql" = { mountpoint = "/srv/postgresql"; extra.options.quota = "5G"; };
|
"safe/svcs/postgresql" = { mountpoint = "/srv/postgresql"; extra.options.quota = "5G"; };
|
||||||
"backup/postgresql" = { mountpoint = "/media/backup/postgresqlbackup"; extra.options.quota = "5G"; };
|
"backup/postgresql" = { mountpoint = "/media/backup/postgresqlbackup"; extra.options.quota = "5G"; };
|
||||||
};
|
};
|
||||||
|
@ -55,5 +60,18 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# setup zramswap (we are very ram limited)
|
||||||
|
zramSwap = {
|
||||||
|
enable = true;
|
||||||
|
memoryPercent = 75;
|
||||||
|
algorithm = "lz4";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
# TMP FIX FOR https://github.com/nix-community/impermanence/issues/229
|
||||||
|
boot.initrd.systemd.suppressedUnits = [ "systemd-machine-id-commit.service" ];
|
||||||
|
systemd.suppressedSystemUnits = [ "systemd-machine-id-commit.service" ];
|
||||||
|
|
||||||
|
|
||||||
system.stateVersion = "24.11";
|
system.stateVersion = "24.11";
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,10 @@ let
|
||||||
auth_request_set $email $upstream_http_remote_email;
|
auth_request_set $email $upstream_http_remote_email;
|
||||||
|
|
||||||
## Inject the metadata response headers from the variables into the request made to the backend.
|
## Inject the metadata response headers from the variables into the request made to the backend.
|
||||||
proxy_set_header Remote-User $user;
|
proxy_set_header ${config.mine.shared.lib.authelia.protectedHeaders.username} $user;
|
||||||
proxy_set_header Remote-Groups $groups;
|
proxy_set_header ${config.mine.shared.lib.authelia.protectedHeaders.groups} $groups;
|
||||||
proxy_set_header Remote-Email $email;
|
proxy_set_header ${config.mine.shared.lib.authelia.protectedHeaders.email} $email;
|
||||||
proxy_set_header Remote-Name $name;
|
proxy_set_header ${config.mine.shared.lib.authelia.protectedHeaders.name} $name;
|
||||||
|
|
||||||
## Configure the redirection when the authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method'
|
## Configure the redirection when the authz failure occurs. Lines starting with 'Modern Method' and 'Legacy Method'
|
||||||
## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url
|
## should be commented / uncommented as pairs. The modern method uses the session cookies configuration's authelia_url
|
||||||
|
@ -73,15 +73,29 @@ let
|
||||||
|
|
||||||
## Legacy Method: When there is a 401 response code from the authz endpoint redirect to the portal with the 'rd'
|
## Legacy Method: When there is a 401 response code from the authz endpoint redirect to the portal with the 'rd'
|
||||||
## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL.
|
## URL parameter set to $target_url. This requires users update 'auth.example.com/' with their external authelia URL.
|
||||||
error_page 401 =302 https://auth.fricloud.dk/?rd=$target_url;
|
error_page 401 =302 https://${config.mine.shared.settings.authelia.domain}/?rd=$target_url;
|
||||||
|
'';
|
||||||
|
|
||||||
|
nginxUnsetAuthHeaders = ''
|
||||||
|
proxy_set_header ${config.mine.shared.lib.authelia.protectedHeaders.username} "";
|
||||||
|
proxy_set_header ${config.mine.shared.lib.authelia.protectedHeaders.groups} "";
|
||||||
|
proxy_set_header ${config.mine.shared.lib.authelia.protectedHeaders.email} "";
|
||||||
|
proxy_set_header ${config.mine.shared.lib.authelia.protectedHeaders.name} "";
|
||||||
'';
|
'';
|
||||||
in {
|
in {
|
||||||
mine.shared.lib.authelia.mkProtectedWebsite = { vhostConfig, endpoint ? "/" }: lib.recursiveUpdate vhostConfig {
|
mine.shared.lib.authelia.mkProtectedWebsite = websiteConfig: lib.recursiveUpdate websiteConfig {
|
||||||
extraConfig = (lib.attrByPath [ "extraConfig" ] "" vhostConfig) + "\n" + "include ${autheliaLocation};";
|
extraConfig = (websiteConfig.extraConfig or "") + "\n" + "include ${autheliaLocation};";
|
||||||
locations."${endpoint}" = config.mine.shared.lib.authelia.mkProtectedLocation (lib.attrByPath [ "locations" endpoint ] {} vhostConfig);
|
locations = lib.mapAttrs (n: v: v // { extraConfig = nginxUnsetAuthHeaders + (v.extraConfig or ""); }) (websiteConfig.locations or {});
|
||||||
};
|
};
|
||||||
|
|
||||||
mine.shared.lib.authelia.mkProtectedLocation = vhostLocationConfig: lib.recursiveUpdate vhostLocationConfig {
|
mine.shared.lib.authelia.mkProtectedLocation = vhostLocationConfig: lib.recursiveUpdate vhostLocationConfig {
|
||||||
extraConfig = (lib.attrByPath [ "extraConfig" ] "" vhostLocationConfig) + "\n" + "include ${autheliaRequest};";
|
extraConfig = (lib.attrByPath [ "extraConfig" ] "" vhostLocationConfig) + "\n" + "include ${autheliaRequest};";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mine.shared.lib.authelia.protectedHeaders = {
|
||||||
|
username = "Remote-User";
|
||||||
|
groups = "Remote-Groups"; # comma separated string of groups
|
||||||
|
email = "Remote-Email";
|
||||||
|
name = "Remote-Name";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,17 @@ let
|
||||||
# configure element web client
|
# configure element web client
|
||||||
pkg_element = pkgs.element-web.override {
|
pkg_element = pkgs.element-web.override {
|
||||||
conf = {
|
conf = {
|
||||||
default_theme = "dark";
|
default_server_name = config.mine.shared.settings.domain;
|
||||||
features.feature_latex_maths = true;
|
embedded_pages.login_for_welcome = true;
|
||||||
disable_guests = true;
|
disable_guests = true;
|
||||||
|
|
||||||
default_server_name = config.mine.shared.settings.domain;
|
brand = config.mine.shared.settings.brand;
|
||||||
|
default_theme = "dark";
|
||||||
|
|
||||||
|
features = {
|
||||||
|
feature_latex_maths = true;
|
||||||
|
feature_video_rooms = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
|
|
|
@ -37,10 +37,6 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO(eyJhb): remove after our ban expires (and nginx config)
|
|
||||||
# already issued for this exact set of domains in the last 168 hours: git.fricloud.dk, retry after 2024-08-10T01:34:44Z
|
|
||||||
security.acme.certs."git.fricloud.dk".extraDomainNames = [ "git2.fricloud.dk" ];
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."${svc_domain}" = {
|
services.nginx.virtualHosts."${svc_domain}" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
|
|
|
@ -1,11 +1,26 @@
|
||||||
diff --git a/templates/user/auth/link_account.tmpl b/templates/user/auth/link_account.tmpl
|
diff --git a/templates/user/auth/link_account.tmpl b/templates/user/auth/link_account.tmpl
|
||||||
index 8dd49ccd60..8cdce5e1ad 100644
|
index e8bb3d409c..aa6d18b97a 100644
|
||||||
--- a/templates/user/auth/link_account.tmpl
|
--- a/templates/user/auth/link_account.tmpl
|
||||||
+++ b/templates/user/auth/link_account.tmpl
|
+++ b/templates/user/auth/link_account.tmpl
|
||||||
|
@@ -4,12 +4,12 @@
|
||||||
|
<div class="overflow-menu-items tw-justify-center">
|
||||||
|
<!-- TODO handle .ShowRegistrationButton once other login bugs are fixed -->
|
||||||
|
{{if not .AllowOnlyInternalRegistration}}
|
||||||
|
- <a class="item {{if not .user_exists}}active{{end}}"
|
||||||
|
+ <a class="item"
|
||||||
|
data-tab="auth-link-signup-tab">
|
||||||
|
{{ctx.Locale.Tr "auth.oauth_signup_tab"}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
- <a class="item {{if .user_exists}}active{{end}}"
|
||||||
|
+ <a class="item active"
|
||||||
|
data-tab="auth-link-signin-tab">
|
||||||
|
{{ctx.Locale.Tr "auth.oauth_signin_tab"}}
|
||||||
|
</a>
|
||||||
@@ -17,11 +17,11 @@
|
@@ -17,11 +17,11 @@
|
||||||
</overflow-menu>
|
</overflow-menu>
|
||||||
<div class="ui middle very relaxed page grid">
|
<div class="ui middle very relaxed page grid">
|
||||||
<div class="column">
|
<div class="column tw-flex tw-flex-col tw-gap-4 tw-max-w-2xl tw-m-auto">
|
||||||
- <div class="ui tab {{if not .user_exists}}active{{end}}"
|
- <div class="ui tab {{if not .user_exists}}active{{end}}"
|
||||||
+ <div class="ui tab"
|
+ <div class="ui tab"
|
||||||
data-tab="auth-link-signup-tab">
|
data-tab="auth-link-signup-tab">
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl
|
diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl
|
||||||
index 9872096fbc..1076f90326 100644
|
index d4ba664e37..2c94eafc22 100644
|
||||||
--- a/templates/user/auth/signin_inner.tmpl
|
--- a/templates/user/auth/signin_inner.tmpl
|
||||||
+++ b/templates/user/auth/signin_inner.tmpl
|
+++ b/templates/user/auth/signin_inner.tmpl
|
||||||
@@ -10,6 +10,7 @@
|
@@ -11,6 +11,7 @@
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
<form class="ui form tw-max-w-2xl tw-m-auto" action="{{.SignInLink}}" method="post">
|
<form class="ui form" action="{{.SignInLink}}" method="post">
|
||||||
+ <div {{if not .LinkAccountMode}}style="display:none;"{{end}}>
|
+ <div {{if not .LinkAccountMode}}style="display:none;"{{end}}>
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||||
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
|
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
|
||||||
@@ -53,6 +54,7 @@
|
@@ -43,6 +44,7 @@
|
||||||
<div class="divider divider-text">
|
{{end}}
|
||||||
{{ctx.Locale.Tr "sign_in_or"}}
|
</button>
|
||||||
</div>
|
</div>
|
||||||
+ </div>
|
+ </div>
|
||||||
<div id="oauth2-login-navigator" class="tw-py-1">
|
</form>
|
||||||
<div class="tw-flex tw-flex-col tw-justify-center">
|
|
||||||
<div id="oauth2-login-navigator-inner" class="tw-flex tw-flex-col tw-flex-wrap tw-items-center tw-gap-2">
|
{{template "user/auth/oauth_container" .}}
|
||||||
|
|
161
machines/gerd/services/lldap/bootstrap/attributes.py
Normal file
161
machines/gerd/services/lldap/bootstrap/attributes.py
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import gql
|
||||||
|
from gql.dsl import DSLQuery, DSLSchema, dsl_gql, DSLMutation
|
||||||
|
from gql.transport.aiohttp import AIOHTTPTransport
|
||||||
|
|
||||||
|
from .utils import to_camelcase, to_snakecase
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeTypes:
|
||||||
|
STRING = "STRING"
|
||||||
|
INTEGER = "INTEGER"
|
||||||
|
JPEG_PHOTO = "JPEG_PHOTO"
|
||||||
|
DATE_TIME = "DATE_TIME"
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeSchema:
|
||||||
|
def __init__(self, raw_vals: dict[str, Any]):
|
||||||
|
self.name: str = raw_vals["name"]
|
||||||
|
self.attributeType: str = raw_vals["attributeType"]
|
||||||
|
self.isList: bool = bool(raw_vals["isList"])
|
||||||
|
self.isVisible: bool = bool(raw_vals["isVisible"])
|
||||||
|
self.isEditable: bool = bool(raw_vals["isEditable"])
|
||||||
|
self.isReadonly: bool = bool(raw_vals["isReadonly"])
|
||||||
|
self.isHardcoded: bool = bool(raw_vals["isHardcoded"])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<AttributeSchema name={self.name} attributeType={self.attributeType} isList={self.isList} isVisible={self.isVisible} isEditable={self.isEditable} isReadonly={self.isReadonly} isHardcoded={self.isHardcoded} />"
|
||||||
|
|
||||||
|
|
||||||
|
class AttributeValue:
|
||||||
|
def __init__(self, raw_attribute: dict[str, Any]):
|
||||||
|
self.name: str = raw_attribute["name"]
|
||||||
|
|
||||||
|
tmpValue = raw_attribute.get("value", [])
|
||||||
|
if isinstance(tmpValue, str):
|
||||||
|
tmpValue = [tmpValue]
|
||||||
|
|
||||||
|
self.value: list[str] = tmpValue
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<AttributeValue name={self.name} value={self.value} />"
|
||||||
|
|
||||||
|
|
||||||
|
class LLDAPAttributes:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
client: gql.Client,
|
||||||
|
using_user_attributes: bool = False,
|
||||||
|
using_group_attributes: bool = False,
|
||||||
|
):
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
self._using_user_attributes = using_user_attributes
|
||||||
|
self._using_group_attributes = using_group_attributes
|
||||||
|
|
||||||
|
if self._using_user_attributes and self._using_group_attributes:
|
||||||
|
raise Exception("can not both use user attributes and group attributes")
|
||||||
|
|
||||||
|
if not self._using_user_attributes and not self._using_group_attributes:
|
||||||
|
raise Exception("neither user attributes and group attributes specified")
|
||||||
|
|
||||||
|
def list_all(self) -> dict[str, AttributeSchema]:
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
|
||||||
|
if self._using_user_attributes:
|
||||||
|
querySchema = ds.Schema.userSchema
|
||||||
|
querySchemaStr = "userSchema"
|
||||||
|
else:
|
||||||
|
querySchema = ds.Schema.groupSchema
|
||||||
|
querySchemaStr = "groupSchema"
|
||||||
|
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLQuery(
|
||||||
|
ds.Query.schema().select(
|
||||||
|
querySchema.select(
|
||||||
|
ds.AttributeList.attributes.select(
|
||||||
|
ds.AttributeSchema.name,
|
||||||
|
ds.AttributeSchema.attributeType,
|
||||||
|
ds.AttributeSchema.isList,
|
||||||
|
ds.AttributeSchema.isVisible,
|
||||||
|
ds.AttributeSchema.isEditable,
|
||||||
|
ds.AttributeSchema.isReadonly,
|
||||||
|
ds.AttributeSchema.isHardcoded,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self._client.execute(query)
|
||||||
|
|
||||||
|
attrs: dict[str, AttributeSchema] = {}
|
||||||
|
for raw_attribute in result["schema"][querySchemaStr]["attributes"]:
|
||||||
|
attr = AttributeSchema(raw_attribute)
|
||||||
|
attrs[attr.name] = attr
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def create(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
attributeType: str,
|
||||||
|
isList: bool,
|
||||||
|
isVisible: bool,
|
||||||
|
isEditable: bool,
|
||||||
|
):
|
||||||
|
logger.debug(
|
||||||
|
f"adding attribute, name:'{name}' attributeType:'{attributeType}' isList:'{isList}' isVisible:'{isVisible}' isEditable:'{isEditable}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
|
||||||
|
if self._using_user_attributes:
|
||||||
|
mutationSchema = ds.Mutation.addUserAttribute
|
||||||
|
else:
|
||||||
|
mutationSchema = ds.Mutation.addGroupAttribute
|
||||||
|
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
mutationSchema.args(
|
||||||
|
name=name,
|
||||||
|
attributeType=attributeType,
|
||||||
|
isList=isList,
|
||||||
|
isVisible=isVisible,
|
||||||
|
isEditable=isEditable,
|
||||||
|
).select(ds.Success.ok)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._client.execute(query)
|
||||||
|
|
||||||
|
def get(self, name: str) -> AttributeSchema | None:
|
||||||
|
attrs = self.list_all()
|
||||||
|
return attrs.get(name)
|
||||||
|
|
||||||
|
def update(self, name: str):
|
||||||
|
raise Exception("unable to update attribute")
|
||||||
|
|
||||||
|
def delete(self, name: str):
|
||||||
|
logger.debug(f"deleting attribute '{name}'")
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
|
||||||
|
if self._using_user_attributes:
|
||||||
|
mutationSchema = ds.Mutation.deleteUserAttribute
|
||||||
|
else:
|
||||||
|
mutationSchema = ds.Mutation.deleteGroupAttribute
|
||||||
|
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
mutationSchema.args(
|
||||||
|
name=name,
|
||||||
|
).select(ds.Success.ok)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._client.execute(query)
|
79
machines/gerd/services/lldap/bootstrap/example.json
Normal file
79
machines/gerd/services/lldap/bootstrap/example.json
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"group_attributes": [
|
||||||
|
{
|
||||||
|
"name": "hosted_email2",
|
||||||
|
"attributeType": "STRING",
|
||||||
|
"isEditable": false,
|
||||||
|
"isList": false,
|
||||||
|
"isVisible": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hosted_email10",
|
||||||
|
"attributeType": "STRING",
|
||||||
|
"isEditable": false,
|
||||||
|
"isList": false,
|
||||||
|
"isVisible": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"user_attributes": [
|
||||||
|
{
|
||||||
|
"name": "member_email",
|
||||||
|
"attributeType": "STRING",
|
||||||
|
"isEditable": false,
|
||||||
|
"isList": false,
|
||||||
|
"isVisible": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"display_name": "base_member",
|
||||||
|
"hosted_email2": "tehnte_member",
|
||||||
|
"hosted_email10": "tehnte_member"
|
||||||
|
},
|
||||||
|
{ "display_name": "system_service" },
|
||||||
|
{ "display_name": "lldap_admin" },
|
||||||
|
{ "display_name": "lldap_password_manager" },
|
||||||
|
{ "display_name": "lldap_strict_readonly" },
|
||||||
|
{ "display_name": "disabled" }
|
||||||
|
],
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"user_id": "admin",
|
||||||
|
"groups": ["lldap_admin"],
|
||||||
|
"mail": "admin@fricloud.dk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": "bind_user",
|
||||||
|
"groups": ["lldap_password_manager", "lldap_strict_readonly"],
|
||||||
|
"mail": "lldap_bind_user@fricloud.dk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": "authelia",
|
||||||
|
"groups": ["system_service", "base_member"],
|
||||||
|
"mail": "authelia@fricloud.dk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": "wger",
|
||||||
|
"groups": ["base_member"],
|
||||||
|
"mail": "wger@fricloud.dk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": "testusername",
|
||||||
|
"groups": ["base_member"],
|
||||||
|
"password": "env:PASSWORD",
|
||||||
|
"mail": "testusername@fricloud.dk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": "eyjhb",
|
||||||
|
"groups": ["lldap_admin", "base_member"],
|
||||||
|
"mail": "eyjhb@fricloud.dk",
|
||||||
|
"member_email": "eyjhb@fricloud.dk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user_id": "rasmus",
|
||||||
|
"groups": ["lldap_admin", "base_member"],
|
||||||
|
"mail": "rasmus@fricloud.dk",
|
||||||
|
"member_email": "rasmus@fricloud.dk"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
136
machines/gerd/services/lldap/bootstrap/groups.py
Normal file
136
machines/gerd/services/lldap/bootstrap/groups.py
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
from typing import Any
|
||||||
|
import re
|
||||||
|
|
||||||
|
import gql
|
||||||
|
from gql.dsl import DSLQuery, DSLSchema, dsl_gql, DSLMutation
|
||||||
|
from gql.transport.aiohttp import AIOHTTPTransport
|
||||||
|
|
||||||
|
from .utils import to_camelcase, to_snakecase
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Group:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
raw_attrs: list[dict[str, str]],
|
||||||
|
):
|
||||||
|
self._attributes: dict[str, Any] = {
|
||||||
|
item["name"]: item["value"] for item in raw_attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
self.groupId: int = int(self.__getattr__("groupId")[0])
|
||||||
|
self.name: str = self.__getattr__("displayName")[0]
|
||||||
|
|
||||||
|
def _attributes_camelcase(self) -> dict[str, str]:
|
||||||
|
return {to_camelcase(k): v for k, v in self._attributes.items()}
|
||||||
|
|
||||||
|
def __getattr__(self, key: str):
|
||||||
|
return self._attributes_camelcase().get(key, "")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Group groupId={self.groupId} name={self.name} />"
|
||||||
|
|
||||||
|
|
||||||
|
class LLDAPGroups:
|
||||||
|
def __init__(self, client: gql.Client):
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
def list_all(self) -> dict[str, Group]:
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLQuery(
|
||||||
|
ds.Query.groups().select(
|
||||||
|
ds.Group.attributes.select(
|
||||||
|
ds.AttributeValue.name,
|
||||||
|
ds.AttributeValue.value,
|
||||||
|
ds.AttributeValue.schema.select(
|
||||||
|
ds.AttributeSchema.isHardcoded,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self._client.execute(query)
|
||||||
|
|
||||||
|
groups: dict[str, Group] = {}
|
||||||
|
for group in result["groups"]:
|
||||||
|
g = Group(
|
||||||
|
raw_attrs=group.get("attributes", []),
|
||||||
|
)
|
||||||
|
groups[g.name] = g
|
||||||
|
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def create(self, groupName: str):
|
||||||
|
logger.debug(f"creating group with name '{groupName}'")
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
ds.Mutation.createGroup.args(
|
||||||
|
name=groupName,
|
||||||
|
).select(ds.Group.displayName)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._client.execute(query)
|
||||||
|
|
||||||
|
def get_by_name(self, groupName: str) -> Group | None:
|
||||||
|
groups = self.list_all()
|
||||||
|
return groups.get(groupName)
|
||||||
|
|
||||||
|
def get_by_id(self, groupId: int) -> Group | None:
|
||||||
|
groups = self.list_all()
|
||||||
|
for group in groups.values():
|
||||||
|
if group.groupId == groupId:
|
||||||
|
return group
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def name_to_id(self, groupName: str) -> int:
|
||||||
|
group = self.get_by_name(groupName)
|
||||||
|
if not group:
|
||||||
|
raise Exception(f"no group with the name {groupName}")
|
||||||
|
|
||||||
|
return group.groupId
|
||||||
|
|
||||||
|
def update(self, groupId: int, attrs: dict[str, str | list[str]]):
|
||||||
|
insertAttributes: list[dict[str, str | list[str]]] = []
|
||||||
|
for k, v in attrs.items():
|
||||||
|
if isinstance(v, str):
|
||||||
|
v = [v]
|
||||||
|
|
||||||
|
insertAttributes.append({"name": to_snakecase(k), "value": v})
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
ds.Mutation.updateGroup.args(
|
||||||
|
group={
|
||||||
|
"id": groupId,
|
||||||
|
"insertAttributes": insertAttributes,
|
||||||
|
},
|
||||||
|
).select(ds.Success.ok),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._client.execute(query)
|
||||||
|
|
||||||
|
def delete(self, groupId: int):
|
||||||
|
logger.debug(f"deleting group with id '{groupId}'")
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
ds.Mutation.deleteGroup.args(
|
||||||
|
groupId=groupId,
|
||||||
|
).select(ds.Success.ok),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._client.execute(query)
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
self.list_all()
|
||||||
|
# self.update("testusername", {"displayName": "Test User Name"})
|
287
machines/gerd/services/lldap/bootstrap/main.py
Normal file
287
machines/gerd/services/lldap/bootstrap/main.py
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell --pure --keep LLDAP_TOKEN -i python3 -p "python3.withPackages (ps: with ps; [ requests gql aiohttp])"
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
import subprocess
|
||||||
|
import secrets
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
import gql
|
||||||
|
from gql.dsl import DSLQuery, DSLSchema, dsl_gql, DSLMutation
|
||||||
|
from gql.transport.aiohttp import AIOHTTPTransport
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
from .users import LLDAPUsers
|
||||||
|
from .groups import LLDAPGroups
|
||||||
|
from .attributes import LLDAPAttributes
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
logging.getLogger("lldapbootstrap").setLevel(logging.DEBUG)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
class LLDAP:
|
||||||
|
def __init__(self, server_url: str, auth_token: str):
|
||||||
|
self._server_url: str = server_url
|
||||||
|
self._server_auth_token: str = auth_token
|
||||||
|
|
||||||
|
self._client: gql.Client = self._init_gql_client()
|
||||||
|
|
||||||
|
self._users = LLDAPUsers(self._client)
|
||||||
|
self._groups = LLDAPGroups(self._client)
|
||||||
|
self._attrsUser = LLDAPAttributes(self._client, using_user_attributes=True)
|
||||||
|
self._attrsGroup = LLDAPAttributes(self._client, using_group_attributes=True)
|
||||||
|
|
||||||
|
def _init_gql_client(self) -> gql.Client:
|
||||||
|
# Select your transport with a defined url endpoint
|
||||||
|
transport = AIOHTTPTransport(
|
||||||
|
url=f"{self._server_url}/api/graphql",
|
||||||
|
headers={"Authorization": f"Bearer {self._server_auth_token}"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a GraphQL client using the defined transport
|
||||||
|
client = gql.Client(transport=transport, fetch_schema_from_transport=True)
|
||||||
|
|
||||||
|
# force fetch schema
|
||||||
|
query = gql.gql(
|
||||||
|
"""
|
||||||
|
query {
|
||||||
|
users {
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = client.execute(query)
|
||||||
|
|
||||||
|
return client
|
||||||
|
|
||||||
|
def _run_ensure_attrs_user(self, attrsClass, neededAttrsUser: list[dict[str, Any]]):
|
||||||
|
dictNeededAttrUser = {v["name"]: v for v in neededAttrsUser}
|
||||||
|
remoteAttrs = attrsClass.list_all()
|
||||||
|
|
||||||
|
# add needed attributes
|
||||||
|
for neededAttr in neededAttrsUser:
|
||||||
|
neededAttrName = neededAttr["name"]
|
||||||
|
|
||||||
|
if neededAttrName in remoteAttrs:
|
||||||
|
cattr = remoteAttrs[neededAttrName]
|
||||||
|
if (
|
||||||
|
neededAttr["attributeType"] != cattr.attributeType
|
||||||
|
or neededAttr["isEditable"] != cattr.isEditable
|
||||||
|
or neededAttr["isList"] != cattr.isList
|
||||||
|
or neededAttr["isVisible"] != cattr.isVisible
|
||||||
|
):
|
||||||
|
logger.debug(
|
||||||
|
f"attribute '{neededAttrName}' out of sync, deleting and adding again"
|
||||||
|
)
|
||||||
|
attrsClass.delete(neededAttrName)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
attrsClass.create(
|
||||||
|
neededAttrName,
|
||||||
|
attributeType=neededAttr["attributeType"],
|
||||||
|
isEditable=neededAttr["isEditable"],
|
||||||
|
isList=neededAttr["isList"],
|
||||||
|
isVisible=neededAttr["isVisible"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# remove unneeded attributes
|
||||||
|
for remoteAttrName, remoteAttr in remoteAttrs.items():
|
||||||
|
# skip hardcoded ones
|
||||||
|
if remoteAttr.isHardcoded:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if remoteAttrName not in dictNeededAttrUser:
|
||||||
|
attrsClass.delete(remoteAttrName)
|
||||||
|
|
||||||
|
def _run_ensure_groups(self, neededGroups: list[dict[str, Any]]):
|
||||||
|
tmpNeededGroups = {v["display_name"]: v for v in neededGroups}
|
||||||
|
remoteGroups = self._groups.list_all()
|
||||||
|
|
||||||
|
for neededGroup in neededGroups:
|
||||||
|
neededGroupDisplay_Name = neededGroup["display_name"]
|
||||||
|
|
||||||
|
if neededGroupDisplay_Name not in remoteGroups:
|
||||||
|
self._groups.create(neededGroupDisplay_Name)
|
||||||
|
|
||||||
|
# refresh groups
|
||||||
|
remoteGroups = self._groups.list_all()
|
||||||
|
|
||||||
|
remoteGroup = remoteGroups[neededGroupDisplay_Name]
|
||||||
|
|
||||||
|
# we cannot update the display name, and we never would anyways
|
||||||
|
del neededGroup["display_name"]
|
||||||
|
|
||||||
|
self._groups.update(remoteGroup.groupId, neededGroup)
|
||||||
|
|
||||||
|
# delete unused groups
|
||||||
|
for remoteGroupName, remoteGroup in remoteGroups.items():
|
||||||
|
# skip all lldap_ groups
|
||||||
|
if remoteGroupName.startswith("lldap_"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if remoteGroupName not in tmpNeededGroups:
|
||||||
|
self._groups.delete(remoteGroup.groupId)
|
||||||
|
|
||||||
|
def _run_ensure_users(
|
||||||
|
self,
|
||||||
|
neededUsers: list[dict[str, Any]],
|
||||||
|
softDelete: bool = True,
|
||||||
|
):
|
||||||
|
tmpNeededUsers = {v["user_id"]: v for v in neededUsers}
|
||||||
|
remoteUsers = self._users.list_all()
|
||||||
|
|
||||||
|
for neededUser in neededUsers:
|
||||||
|
# get required info from dict, and DELETE from dict
|
||||||
|
# while we're at it. This means that we can safely use
|
||||||
|
# `neededUser` for updating later
|
||||||
|
neededUserId = neededUser.pop("user_id")
|
||||||
|
neededUserGroups = neededUser.pop("groups", [])
|
||||||
|
neededUserPassword: str | None = neededUser.pop("password", None)
|
||||||
|
|
||||||
|
# create user if needed
|
||||||
|
if neededUserId not in remoteUsers:
|
||||||
|
self._users.create(
|
||||||
|
neededUserId,
|
||||||
|
neededUser.get("mail", "no-email-specified"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# refresh users
|
||||||
|
remoteUsers = self._users.list_all()
|
||||||
|
|
||||||
|
# update user
|
||||||
|
self._users.update(neededUserId, neededUser)
|
||||||
|
|
||||||
|
# set correct groups
|
||||||
|
remoteUser = remoteUsers[neededUserId]
|
||||||
|
|
||||||
|
# print warning about groups attribute
|
||||||
|
if neededUserGroups:
|
||||||
|
logger.info(
|
||||||
|
f"using attribute 'groups' for userId '{neededUserId}', for setting groups, NOT SETTING AS ATTRIBUTE!!"
|
||||||
|
)
|
||||||
|
|
||||||
|
# add to correct groups
|
||||||
|
for groupName in neededUserGroups:
|
||||||
|
if groupName not in remoteUser.groups:
|
||||||
|
self._users.add_group(
|
||||||
|
neededUserId,
|
||||||
|
self._groups.name_to_id(groupName),
|
||||||
|
)
|
||||||
|
|
||||||
|
# remove from unused groups
|
||||||
|
for groupName, group in remoteUser.groups.items():
|
||||||
|
if groupName not in neededUserGroups:
|
||||||
|
self._users.remove_group(
|
||||||
|
neededUserId,
|
||||||
|
self._groups.name_to_id(groupName),
|
||||||
|
)
|
||||||
|
|
||||||
|
if neededUserPassword:
|
||||||
|
logger.info(
|
||||||
|
f"using attribute 'password' for userId '{neededUserId}', for setting password, NOT SETTING AS ATTRIBUTE!!"
|
||||||
|
)
|
||||||
|
|
||||||
|
if neededUserPassword.startswith("file:"):
|
||||||
|
passwordFile = neededUserPassword[len("file:") :]
|
||||||
|
logger.debug(
|
||||||
|
f"reading password from file from file '{passwordFile}' for user '{neededUserId}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._user_set_password(
|
||||||
|
neededUserId,
|
||||||
|
open(passwordFile, "r").read().strip(),
|
||||||
|
)
|
||||||
|
elif neededUserPassword.startswith("env:"):
|
||||||
|
cleanedPasswordEnv = neededUserPassword.strip()[len("env:") :]
|
||||||
|
logger.debug(
|
||||||
|
f"reading password from envvar '{cleanedPasswordEnv}' for user '{neededUserId}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
password = os.getenv(cleanedPasswordEnv)
|
||||||
|
if not password:
|
||||||
|
raise Exception(
|
||||||
|
f"could not find env '{cleanedPasswordEnv}' for getting password"
|
||||||
|
)
|
||||||
|
self._user_set_password(
|
||||||
|
neededUserId,
|
||||||
|
password.strip(),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
f"using the raw value of password, as the password for user '{neededUserId}'"
|
||||||
|
)
|
||||||
|
self._user_set_password(
|
||||||
|
neededUserId,
|
||||||
|
neededUserPassword.strip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# delete unused users
|
||||||
|
for remoteUserName, remoteUser in remoteUsers.items():
|
||||||
|
if remoteUserName not in tmpNeededUsers:
|
||||||
|
if softDelete:
|
||||||
|
self._user_disable(remoteUserName)
|
||||||
|
else:
|
||||||
|
self._users.delete(remoteUser.userId)
|
||||||
|
|
||||||
|
def _user_disable(self, userId: str, disabled_group_name: str = "disabled"):
|
||||||
|
user = self._users.get(userId)
|
||||||
|
if not user:
|
||||||
|
return
|
||||||
|
|
||||||
|
# remove all groups
|
||||||
|
for groupName, groupId in user.groups.items():
|
||||||
|
if groupName == disabled_group_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._users.remove_group(userId, groupId)
|
||||||
|
|
||||||
|
# if disabled group is in the users groups, then return
|
||||||
|
if disabled_group_name in user.groups:
|
||||||
|
return
|
||||||
|
|
||||||
|
# ensure group exists
|
||||||
|
groups = self._groups.list_all()
|
||||||
|
if disabled_group_name not in groups:
|
||||||
|
self._groups.create(disabled_group_name)
|
||||||
|
|
||||||
|
# add disabled group
|
||||||
|
self._users.add_group(userId, self._groups.name_to_id(disabled_group_name))
|
||||||
|
|
||||||
|
# set password to a long string
|
||||||
|
self._user_set_password(userId, secrets.token_urlsafe(128))
|
||||||
|
|
||||||
|
def _user_set_password(self, userId: str, password: str):
|
||||||
|
subprocess.check_output(
|
||||||
|
[
|
||||||
|
"lldap_set_password",
|
||||||
|
f"--base-url={self._server_url}",
|
||||||
|
f"--token={self._server_auth_token}",
|
||||||
|
f"--username={userId}",
|
||||||
|
f"--password={password}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
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"])
|
||||||
|
self._run_ensure_groups(data["groups"])
|
||||||
|
self._run_ensure_users(data["users"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
auth_token = os.getenv("LLDAP_TOKEN")
|
||||||
|
if not auth_token:
|
||||||
|
raise Exception("No LLDAP_TOKEN provided. please set")
|
||||||
|
|
||||||
|
x = LLDAP("https://ldap.fricloud.dk", auth_token)
|
||||||
|
x.run()
|
154
machines/gerd/services/lldap/bootstrap/users.py
Normal file
154
machines/gerd/services/lldap/bootstrap/users.py
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
from typing import Any
|
||||||
|
import re
|
||||||
|
|
||||||
|
import gql
|
||||||
|
from gql.dsl import DSLQuery, DSLSchema, dsl_gql, DSLMutation
|
||||||
|
from gql.transport.aiohttp import AIOHTTPTransport
|
||||||
|
|
||||||
|
from .utils import to_camelcase, to_snakecase
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
raw_groups: list[dict[str, Any]],
|
||||||
|
raw_attrs: list[dict[str, str]],
|
||||||
|
):
|
||||||
|
self._attributes: dict[str, Any] = {
|
||||||
|
item["name"]: item["value"] for item in raw_attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
self.groups: dict[str, int] = {
|
||||||
|
info["displayName"]: info["id"] for info in raw_groups
|
||||||
|
}
|
||||||
|
self.userId: str = self.__getattr__("userId")[0]
|
||||||
|
self.email: str = self.__getattr__("mail")[0]
|
||||||
|
|
||||||
|
def _attributes_camelcase(self) -> dict[str, str]:
|
||||||
|
return {to_camelcase(k): v for k, v in self._attributes.items()}
|
||||||
|
|
||||||
|
def __getattr__(self, key: str):
|
||||||
|
return self._attributes_camelcase().get(key, "")
|
||||||
|
|
||||||
|
|
||||||
|
class LLDAPUsers:
|
||||||
|
def __init__(self, client: gql.Client):
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
def list_all(self) -> dict[str, User]:
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLQuery(
|
||||||
|
ds.Query.users().select(
|
||||||
|
ds.User.groups.select(
|
||||||
|
ds.Group.id,
|
||||||
|
ds.Group.displayName,
|
||||||
|
),
|
||||||
|
ds.User.attributes.select(
|
||||||
|
ds.AttributeValue.name,
|
||||||
|
ds.AttributeValue.value,
|
||||||
|
ds.AttributeValue.schema.select(
|
||||||
|
ds.AttributeSchema.isHardcoded,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self._client.execute(query)
|
||||||
|
|
||||||
|
users: dict[str, User] = {}
|
||||||
|
for user in result["users"]:
|
||||||
|
u = User(
|
||||||
|
raw_groups=user.get("groups", []),
|
||||||
|
raw_attrs=user.get("attributes", []),
|
||||||
|
)
|
||||||
|
users[u.userId] = u
|
||||||
|
|
||||||
|
return users
|
||||||
|
|
||||||
|
def create(self, userId: str, email: str):
|
||||||
|
logger.debug(f"creating user with name '{userId}' and email '{email}'")
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
ds.Mutation.createUser.args(
|
||||||
|
user={"id": userId, "email": email},
|
||||||
|
).select(ds.User.displayName)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._client.execute(query)
|
||||||
|
|
||||||
|
def get(self, userId: str):
|
||||||
|
users = self.list_all()
|
||||||
|
return users.get(userId)
|
||||||
|
|
||||||
|
def update(self, userId: str, attrs: dict[str, str | list[str]]):
|
||||||
|
insertAttributes: list[dict[str, str | list[str]]] = []
|
||||||
|
for k, v in attrs.items():
|
||||||
|
if isinstance(v, str):
|
||||||
|
v = [v]
|
||||||
|
|
||||||
|
insertAttributes.append({"name": to_snakecase(k), "value": v})
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
ds.Mutation.updateUser.args(
|
||||||
|
user={
|
||||||
|
"id": userId,
|
||||||
|
"insertAttributes": insertAttributes,
|
||||||
|
},
|
||||||
|
).select(ds.Success.ok),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._client.execute(query)
|
||||||
|
|
||||||
|
def delete(self, userId: str):
|
||||||
|
logger.debug(f"deleting user with name '{userId}'")
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
ds.Mutation.deleteUser.args(
|
||||||
|
userId=userId,
|
||||||
|
).select(ds.Success.ok),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self._client.execute(query)
|
||||||
|
|
||||||
|
# groups
|
||||||
|
def add_group(self, userId: str, groupId: int):
|
||||||
|
logger.debug(f"adding user '{userId}' to group '{groupId}'")
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
ds.Mutation.addUserToGroup.args(
|
||||||
|
userId=userId,
|
||||||
|
groupId=groupId,
|
||||||
|
).select(ds.Success.ok)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._client.execute(query)
|
||||||
|
|
||||||
|
def remove_group(self, userId: str, groupId: int):
|
||||||
|
logger.debug(f"removing user '{userId}' from group '{groupId}'")
|
||||||
|
|
||||||
|
ds = DSLSchema(self._client.schema)
|
||||||
|
query = dsl_gql(
|
||||||
|
DSLMutation(
|
||||||
|
ds.Mutation.removeUserFromGroup.args(
|
||||||
|
userId=userId,
|
||||||
|
groupId=groupId,
|
||||||
|
).select(ds.Success.ok)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._client.execute(query)
|
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()
|
|
@ -5,20 +5,26 @@ let
|
||||||
|
|
||||||
resetPasswordStartPatch = pkgs.writeText "lldap-reset-password-start.patch" ''
|
resetPasswordStartPatch = pkgs.writeText "lldap-reset-password-start.patch" ''
|
||||||
diff --git a/server/src/main.rs b/server/src/main.rs
|
diff --git a/server/src/main.rs b/server/src/main.rs
|
||||||
index 71e4928..63be13c 100644
|
index 6f42473..b3746a1 100644
|
||||||
--- a/server/src/main.rs
|
--- a/server/src/main.rs
|
||||||
+++ b/server/src/main.rs
|
+++ b/server/src/main.rs
|
||||||
@@ -158,7 +158,7 @@ async fn set_up_server(config: Configuration) -> Result<ServerBuilder> {
|
@@ -171,7 +171,7 @@ async fn set_up_server(config: Configuration) -> Result<ServerBuilder> {
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
if config.force_update_private_key || config.force_ldap_user_pass_reset {
|
if config.force_update_private_key || config.force_ldap_user_pass_reset.is_yes() {
|
||||||
- bail!("Restart the server without --force-update-private-key or --force-ldap-user-pass-reset to continue.");
|
- bail!("Restart the server without --force-update-private-key or --force-ldap-user-pass-reset to continue.");
|
||||||
+ // bail!("Restart the server without --force-update-private-key or --force-ldap-user-pass-reset to continue.");
|
+ // bail!("Restart the server without --force-update-private-key or --force-ldap-user-pass-reset to continue.");
|
||||||
}
|
}
|
||||||
let server_builder = infra::ldap_server::build_ldap_server(
|
let server_builder = infra::ldap_server::build_ldap_server(
|
||||||
&config,
|
&config,
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
pkgLLDAPCli = pkgs.callPackage ./../../../../shared/pkgs/lldap-cli.nix {};
|
||||||
in {
|
in {
|
||||||
|
environment.systemPackages = [
|
||||||
|
pkgLLDAPCli
|
||||||
|
];
|
||||||
|
|
||||||
services.lldap = {
|
services.lldap = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
@ -56,7 +56,6 @@ in {
|
||||||
max_upload_size = max_upload_size;
|
max_upload_size = max_upload_size;
|
||||||
|
|
||||||
# only authenticated media
|
# only authenticated media
|
||||||
# TODO: Should default to true at some point
|
|
||||||
enable_authenticated_media = true;
|
enable_authenticated_media = true;
|
||||||
|
|
||||||
# retentien policies
|
# retentien policies
|
||||||
|
|
|
@ -8,6 +8,7 @@ import argparse
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -94,10 +95,10 @@ def extract_secrets() -> dict[str, str]:
|
||||||
def index():
|
def index():
|
||||||
# extract user information
|
# extract user information
|
||||||
user_info = {
|
user_info = {
|
||||||
"username": request.headers.get("Remote-User"),
|
"username": request.headers.get(os.environ.get("AUTH_PROXY_USERNAME")),
|
||||||
"name": request.headers.get("Remote-Name"),
|
"name": request.headers.get(os.environ.get("AUTH_PROXY_NAME")),
|
||||||
"groups": request.headers.get("Remote-Groups"),
|
"groups": request.headers.get(os.environ.get("AUTH_PROXY_GROUPS")),
|
||||||
"email": request.headers.get("Remote-Email"),
|
"email": request.headers.get(os.environ.get("AUTH_PROXY_EMAIL")),
|
||||||
}
|
}
|
||||||
tmpl_firstpass = render_template_string(
|
tmpl_firstpass = render_template_string(
|
||||||
tmpl_index,
|
tmpl_index,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{ config, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
urlpath = "/members";
|
urlpath = "/members";
|
||||||
|
@ -9,6 +9,14 @@ in {
|
||||||
description = "members area website";
|
description = "members area website";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "networking.target" ];
|
after = [ "networking.target" ];
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
AUTH_PROXY_USERNAME = config.mine.shared.lib.authelia.protectedHeaders.username;
|
||||||
|
AUTH_PROXY_GROUPS = config.mine.shared.lib.authelia.protectedHeaders.groups;
|
||||||
|
AUTH_PROXY_EMAIL = config.mine.shared.lib.authelia.protectedHeaders.email;
|
||||||
|
AUTH_PROXY_NAME = config.mine.shared.lib.authelia.protectedHeaders.name;
|
||||||
|
};
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = let
|
ExecStart = let
|
||||||
pythonEnv = pkgs.python3.withPackages(ps: with ps; [ flask ]);
|
pythonEnv = pkgs.python3.withPackages(ps: with ps; [ flask ]);
|
||||||
|
@ -18,10 +26,25 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.virtualHosts."${config.mine.shared.settings.domain}" = config.mine.shared.lib.authelia.mkProtectedWebsite {
|
services.nginx.virtualHosts."${config.mine.shared.settings.domain}" = config.mine.shared.lib.authelia.mkProtectedWebsite {
|
||||||
endpoint = urlpath;
|
locations."${urlpath}" = config.mine.shared.lib.authelia.mkProtectedLocation {
|
||||||
vhostConfig.locations."${urlpath}" = {
|
|
||||||
# extraConfig = "rewrite ^${urlpath}(.*)$ /$1 break;";
|
|
||||||
proxyPass = "http://localhost:${builtins.toString port}";
|
proxyPass = "http://localhost:${builtins.toString port}";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mine.shared.meta.website-members = {
|
||||||
|
name = "Members Website";
|
||||||
|
description = "This website you are looking at right now, which is our members website.";
|
||||||
|
url = "https://${config.mine.shared.settings.domain}${urlpath}";
|
||||||
|
|
||||||
|
package = {
|
||||||
|
name = "members-website";
|
||||||
|
version = "v0.0.1";
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Members website for ${config.mine.shared.settings.domain}";
|
||||||
|
license = licenses.free;
|
||||||
|
homepage = "https://git.fricloud.dk/fricloud/server-configs/src/branch/main/machines/gerd/services/member-website/app.py";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
57
machines/gerd/services/miniflux.nix
Normal file
57
machines/gerd/services/miniflux.nix
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
svc_domain = "miniflux.${config.mine.shared.settings.domain}";
|
||||||
|
port = 6466;
|
||||||
|
in {
|
||||||
|
services.miniflux = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# listen only on localhost
|
||||||
|
LISTEN_ADDR = "localhost:${builtins.toString port}";
|
||||||
|
|
||||||
|
# setup the correct baseurl
|
||||||
|
BASE_URL = "https://${svc_domain}";
|
||||||
|
|
||||||
|
# disable admin account, disable local auth
|
||||||
|
CREATE_ADMIN = 0;
|
||||||
|
DISABLE_LOCAL_AUTH = "true";
|
||||||
|
|
||||||
|
# use auth proxy
|
||||||
|
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_MODE = "all";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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."/v1".proxyPass = "http://localhost:${builtins.toString port}";
|
||||||
|
};
|
||||||
|
|
||||||
|
# meta
|
||||||
|
mine.shared.meta.miniflux = {
|
||||||
|
name = "Miniflux";
|
||||||
|
description = "We host our own miniflux, use it to read all your feeds!";
|
||||||
|
url = "https://${svc_domain}";
|
||||||
|
|
||||||
|
package = let
|
||||||
|
pkg = config.services.miniflux.package;
|
||||||
|
in {
|
||||||
|
name = pkg.pname;
|
||||||
|
version = pkg.version;
|
||||||
|
meta = pkg.meta;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -14,7 +14,11 @@ in {
|
||||||
|
|
||||||
environmentFile = config.age.secrets.murmur-env.path;
|
environmentFile = config.age.secrets.murmur-env.path;
|
||||||
password = "$MURMUR_PASSWORD";
|
password = "$MURMUR_PASSWORD";
|
||||||
welcometext = "Welcome to Friclouds Mumble server!";
|
welcometext = ''Welcome to ${config.mine.shared.settings.brand}s Mumble server!
|
||||||
|
Feel free to join our Teeworlds server while waiting. Credentials can be found on the members website here ${config.mine.shared.meta.website-members.url}.
|
||||||
|
'';
|
||||||
|
|
||||||
|
bandwidth = 130000;
|
||||||
};
|
};
|
||||||
|
|
||||||
# set superpassword on start from secrets
|
# set superpassword on start from secrets
|
||||||
|
|
|
@ -125,7 +125,7 @@ let
|
||||||
in {
|
in {
|
||||||
services.nextcloud = {
|
services.nextcloud = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.nextcloud29;
|
package = pkgs.nextcloud30;
|
||||||
datadir = stateDir;
|
datadir = stateDir;
|
||||||
|
|
||||||
config.adminpassFile = config.age.secrets.nextcloud-admin-pass.path;
|
config.adminpassFile = config.age.secrets.nextcloud-admin-pass.path;
|
||||||
|
@ -138,12 +138,12 @@ in {
|
||||||
# apps
|
# apps
|
||||||
extraAppsEnable = true;
|
extraAppsEnable = true;
|
||||||
extraApps = {
|
extraApps = {
|
||||||
inherit (config.services.nextcloud.package.packages.apps) contacts calendar tasks;
|
inherit (config.services.nextcloud.package.packages.apps) contacts calendar tasks gpoddersync;
|
||||||
oidc_login = let
|
oidc_login = let
|
||||||
version = "3.1.1";
|
version = "3.2.0";
|
||||||
# TODO(eyJhb): add to niv
|
# TODO(eyJhb): add to niv
|
||||||
in pkgs.fetchNextcloudApp {
|
in pkgs.fetchNextcloudApp {
|
||||||
sha256 = "sha256-b/tKk+y+ZypCHGNDtunDua2msYD6/TzA0haoC0k85F4=";
|
sha256 = "sha256-DrbaKENMz2QJfbDKCMrNGEZYpUEvtcsiqw9WnveaPZA=";
|
||||||
url = "https://github.com/pulsejet/nextcloud-oidc-login/releases/download/v${version}/oidc_login.tar.gz";
|
url = "https://github.com/pulsejet/nextcloud-oidc-login/releases/download/v${version}/oidc_login.tar.gz";
|
||||||
license = "agpl3Only";
|
license = "agpl3Only";
|
||||||
};
|
};
|
||||||
|
|
48
machines/gerd/services/searx.nix
Normal file
48
machines/gerd/services/searx.nix
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
svc_domain = "searx.${config.mine.shared.settings.domain}";
|
||||||
|
port = 7378;
|
||||||
|
in {
|
||||||
|
services.searx = {
|
||||||
|
enable = true;
|
||||||
|
runInUwsgi = true;
|
||||||
|
redisCreateLocally = true;
|
||||||
|
|
||||||
|
environmentFile = config.age.secrets.searx-env.path;
|
||||||
|
|
||||||
|
uwsgiConfig.http = "127.0.0.1:${builtins.toString port}";
|
||||||
|
settings = {
|
||||||
|
general.debug = false;
|
||||||
|
server = {
|
||||||
|
base_url = "https://${svc_domain}";
|
||||||
|
secret_key = "@SECRET_KEY@";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# meta
|
||||||
|
mine.shared.meta.searx = {
|
||||||
|
name = "searX";
|
||||||
|
description = "We host our own searX, use it to search the web!";
|
||||||
|
url = "https://${svc_domain}";
|
||||||
|
|
||||||
|
package = let
|
||||||
|
pkg = config.services.searx.package;
|
||||||
|
in {
|
||||||
|
name = pkg.pname;
|
||||||
|
version = pkg.version;
|
||||||
|
meta = pkg.meta;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -26,12 +26,6 @@ in {
|
||||||
enable = true;
|
enable = true;
|
||||||
openFirewall = true;
|
openFirewall = true;
|
||||||
|
|
||||||
package = pkgs.stalwart-mail.overrideAttrs (old: {
|
|
||||||
patches = old.patches ++ [
|
|
||||||
./patches/stalwart-cli-dns-records.patch
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
lookup.default.hostname = svc_domain;
|
lookup.default.hostname = svc_domain;
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
password = "$TEEWORLDS_PASSWORD";
|
password = "$TEEWORLDS_PASSWORD";
|
||||||
};
|
};
|
||||||
|
|
||||||
mine.shared.meta.teeworlds = {
|
mine.shared.meta.teeworlds = rec {
|
||||||
name = "Teeworlds";
|
name = "Teeworlds";
|
||||||
description = ''We host our own Teeworlds instance. Connect using `nix-shell -p teeworlds --run 'teeworlds "connect teeworlds.fricloud.dk" "password {{secrets.TEEWORLDS_PASSWORD}}"'`, the password is {{secrets.TEEWORLDS_PASSWORD}}'';
|
description = ''We host our own Teeworlds instance. Connect using `nix-shell -p teeworlds --run 'teeworlds "connect ${url}" "password {{secrets.TEEWORLDS_PASSWORD}}"'`, the password is {{secrets.TEEWORLDS_PASSWORD}}'';
|
||||||
url = "";
|
url = "teeworlds.${config.mine.shared.settings.domain}";
|
||||||
|
|
||||||
secrets.auth = config.age.secrets.teeworlds-env.path;
|
secrets.auth = config.age.secrets.teeworlds-env.path;
|
||||||
|
|
||||||
|
|
79
machines/gerd/services/wger/default.nix
Normal file
79
machines/gerd/services/wger/default.nix
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{ config, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
svc_domain = "wger.${config.mine.shared.settings.domain}";
|
||||||
|
port = config.services.wger.port;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
./wgerpkg/module.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
services.wger = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
configureRedis = true;
|
||||||
|
configurePostgres = true;
|
||||||
|
|
||||||
|
dataDir = config.mine.zfsMounts."rpool/safe/svcs/wger";
|
||||||
|
|
||||||
|
# wger specific settings
|
||||||
|
wgerSettings = {
|
||||||
|
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 = rec {
|
||||||
|
# setup site stuff
|
||||||
|
SITE_URL = "https://${svc_domain}";
|
||||||
|
CSRF_TRUSTED_ORIGINS = [ "https://${svc_domain}" ];
|
||||||
|
ALLOWED_HOSTS = [ svc_domain ];
|
||||||
|
|
||||||
|
# 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 = "$EMAIL_HOST_PASSWORD";
|
||||||
|
EMAIL_FROM_ADDRESS = config.services.wger.wgerSettings.EMAIL_FROM;
|
||||||
|
EMAIL_PAGE_DOMAIN = SITE_URL;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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."/api/v2/register" = config.mine.shared.lib.authelia.mkProtectedLocation {
|
||||||
|
proxyPass = "http://localhost:${builtins.toString port}";
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."/static".root = "${config.services.wger.package}/share";
|
||||||
|
locations."/media".root = "${config.services.wger.dataDir}";
|
||||||
|
locations."/api".proxyPass = "http://localhost:${builtins.toString port}";
|
||||||
|
};
|
||||||
|
|
||||||
|
# metadata
|
||||||
|
mine.shared.meta.wger = {
|
||||||
|
name = "Wger";
|
||||||
|
description = "We host Wger, which is a FLOSS fitness/workout/nutrition and weight tracker, with FLOSS apps, read more [here](https://wger.de/).";
|
||||||
|
url = "https://${svc_domain}";
|
||||||
|
|
||||||
|
package = let
|
||||||
|
pkg = config.services.wger.package;
|
||||||
|
in {
|
||||||
|
name = pkg.pname;
|
||||||
|
version = pkg.version;
|
||||||
|
meta = pkg.meta;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
138
machines/gerd/services/wger/wgerpkg/default.nix
Normal file
138
machines/gerd/services/wger/wgerpkg/default.nix
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
python3,
|
||||||
|
fetchFromGitHub,
|
||||||
|
callPackage,
|
||||||
|
writeText,
|
||||||
|
fetchpatch,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
frontend = callPackage ./frontend.nix {};
|
||||||
|
in python3.pkgs.buildPythonPackage rec {
|
||||||
|
pname = "wger";
|
||||||
|
version = "unstable-2024-12-30";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "wger-project";
|
||||||
|
repo = "wger";
|
||||||
|
rev = "30871d621fa6e732f07bd33d4112b99539974e5f";
|
||||||
|
hash = "sha256-WcycWbzKug8vUfNnUDhvgmj1kUCpT1P1YJBfdIC1H9g=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
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=";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
# dependencies = with python3.pkgs; [
|
||||||
|
propagatedBuildInputs = with python3.pkgs; [
|
||||||
|
bleach
|
||||||
|
celery
|
||||||
|
django-crispy-bootstrap5
|
||||||
|
django
|
||||||
|
# django-activity-stream
|
||||||
|
(python3.pkgs.callPackage ./django-activity-stream.nix {})
|
||||||
|
django-axes
|
||||||
|
django-compressor
|
||||||
|
django-cors-headers
|
||||||
|
django-crispy-forms
|
||||||
|
# django-email-verification
|
||||||
|
(python3.pkgs.callPackage ./django-email-verification.nix {})
|
||||||
|
django-environ
|
||||||
|
django-filter
|
||||||
|
django-formtools
|
||||||
|
django-prometheus
|
||||||
|
# django-recaptcha
|
||||||
|
(python3.pkgs.callPackage ./django-recaptcha.nix {})
|
||||||
|
django-simple-history
|
||||||
|
# django-sortedm2m
|
||||||
|
(python3.pkgs.callPackage ./django-sortedm2m.nix {})
|
||||||
|
django-storages
|
||||||
|
djangorestframework
|
||||||
|
djangorestframework-simplejwt
|
||||||
|
drf-spectacular
|
||||||
|
easy-thumbnails
|
||||||
|
flower
|
||||||
|
fontawesomefree
|
||||||
|
icalendar
|
||||||
|
invoke
|
||||||
|
# openfoodfacts
|
||||||
|
(python3.pkgs.callPackage ./openfoodfacts.nix {})
|
||||||
|
pillow
|
||||||
|
reportlab
|
||||||
|
requests
|
||||||
|
tqdm
|
||||||
|
tzdata
|
||||||
|
|
||||||
|
# extra??
|
||||||
|
redis
|
||||||
|
django-redis
|
||||||
|
drf-spectacular-sidecar
|
||||||
|
(python3.pkgs.callPackage ./django-bootstrap-breadcrumbs.nix {})
|
||||||
|
psycopg2
|
||||||
|
];
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
cp manage.py wger/manage.py
|
||||||
|
'';
|
||||||
|
|
||||||
|
# fixup compressed files
|
||||||
|
postBuild = let
|
||||||
|
staticSettings = writeText "static_settings.py" ''
|
||||||
|
import os
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
STATIC_ROOT = os.environ["static"]
|
||||||
|
COMPRESS_OFFLINE = True
|
||||||
|
# So we don't need postgres dependencies
|
||||||
|
DATABASES = {}
|
||||||
|
'';
|
||||||
|
in ''
|
||||||
|
# copy over static yarn things
|
||||||
|
# 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
|
||||||
|
cat ${staticSettings} >> $PWD/tmp_settings.py
|
||||||
|
mkdir tmpstatic
|
||||||
|
pushd tmpstatic
|
||||||
|
|
||||||
|
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/${python3.sitePackages}/wger/core/static
|
||||||
|
cp -a tmpstatic $out/${python3.sitePackages}/wger/core/static
|
||||||
|
|
||||||
|
mkdir $out/share
|
||||||
|
cp -a $out/${python3.sitePackages}/wger/core/static $out/share
|
||||||
|
'';
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
"wger"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "";
|
||||||
|
homepage = "https://github.com/wger-project/wger";
|
||||||
|
license = lib.licenses.agpl3Only;
|
||||||
|
maintainers = with lib.maintainers; [ eyjhb ];
|
||||||
|
mainProgram = "wger";
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildPythonPackage,
|
||||||
|
fetchFromGitHub,
|
||||||
|
setuptools,
|
||||||
|
wheel,
|
||||||
|
django,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "django-activity-stream";
|
||||||
|
version = "2.0.0";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "justquick";
|
||||||
|
repo = "django-activity-stream";
|
||||||
|
rev = version;
|
||||||
|
hash = "sha256-fZrZDCWBFx1R9GGcTkjos7blSBNx1JTdTIVLKz+E2+c=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
django
|
||||||
|
];
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
# "django_activity_stream"
|
||||||
|
"actstream"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Generate generic activity streams from the actions on your site. Users can follow any actors' activities for personalized streams";
|
||||||
|
homepage = "https://github.com/justquick/django-activity-stream";
|
||||||
|
changelog = "https://github.com/justquick/django-activity-stream/blob/${src.rev}/CHANGELOG.rst";
|
||||||
|
license = lib.licenses.bsd3;
|
||||||
|
maintainers = with lib.maintainers; [ ];
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildPythonPackage,
|
||||||
|
fetchFromGitHub,
|
||||||
|
setuptools,
|
||||||
|
wheel,
|
||||||
|
django,
|
||||||
|
six,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "bootstrap-breadcrumbs";
|
||||||
|
version = "0.9.2";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "prymitive";
|
||||||
|
repo = "bootstrap-breadcrumbs";
|
||||||
|
rev = version;
|
||||||
|
hash = "sha256-w6s3LL/skzz4EnWtdsa5GXeISrJzr4yQ8hm/gQMva1o=";
|
||||||
|
};
|
||||||
|
|
||||||
|
patches = [
|
||||||
|
./patches/breadcrumbs.patch
|
||||||
|
];
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
django
|
||||||
|
six
|
||||||
|
];
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
# "bootstrap_breadcrumbs"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Django template tags for easy breadcrumbs using twitter bootstrap css classes or custom template";
|
||||||
|
homepage = "https://github.com/prymitive/bootstrap-breadcrumbs";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ ];
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildPythonPackage,
|
||||||
|
fetchFromGitHub,
|
||||||
|
setuptools,
|
||||||
|
wheel,
|
||||||
|
asgiref,
|
||||||
|
coverage,
|
||||||
|
deprecation,
|
||||||
|
django,
|
||||||
|
iniconfig,
|
||||||
|
packaging,
|
||||||
|
pluggy,
|
||||||
|
pyjwt,
|
||||||
|
pytest,
|
||||||
|
pytest-django,
|
||||||
|
sqlparse,
|
||||||
|
validators,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "django-email-verification";
|
||||||
|
version = "unstable-2024-07-12";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "LeoneBacciu";
|
||||||
|
repo = "django-email-verification";
|
||||||
|
rev = "49e841b96e8bd39f0ad359a75be4711508ac4879";
|
||||||
|
hash = "sha256-4hMSA1d6GOu7Xo7Qq1tBob4lW2zq1E4YaD8w0BnFfVc=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
asgiref
|
||||||
|
coverage
|
||||||
|
deprecation
|
||||||
|
django
|
||||||
|
iniconfig
|
||||||
|
packaging
|
||||||
|
pluggy
|
||||||
|
pyjwt
|
||||||
|
pytest
|
||||||
|
pytest-django
|
||||||
|
sqlparse
|
||||||
|
validators
|
||||||
|
];
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
# "django_email_verification"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "A Django app that takes care of verifying a users's email address and activating their profile";
|
||||||
|
homepage = "https://github.com/LeoneBacciu/django-email-verification";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ ];
|
||||||
|
};
|
||||||
|
}
|
46
machines/gerd/services/wger/wgerpkg/django-recaptcha.nix
Normal file
46
machines/gerd/services/wger/wgerpkg/django-recaptcha.nix
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildPythonPackage,
|
||||||
|
fetchFromGitHub,
|
||||||
|
setuptools,
|
||||||
|
wheel,
|
||||||
|
django,
|
||||||
|
coveralls,
|
||||||
|
tox,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "django-recaptcha";
|
||||||
|
version = "4.0.0";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "django-recaptcha";
|
||||||
|
repo = "django-recaptcha";
|
||||||
|
rev = version;
|
||||||
|
hash = "sha256-B6Z9oKcMjSh+zE28k0ipoBppm9dD+Moa+PAZqXVabpA=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
django
|
||||||
|
coveralls
|
||||||
|
tox
|
||||||
|
];
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
# "django_recaptcha"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Django reCAPTCHA form field/widget integration app";
|
||||||
|
homepage = "https://github.com/django-recaptcha/django-recaptcha";
|
||||||
|
changelog = "https://github.com/django-recaptcha/django-recaptcha/blob/${src.rev}/CHANGELOG.md";
|
||||||
|
license = lib.licenses.bsd3;
|
||||||
|
maintainers = with lib.maintainers; [ ];
|
||||||
|
};
|
||||||
|
}
|
49
machines/gerd/services/wger/wgerpkg/django-sortedm2m.nix
Normal file
49
machines/gerd/services/wger/wgerpkg/django-sortedm2m.nix
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildPythonPackage,
|
||||||
|
fetchFromGitHub,
|
||||||
|
setuptools,
|
||||||
|
wheel,
|
||||||
|
coverage,
|
||||||
|
isort,
|
||||||
|
pycodestyle,
|
||||||
|
pylint-django,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "django-sortedm2m";
|
||||||
|
version = "4.0.0";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "jazzband";
|
||||||
|
repo = "django-sortedm2m";
|
||||||
|
rev = version;
|
||||||
|
hash = "sha256-Jr3C6teU4On2PiJJV9vW4EEPEuknNCZRVMDMmrs6VY8=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
coverage
|
||||||
|
isort
|
||||||
|
pycodestyle
|
||||||
|
pylint-django
|
||||||
|
setuptools
|
||||||
|
];
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
# "django_sortedm2m"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "A transparent sorted ManyToMany field for django";
|
||||||
|
homepage = "https://github.com/jazzband/django-sortedm2m";
|
||||||
|
changelog = "https://github.com/jazzband/django-sortedm2m/blob/${src.rev}/CHANGES.rst";
|
||||||
|
license = lib.licenses.bsd3;
|
||||||
|
maintainers = with lib.maintainers; [ ];
|
||||||
|
};
|
||||||
|
}
|
63
machines/gerd/services/wger/wgerpkg/frontend.nix
Normal file
63
machines/gerd/services/wger/wgerpkg/frontend.nix
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
python3,
|
||||||
|
fetchFromGitHub,
|
||||||
|
mkYarnPackage,
|
||||||
|
fetchYarnDeps,
|
||||||
|
sass,
|
||||||
|
stdenv,
|
||||||
|
yarn,
|
||||||
|
fixup-yarn-lock,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "wger-project";
|
||||||
|
repo = "wger";
|
||||||
|
rev = "bfca74e88f6c9ff6e917e0ba0e8e9c782ae0047b";
|
||||||
|
hash = "sha256-VuVKgkNp6Omiag72lOn6p51kC/jvApX/kRAPpK95U7w=";
|
||||||
|
};
|
||||||
|
|
||||||
|
offlineCache = fetchYarnDeps {
|
||||||
|
yarnLock = "${src}/yarn.lock";
|
||||||
|
hash = "sha256-olRU6ZGh6bpZ/WfwIKeREJRGd3oo7kEffFx8+4+7s5k=";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "tetrio-plus";
|
||||||
|
version = "1.0.0";
|
||||||
|
|
||||||
|
src = src;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
yarn
|
||||||
|
fixup-yarn-lock
|
||||||
|
sass
|
||||||
|
];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
export HOME=$(mktemp -d)
|
||||||
|
|
||||||
|
yarn config --offline set yarn-offline-mirror ${offlineCache}
|
||||||
|
fixup-yarn-lock yarn.lock
|
||||||
|
yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
|
||||||
|
|
||||||
|
sass wger/core/static/scss/main.scss wger/core/static/yarn/bootstrap-compiled.css
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -a wger/core/static $out/static
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "";
|
||||||
|
homepage = "https://github.com/wger-project/wger";
|
||||||
|
license = lib.licenses.agpl3Only;
|
||||||
|
maintainers = with lib.maintainers; [ ];
|
||||||
|
mainProgram = "wger";
|
||||||
|
};
|
||||||
|
}
|
296
machines/gerd/services/wger/wgerpkg/module.nix
Normal file
296
machines/gerd/services/wger/wgerpkg/module.nix
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
# TODO: when DEBUG = False, serving static/media does not work, not sure why
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
cfg = config.services.wger;
|
||||||
|
|
||||||
|
defaultUser = "wger";
|
||||||
|
|
||||||
|
wgerpkgs = pkgs.callPackage ./default.nix {};
|
||||||
|
|
||||||
|
# generate settings files
|
||||||
|
settingsFormat = pkgs.formats.json {};
|
||||||
|
|
||||||
|
wger_settings_file = pkgs.writeText "settings.json" (builtins.toJSON cfg.wgerSettings);
|
||||||
|
django_settings_file = pkgs.writeText "settings.json" (builtins.toJSON cfg.djangoSettings);
|
||||||
|
settingsFile = pkgs.writeText "settings.py" ''
|
||||||
|
from wger.settings_global import *
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
with open("${django_settings_file}") as f:
|
||||||
|
for k, v in json.load(f).items():
|
||||||
|
if isinstance(v, str) and v.startswith("$"):
|
||||||
|
v = os.environ[v[1:]]
|
||||||
|
|
||||||
|
globals()[k] = v
|
||||||
|
|
||||||
|
with open("${wger_settings_file}") as f:
|
||||||
|
for k, v in json.load(f).items():
|
||||||
|
if isinstance(v, str) and v.startswith("$"):
|
||||||
|
v = os.environ[v[1:]]
|
||||||
|
|
||||||
|
WGER_SETTINGS[k] = v
|
||||||
|
'';
|
||||||
|
settingsFileDir = pkgs.writeTextDir "settings.py" (builtins.readFile settingsFile);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
meta.maintainers = with maintainers; [ eyjhb ];
|
||||||
|
|
||||||
|
options.services.wger = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Enable Wger.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
dataDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/var/lib/wger";
|
||||||
|
description = "Directory to store the Wger data.";
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "${cfg.dataDir}/media";
|
||||||
|
defaultText = literalExpression ''"''${dataDir}/media"'';
|
||||||
|
description = "Directory to store the Wger media.";
|
||||||
|
};
|
||||||
|
|
||||||
|
environmentFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = null;
|
||||||
|
example = "/var/lib/teeworlds/teeworlds.env";
|
||||||
|
description = ''
|
||||||
|
Environment file as defined in {manpage}`systemd.exec(5)`.
|
||||||
|
|
||||||
|
Secrets may be passed to the service without adding them to the world-readable
|
||||||
|
Nix store, by specifying placeholder variables as the option value in Nix and
|
||||||
|
setting these variables accordingly in the environment file.
|
||||||
|
|
||||||
|
```
|
||||||
|
# snippet of teeworlds-related config
|
||||||
|
services.teeworlds.settings.SECRET_KEY = "$SECRETS_KEY";
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# content of the environment file
|
||||||
|
SECRETS_KEY=verysecretpassword
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this file needs to be available on the host on which
|
||||||
|
`wger` is running.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
address = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "localhost";
|
||||||
|
description = "Web interface address.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 28391;
|
||||||
|
description = "Web interface port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
djangoSettings = mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
freeformType = settingsFormat.type;
|
||||||
|
};
|
||||||
|
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
wgerSettings = mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
freeformType = settingsFormat.type;
|
||||||
|
};
|
||||||
|
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = defaultUser;
|
||||||
|
# TODO: fix this, it is because of the database thing
|
||||||
|
readOnly = true;
|
||||||
|
description = "User under which Wger runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
configureRedis = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
configurePostgres = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
package = mkPackageOption { wger = wgerpkgs; } "wger" { };
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.wger.wgerSettings = {
|
||||||
|
EMAIL_FROM = mkDefault "wger Workout Manager <wger@example.com>";
|
||||||
|
ALLOW_REGISTRATION = mkDefault true;
|
||||||
|
ALLOW_GUEST_USERS = mkDefault true;
|
||||||
|
ALLOW_UPLOAD_VIDEOS = mkDefault false;
|
||||||
|
MIN_ACCOUNT_AGE_TO_TRUST = mkDefault 1;
|
||||||
|
EXERCISE_CACHE_TTL = mkDefault 3600; # 1 hour
|
||||||
|
};
|
||||||
|
|
||||||
|
services.wger.djangoSettings = rec {
|
||||||
|
DEBUG = mkDefault false;
|
||||||
|
|
||||||
|
# configure database as postgresql or sqlite
|
||||||
|
DATABASES.default = if cfg.configurePostgres then {
|
||||||
|
ENGINE = "django.db.backends.postgresql";
|
||||||
|
NAME = "wger";
|
||||||
|
USER = "wger";
|
||||||
|
PASSWORD = "";
|
||||||
|
HOST = "/run/postgresql";
|
||||||
|
PORT = "";
|
||||||
|
} else {
|
||||||
|
ENGINE = "django.db.backends.sqlite3";
|
||||||
|
NAME = "${cfg.dataDir}/database.db";
|
||||||
|
USER = "";
|
||||||
|
PASSWORD = "";
|
||||||
|
HOST = "";
|
||||||
|
PORT = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
SECRET_KEY = "$SECRET_KEY";
|
||||||
|
|
||||||
|
MEDIA_ROOT = cfg.mediaDir;
|
||||||
|
MEDIA_URL = "/media/";
|
||||||
|
|
||||||
|
# EMAIL
|
||||||
|
EMAIL_BACKEND = mkDefault "django.core.mail.backends.console.EmailBackend";
|
||||||
|
|
||||||
|
# Cache - Redis
|
||||||
|
CACHES.default = mkIf cfg.configureRedis {
|
||||||
|
BACKEND = "django_redis.cache.RedisCache";
|
||||||
|
LOCATION = "unix://${config.services.redis.servers.wger.unixSocket}";
|
||||||
|
TIMEOUT = 15 * 24 * 60 * 60; # 15 days
|
||||||
|
OPTIONS.CLIENT_CLASS = "django_redis.client.DefaultClient";
|
||||||
|
};
|
||||||
|
|
||||||
|
# setup allowed hosts
|
||||||
|
# CSRF_TRUSTED_ORIGINS = [ "https://${svc_domain}" ];
|
||||||
|
# ALLOWED_HOSTS = [ svc_domain ];
|
||||||
|
|
||||||
|
# disable recaptcha
|
||||||
|
RECAPTCHA_PUBLIC_KEY = "";
|
||||||
|
RECAPTCHA_PRIVATE_KEY = "";
|
||||||
|
USE_RECAPTCHA = false;
|
||||||
|
|
||||||
|
# does not work
|
||||||
|
STATIC_ROOT = "${cfg.package}/share/static";
|
||||||
|
COMPRESS_ROOT = STATIC_ROOT;
|
||||||
|
COMPRESS_OFFLINE = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# main service
|
||||||
|
systemd.services.wger = {
|
||||||
|
description = "wger fitness";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "networking.target" ];
|
||||||
|
|
||||||
|
script = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
gunicorn
|
||||||
|
# TODO: fix this, it should work with cfg.package
|
||||||
|
(pkgs.python3Packages.callPackage ./default.nix {})
|
||||||
|
]);
|
||||||
|
in ''
|
||||||
|
# initial setup
|
||||||
|
${cfg.package}/bin/wger migrate-db -s ${settingsFile} || true
|
||||||
|
# TODO: fix at some point
|
||||||
|
# ${cfg.package}/bin/wger load-fixtures -s ${settingsFile} || true
|
||||||
|
|
||||||
|
# run server
|
||||||
|
# ${cfg.package}/bin/wger start -s ${settingsFile}
|
||||||
|
PYTHONPATH="${pythonEnv}/${pkgs.python3.sitePackages}:${settingsFileDir}" ${pythonEnv}/bin/gunicorn wger.wsgi:application --reload --bind ${cfg.address}:${builtins.toString cfg.port}
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
EnvironmentFile = config.age.secrets.wger-env.path;
|
||||||
|
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "5s";
|
||||||
|
|
||||||
|
PrivateTmp = "yes";
|
||||||
|
|
||||||
|
User = cfg.user;
|
||||||
|
# TODO: fix this, maybe
|
||||||
|
Group = cfg.user;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# periodic keep up-to-date
|
||||||
|
systemd.timers."wger-housekeeping" = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig.OnCalendar = "daily";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."wger-housekeeping" = {
|
||||||
|
after = [ "wger.service" ];
|
||||||
|
requires = [ "wger.service" ];
|
||||||
|
script = ''
|
||||||
|
WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage sync-exercises || true
|
||||||
|
WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage download-exercise-images || true
|
||||||
|
WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage download-exercise-videos || true
|
||||||
|
# WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage sync-ingredients || true
|
||||||
|
${cfg.package}/bin/wger load-online-fixtures -s ${settingsFile} || true
|
||||||
|
WGER_SETTINGS=${settingsFile} ${cfg.package}/bin/manage exercises-health-check || true
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
EnvironmentFile = config.age.secrets.wger-env.path;
|
||||||
|
|
||||||
|
# Type = "oneshot";
|
||||||
|
User = cfg.user;
|
||||||
|
# TODO: fix this, maybe
|
||||||
|
Group = cfg.user;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# postgresql
|
||||||
|
services.postgresql = lib.mkIf cfg.configurePostgres {
|
||||||
|
ensureDatabases = [ cfg.user ];
|
||||||
|
ensureUsers = [{
|
||||||
|
name = cfg.user;
|
||||||
|
ensureDBOwnership = true;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
# redis
|
||||||
|
services.redis.servers.wger = lib.mkIf cfg.configureRedis {
|
||||||
|
enable = true;
|
||||||
|
user = cfg.user;
|
||||||
|
};
|
||||||
|
|
||||||
|
# setup user
|
||||||
|
users = optionalAttrs (cfg.user == defaultUser) {
|
||||||
|
users.${defaultUser} = {
|
||||||
|
group = defaultUser;
|
||||||
|
# TODO: fix this
|
||||||
|
# uid = config.ids.uids.paperless + 2;
|
||||||
|
uid = 738;
|
||||||
|
home = cfg.dataDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
groups.${defaultUser} = {
|
||||||
|
# TODO: fix this
|
||||||
|
# gid = config.ids.gids.paperless + 2;
|
||||||
|
gid = 738;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
55
machines/gerd/services/wger/wgerpkg/openfoodfacts.nix
Normal file
55
machines/gerd/services/wger/wgerpkg/openfoodfacts.nix
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildPythonPackage,
|
||||||
|
fetchFromGitHub,
|
||||||
|
poetry-core,
|
||||||
|
pydantic,
|
||||||
|
requests,
|
||||||
|
tqdm,
|
||||||
|
pillow,
|
||||||
|
redis,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "openfoodfacts-python";
|
||||||
|
version = "2.2.0";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "openfoodfacts";
|
||||||
|
repo = "openfoodfacts-python";
|
||||||
|
rev = "v${version}";
|
||||||
|
hash = "sha256-aG+zbFr7lhh5OCdPe7h2XJSwok7sdrnpsEBzPgJ6Bas=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
poetry-core
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
pydantic
|
||||||
|
requests
|
||||||
|
tqdm
|
||||||
|
];
|
||||||
|
|
||||||
|
optional-dependencies = {
|
||||||
|
Pillow = [
|
||||||
|
pillow
|
||||||
|
];
|
||||||
|
redis = [
|
||||||
|
redis
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
"openfoodfacts"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Python package for Open Food Facts";
|
||||||
|
homepage = "https://github.com/openfoodfacts/openfoodfacts-python";
|
||||||
|
changelog = "https://github.com/openfoodfacts/openfoodfacts-python/blob/${src.rev}/CHANGELOG.md";
|
||||||
|
license = with lib.licenses; [ mit asl20 ];
|
||||||
|
maintainers = with lib.maintainers; [ ];
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
diff --git a/django_bootstrap_breadcrumbs/templatetags/django_bootstrap_breadcrumbs.py b/django_bootstrap_breadcrumbs/templatetags/django_bootstrap_breadcrumbs.py
|
||||||
|
index 0e98c65..4a4c13e 100644
|
||||||
|
--- a/django_bootstrap_breadcrumbs/templatetags/django_bootstrap_breadcrumbs.py
|
||||||
|
+++ b/django_bootstrap_breadcrumbs/templatetags/django_bootstrap_breadcrumbs.py
|
||||||
|
@@ -12,7 +12,7 @@ from inspect import ismethod
|
||||||
|
|
||||||
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
-from django.utils.encoding import smart_text
|
||||||
|
+from django.utils.encoding import smart_str
|
||||||
|
from django.db.models import Model
|
||||||
|
from django.conf import settings
|
||||||
|
from django import template, VERSION
|
||||||
|
@@ -148,7 +148,7 @@ def render_breadcrumbs(context, *args):
|
||||||
|
kwargs=view_kwargs, current_app=current_app)
|
||||||
|
except NoReverseMatch:
|
||||||
|
url = viewname
|
||||||
|
- links.append((url, smart_text(label) if label else label))
|
||||||
|
+ links.append((url, smart_str(label) if label else label))
|
||||||
|
|
||||||
|
if not links:
|
||||||
|
return ''
|
|
@ -0,0 +1,32 @@
|
||||||
|
diff --git a/wger/exercises/api/views.py b/wger/exercises/api/views.py
|
||||||
|
index d6387bb2b..86bca386b 100644
|
||||||
|
--- a/wger/exercises/api/views.py
|
||||||
|
+++ b/wger/exercises/api/views.py
|
||||||
|
@@ -374,12 +374,13 @@ def search(request):
|
||||||
|
image = image_obj.image.url
|
||||||
|
t = get_thumbnailer(image_obj.image)
|
||||||
|
thumbnail = None
|
||||||
|
- try:
|
||||||
|
- thumbnail = t.get_thumbnail(aliases.get('micro_cropped')).url
|
||||||
|
- except InvalidImageFormatError as e:
|
||||||
|
- logger.info(f'InvalidImageFormatError while processing a thumbnail: {e}')
|
||||||
|
- except OSError as e:
|
||||||
|
- logger.info(f'OSError while processing a thumbnail: {e}')
|
||||||
|
+ if not image.lower().endswith(".gif"):
|
||||||
|
+ try:
|
||||||
|
+ thumbnail = t.get_thumbnail(aliases.get('micro_cropped')).url
|
||||||
|
+ except InvalidImageFormatError as e:
|
||||||
|
+ logger.info(f'InvalidImageFormatError while processing a thumbnail: {e}')
|
||||||
|
+ except OSError as e:
|
||||||
|
+ logger.info(f'OSError while processing a thumbnail: {e}')
|
||||||
|
|
||||||
|
result_json = {
|
||||||
|
'value': translation.name,
|
||||||
|
@@ -393,6 +394,7 @@ def search(request):
|
||||||
|
},
|
||||||
|
}
|
||||||
|
results.append(result_json)
|
||||||
|
+
|
||||||
|
response['suggestions'] = results
|
||||||
|
return Response(response)
|
||||||
|
|
36
machines/gerd/services/wger/wgerpkg/patches/manage.patch
Normal file
36
machines/gerd/services/wger/wgerpkg/patches/manage.patch
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
diff --git a/manage.py b/manage.py
|
||||||
|
index 873291be6..368de89fe 100644
|
||||||
|
--- a/manage.py
|
||||||
|
+++ b/manage.py
|
||||||
|
@@ -2,6 +2,7 @@
|
||||||
|
|
||||||
|
# Standard Library
|
||||||
|
import sys
|
||||||
|
+import os
|
||||||
|
|
||||||
|
# Django
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
@@ -12,13 +13,20 @@ from wger.tasks import (
|
||||||
|
setup_django_environment,
|
||||||
|
)
|
||||||
|
|
||||||
|
-
|
||||||
|
-if __name__ == '__main__':
|
||||||
|
+def main():
|
||||||
|
# If user passed the settings flag ignore the default wger settings
|
||||||
|
- if not any('--settings' in s for s in sys.argv):
|
||||||
|
+ settings_file = os.getenv("WGER_SETTINGS")
|
||||||
|
+
|
||||||
|
+ if not any('--settings' in s for s in sys.argv) and not settings_file:
|
||||||
|
setup_django_environment(get_path('settings.py'))
|
||||||
|
|
||||||
|
+ if settings_file:
|
||||||
|
+ setup_django_environment(get_path(settings_file))
|
||||||
|
+
|
||||||
|
# Alternative to above
|
||||||
|
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
|
||||||
|
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
+
|
||||||
|
+if __name__ == '__main__':
|
||||||
|
+ main()
|
24
machines/gerd/services/wger/wgerpkg/patches/pyproject.patch
Normal file
24
machines/gerd/services/wger/wgerpkg/patches/pyproject.patch
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
diff --git a/pyproject.toml b/pyproject.toml
|
||||||
|
index f10460b1e..62377bd9c 100644
|
||||||
|
--- a/pyproject.toml
|
||||||
|
+++ b/pyproject.toml
|
||||||
|
@@ -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.tasks:main"
|
||||||
|
+manage = "wger.manage:main"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = false
|
||||||
|
@@ -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,9 +1,13 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg 2i+hCYHZQ8bEtQJWnazPdAkDky907gzu1tMod6tIUkQ
|
-> ssh-ed25519 QSDXqg tEIy1xIa5R5j/fEdG7bF+DqcHjAM8ECWdVod2VAxPQw
|
||||||
c7AoKQEZERJziS+b89OP9v3j5BFG1FTcc5yK4U7wHtg
|
icCj7TE8HPoHKVKu6fCQf/xP960k+lThRssBbKDsgZA
|
||||||
-> ssh-ed25519 n8n9DQ O1jM3fRClKiKGaJig/u+APxwi/MzIvs7l/HC+rDiQiw
|
-> X25519 1zS/IpbtMt6ou+IPqAWkl5ZgzIPuw7f2/P0oOEcNsQ0
|
||||||
+0VQR4gO/rxXZJRjfv/t+mfaDi0kUioTom8OoNoFDio
|
lA3hqus1M7r9nfAemyVL17WRwaF9FPznt5L5J21Wy/s
|
||||||
-> ssh-ed25519 BTp6UA 93ld1x4OCnO4GshJz3Hf7mB2jFVGYqZQ8AwvB7cOqzg
|
-> ssh-ed25519 n8n9DQ PtaxammhSzlPG2mJqoPowrGOEhCG8zKhS778Vc8w1CM
|
||||||
AMFa8ueIf3Fz8VQpWWrS6ncfrh+pdsU7RMR3ZjA8KLE
|
MwTEogGIqX7tAWTiHWdoYg0prWf0iqRWupxQHllwZ3I
|
||||||
--- qDtFEysXwYfNfu63ufZFt2lARP72Gkx0Kp6zs81VkT8
|
-> ssh-ed25519 BTp6UA Ay9Y/HNv1P5d4Y/yQ+IAumk6P8fRcVB/Lo0EDXg4WCk
|
||||||
Oj´}¼4VfĬj¢Ç\cBÁ!9ÏìÚYÚ¨Ô(ìd2©\bÙs5…ïâ2ËhTRœ@êg¼ªÔ·®•„kì9¹S<½wq~ÕÞ%)º^B ÎõJS @Å©x±Í‘1[†Ì0œá>
|
68vCguKTvIvvyjD5jlsYe71wV/OjdTVzM3j6w5c1onk
|
||||||
|
--- K46hZrYKyLnC/zUbRQ48nAeyUFwrD7+JkMyMaKiecGg
|
||||||
|
ÍăŇbóÜ”š`f BŻĺoÎ(Č,•ąďsi}u).zŤ
|
||||||
|
L ·á îŠ<C3AE>LČ8- ŠŐ8ĄďS„÷×mRn=ĆőçÚç
|
||||||
|
aů3˝óKĐńl3´4ť¶¨(ň[]Ń:¤¨¬‰±ku
|
Binary file not shown.
Binary file not shown.
|
@ -1,10 +1,12 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg s4bJfm5nhl8dESl1yXgQFkCT2nJdKeMVhOC10Z1e1TE
|
-> ssh-ed25519 QSDXqg YoKK3e/O8rsIw9olLq0xOEbUHi/OWUlNTndiM6mW/x4
|
||||||
m1MEBzSr/GZRdNrw2ceFFVjFfcVOdO3D8dxsg4x/lUU
|
/F9jMFY31Brnj5UHzP6VmxUVvnsfvaM+rJc0gpn2Bc0
|
||||||
-> ssh-ed25519 n8n9DQ GwPbYmxKFHZ/JJtJV5o/MSi2mYyJtpupT6TF/QAUAjI
|
-> X25519 gGccDs1K9oQJYejMUS0EYJwgDcD0qMvNlgC+mYq0dTg
|
||||||
FZ0WMuYfq3e8Kcp7DAI6kkHVavfVFNm4mIwGbaw1VWk
|
sXZshUFzMF/vlo6KLIAWZD5D/jI35/fS0wKTAehuZB8
|
||||||
-> ssh-ed25519 BTp6UA QcXiF+NIbadObCT3jK7KnVluDqjFev+XA5xQJwk2cA4
|
-> ssh-ed25519 n8n9DQ 7NT1vUQu+HpRFULWtRc/o4J8O5Z3U8m3LlJ8Q2Bq9wo
|
||||||
/FKzec70a9cuKq3FStESSwbbgUi3Zf5k5xfa45eMB5g
|
UBDmhGTwtUmghywmzGTwhcu3fHExBFy0rif3pPfblrY
|
||||||
--- lwDjO24aMTssxFfekozBYCnigZJ7ztklFwFh0Gn10pA
|
-> ssh-ed25519 BTp6UA oSpx5EIkXL8nvmw72WdCwFomtGfpq5+SyrtKCIMu33Q
|
||||||
cïPvýqÕÿœæT‰Ï_Kt
``\˜–1_Ô0^S¬ô’BQ8Þ<38>u’Ã}òEËÒϬ¿â3{))š<3®uwCµ‹jëý„R ¡ÏÉ#û@g0xk TÍ8ÊR<C38A>Un·¨$
|
00AEgN3W0kiUivhA+xnfoETHhn4tvgJQMc7caGZ5q4w
|
||||||
æ³µ
|
--- T+xLNMpqfWIaV9dkq8l+kBuADWmtCIFrZhOoOK1fyro
|
||||||
|
õãÑXÔÁ?5åŽ=Cžwf®´<15>‹5l;?2l ²¾YB]ªqãhêÇ¥P’ <E28099>o.¿ð±’1«
|
||||||
|
„û§©–&.5—ÞÆxZ_!¶Ùð\ȸÒÈ=|è—ÍHtòìŠKDëmúD&Ý«=
|
|
@ -1,9 +1,11 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg ukkpdyQwjxQ5ZSDRNp3scWW/UaL9KkvYjgOohagME2E
|
-> ssh-ed25519 QSDXqg f5BkTN2OAhqz73HpxPFRKynlnOtzvTr7Xiwfrt5OblY
|
||||||
TBz/F6ki/WRQ36dwWGya/+jk6d/CVit0uk6ftUGwkM0
|
vyVQDs1RCxStxJcdL23b7xKJZM7kzx6/CafYbXcvZqA
|
||||||
-> ssh-ed25519 n8n9DQ 4tAfBISscjJkXdT2ze7qwjSgXsKVORQdJ6BU2FWziBI
|
-> X25519 i75r1tQHsdp8/znywa9dPJ290PYZtz6r37pobJd4rF0
|
||||||
KsgjA34+cX5JP5zQDJu2S42T07L1bUH6rFNLnGpGsCk
|
xk/B0oIOT3C16pZMWzo2ab2507rkVjCDf5JTocHL9uw
|
||||||
-> ssh-ed25519 BTp6UA MN57AovukP5h7xP0TtdZJnbGUVGem9Ag4yrXeQPLOmc
|
-> ssh-ed25519 n8n9DQ LCWwNu2knTWEVyM95r7JZRa1ewrbLG8jfkJDcGuCFgM
|
||||||
XvlsXC6kI6gbzrujxfGQII2bwPoXd7pAQfP3oXGqe4U
|
3Oe9WnBHLvOvWdHBHX1/w6oHZAgcAWVLI+3WWNEcJUo
|
||||||
--- qWkj5My+16z9Qjge9GR0ezFkzi4zONiEny+I/5j9qpQ
|
-> ssh-ed25519 BTp6UA g2xh6AGZTvC4wl6V9HRZSM/np12aRMI0bhB2r/tRBkI
|
||||||
*ヲ{|<7C>o訒<6F>i_k}oセ暉<EFBDBE>ー乕z)ァ讐ネQウオク、ニc弥戻ヲH「、<]約・g\{W<><57>7:
|
Stk2VwOZL/Mr3jhBEG4GLn5Gve1qjsRltdPn/XQQZBI
|
||||||
|
--- 5pvauehP6HZ7uOzWT2vYN3T9JKLYZ9lVhBOyOC22/YM
|
||||||
|
×ZÛÛ‹ÃP<C383><50>¤Ú%[bãcçµø€ª,—6BÏ}îÄZÆΑjc¢¤íE8O/íå&D}/÷f9·²ºå–ÔD
|
|
@ -1,10 +1,11 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg /Ywa18VQyXbCgwIBWGRDB0m9mNd7TtQH4HEQvJpxLkU
|
-> ssh-ed25519 QSDXqg 73fdkNhCNfHaCy86Ec3KL5oaZfzsn189LX5VpJSZVCM
|
||||||
NdigMBP4yDz1v6Q8OXGu7lOd4JpxnBJuaWj5xgz/I/w
|
MdBa9ufGc1Zh7bIph+wQkfwrAlkTLE4KtC59nD9bjvk
|
||||||
-> ssh-ed25519 n8n9DQ yAQO33Csz6+h8dEKmOvVbZUgxN+nPY6+OvE2W3wBNmI
|
-> X25519 3uJmCujV7zE6Lf9mbcGCoHrUjceLyRIg4RrJQHoLcQ0
|
||||||
5v8JM8vHAmWUlnYiK+eBhp+BIKwbGSOS4UzFpxuvzEo
|
P3OP2PH5i/bRTuxeMGOeKsTuN8j9i+1DqT4m3oqLl5g
|
||||||
-> ssh-ed25519 BTp6UA VnmGREd7Rn1c4sYJRo85cvnuH1QBTQxG6P+c/tdat1M
|
-> ssh-ed25519 n8n9DQ KOQn0fTCgfO/CaBPmlsTERgByfkMBR/g+MAzdCsg5C0
|
||||||
0TBJ+a1BBtFBo4beFx5671hIq/pluFJ9wiUK59dZEc0
|
C3hY9QIr2DsNk1xz/58h5gKMRb18IXr13NWbKrvxdAA
|
||||||
--- qzbsERkRBc+PLfAg8/+MiwO2Rh2bWQi6YD0B1QiyzJ0
|
-> ssh-ed25519 BTp6UA SLKD+FrhoE+SIDpV+WdBq9epMHTZsrMEKLvBNc8XQwc
|
||||||
<EFBFBD>ra•ËteX
PœZ¥Á Ê!Y *ð§aþ™ß;‰í±ˆYöÏá&¶
|
DZyFUjhZHuelH5Rs0XV49aqc5PlHZyUqSdy/GgHJ9+g
|
||||||
eñ4<>¡¹ÿéì¥UÉz )ºº2«
Ê’ «¤>íº8SßozRÈÁ@·Âè(UÒ´rܹË$åUVóÆßäw
|
--- 09dMjfzGZgH1Ol73FQxDx/Lg51TFMLePxrOE4WDThFo
|
||||||
|
J—m'Ú¤öî¢Ë_£e»ÞHfPS’ë¢áØÖ-ìG4K銔0иÆåtβÒw×0JUóß9ÞÙŸEóeIëºíÑ#ËÊêðåq+¦3a¢‰ñQ¬ÑXHXŽØà$õëÈÉ
|
|
@ -38,6 +38,16 @@
|
||||||
|
|
||||||
# matrix-synapse
|
# matrix-synapse
|
||||||
matrix-synapse-config-authelia-secret.file = ./matrix-synapse/config-authelia-secret.age;
|
matrix-synapse-config-authelia-secret.file = ./matrix-synapse/config-authelia-secret.age;
|
||||||
|
|
||||||
|
# wger
|
||||||
|
wger-env.file = ./wger/env.age;
|
||||||
|
|
||||||
|
# restic
|
||||||
|
restic-env.file = ./restic/env.age;
|
||||||
|
restic-pass.file = ./restic/pass.age;
|
||||||
|
|
||||||
|
# searx
|
||||||
|
searx-env.file = ./searx/env.age;
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups.secrets-lldap-bind-user-pass = {};
|
users.groups.secrets-lldap-bind-user-pass = {};
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg 6QDsf2ACEhUdb2IUxDaILcvoPLPdpXupn7DDTQCREWA
|
-> ssh-ed25519 QSDXqg Z2carBfkWnq3pSVRwIsaOK4LtUwQylMzl6qhJrVHPFk
|
||||||
8VYqwj6UoIee6DHG+bF3lHnY+NxLP8KkjjxynSiEUEk
|
84ho1ewfxZLddbwoTS8OfK2+DqQee+ew1QfkM1WodZM
|
||||||
-> ssh-ed25519 n8n9DQ wAIhE1M0sMi+0tdc2fZDFzLJLlaZkhZOXIEkB2lm3UE
|
-> X25519 zNMm+jdN0pdifxqkCRrwSMlwnDth31GojIrVROyUCEo
|
||||||
iG5tvWLkYfv/klmc0LjUn+96RuYTRUrLbMpPicpVchc
|
fpgNEoxiMGDDvj/xvznNRdaS1Q4gHxu02MoqH7zqjN8
|
||||||
-> ssh-ed25519 BTp6UA GSOxjXyCvjwg/jS3pG3gegh9jcYbVHy3y4v/dUM7plQ
|
-> ssh-ed25519 n8n9DQ wIJ3wnHibsg0UrxrzDhKXeXzDgH3wNraL1DTa9K2clU
|
||||||
ItU5OQJswLZ3nCpfUcLMhkdEVF6Iu+lPP0qGj9MU7h0
|
myZVNdu74xFFZ2NiljKHq8/KlJwyhXWB+rkCFTz1IlY
|
||||||
--- ONOkuJRp+quOeMUJ6kf+j4Bweks4FAGurAE3xgk3wIw
|
-> ssh-ed25519 BTp6UA 7R89FE3UckXyVnSqnxv+fI9FQ8FTeuRIQAy4crC6USc
|
||||||
ðà7‡h#p€<>·ŒØ˜ÿ,ŒÙîúà‹SV|^8"‰)¢š§†‡è]ýbiàڡܶġTWêi Aÿ
|
wBWGkwiF4b+fjS7VMDlqLIzMh7oj2VyYujIRDFUo41o
|
||||||
¹\›ò¾%X¨v
«
4Fè”9{<7B>FY’<59>"‹Ä5`wZ¬¼¸MÓ$eý='€a
|
--- L89Yp8X8Pte6KefTjNtz4YX+2d66w71EOg6dHHeiPys
|
||||||
|
šÑ ÌÅ¥lº·g$+ŠG%œÏ0®(¸AÊÊÿÊ(7DÒñ®Ž$º
f|Ú'£D«böRÚæL¥š"q¥i„y$A"Q î÷"ô
|
||||||
|
ý¸®Ýœ÷>,¥½ho”8"š›Š!›óºo–L<E28093>ª‰
|
|
@ -1,10 +1,11 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg GGeJqXdtwwxIlkG/yl4DfkKykQ3uJyWqLguJ680vZlY
|
-> ssh-ed25519 QSDXqg n5RiJL05MGTaHFNBOq4V7aJDdnw2wNI9S4rgHwLLIkE
|
||||||
LS19/W+IHFSAeog3c2qAzvgE2VDWF81B5ehqo2xoCVk
|
M67SzRRJzEbWzlLp4QJG71dbQ5HEp8R5n6Tmxl//PtQ
|
||||||
-> ssh-ed25519 n8n9DQ 8xOzOWPQEwAAslYAg71Hf8sf67+QGFKeX280ueXrYVk
|
-> X25519 O3ZSSvjwXHqZbQcrIAiMbbscdYpC29NQKNP+jIX2GW4
|
||||||
ZdzT710/gB1N7eosXQbyRdyzQvQDuLeCFS6ocpkvooU
|
8fbx+Dfgg+Huueeic272Nz9hXkntkMSt8zoYaUCgzy0
|
||||||
-> ssh-ed25519 BTp6UA RyRdwb7gHk74LgqEmWUJ8SpiS94IHczpO2ZokCFO0QY
|
-> ssh-ed25519 n8n9DQ RUKMRjuVi4pjCqJO9ZqP5txbBNnq8yQ84Rvqq0ooG3g
|
||||||
c3t3vZyRqSIWiFnt0slV8AjACKW44PgUvwijLTNigck
|
JuEioORVfSLsOHqSmdQaJBd+xp/yYl/wuJUvhTQEmDY
|
||||||
--- emrYR6UhtLGsqpz7q+KAivD5e0sAf6zaA5qh3vD/13A
|
-> ssh-ed25519 BTp6UA NEPujkvmlk9mHl7KeaCKh7lOSanfKlU2lm9dqMy1yEc
|
||||||
Ùüù‚×H@ø|²aè›>á3C‰Œ&*µ_8
|
v2LSptbFd/bEwh+AKsXGnNoALLwyjRYEO8wYaxSp+x8
|
||||||
~ç7RÛ†)°$<24>aÄçü–]éD©y±±}ß.Ê:
|
--- vjwuX+eM6Hbzd5URUnhWYArVBNd7q/5+DM6FiTc/0OQ
|
||||||
|
<«×è ݵ.ŒÇ|þÏî2y%Û—ô¡@´@—¦£‹LRQ4SΈhâqSzÝßÜc¥”€Cßå„2)æ>«‰,ðý
|
|
@ -1,10 +1,12 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg lB8SsqHApg4Bmrg+YP4Gns7+UtUb8jOjxEXzTRHT4A8
|
-> ssh-ed25519 QSDXqg UVHeL31U+0oPD6WkJcgaGJKn6RCLchQwi4RbV3t1ViY
|
||||||
psFKfMdO5j54Q1ISyA+FgWZCRHVmEWXNNkjweqWZ1qs
|
HndUvlRyE4ewwN0I+fjR8/V8i2hpdm2T77T5mkGDwqs
|
||||||
-> ssh-ed25519 n8n9DQ WGAMIZbfqukK5mrTlKYy8rUNy+DEwxFijZiCxOGlX1o
|
-> X25519 471FQ92/VNQ0rMpA4TYpa4XLhl5neg/Rvly+1qMznxk
|
||||||
7/YPyZbUfJ7SB9T38JwgcW2LAnZSLgMFDbf6N7NbRbI
|
mfGl3rlfbcwYgUusDDt6tpdD5p+/Qqlx47NHDmluqfM
|
||||||
-> ssh-ed25519 BTp6UA ZkEWRVFiMWnQQap3CGb+FihCw/y8funz6UFuxPYsmQU
|
-> ssh-ed25519 n8n9DQ Or6JhMO4kbyvjReIeHPk+Qy/mZv3Aq0jmuL+lxGCdk4
|
||||||
e9h5DaikoTLmsWPIC82DA7EUUOX7X1ZrSmBKeMk9T04
|
Z1i3PDxXR3hBxH5JjhQip0KbLJOBDDtG2fftvjA7Urs
|
||||||
--- Uif4qB0IT17YXBP1r36yXjUzO2rd6wuVi9wP0x0D1WY
|
-> ssh-ed25519 BTp6UA TfNnysrP9cXIHILbD9anYH70wfiloFcZUTX2yavpfkM
|
||||||
ËÔèw¥Zhõ(È‚ùSú¯ÝFø+;•LÛÕ%þúЛ“vÌ<76>4›ßh?ÞàýdǤ’¯_
|
/Jn50hf5TK8DnOZGniK1Klung9O4SVal3QhUjxXweq0
|
||||||
ŸÎô&Ê%Kì<4B>-ïO+ªš™Díü¯"R™¨A›#_£Ø.
|
--- m4HRBmPMsLUnS4n1IJwi0+bVPAIdXjgGUDS91dd5kbk
|
||||||
|
Û;)—©ã#^VðA¿<41>®!vˆ‚‚û)4ÇR4z¦‘<C2A6>ÇÔ²;üHl£_y|„Iü}¿…¤ÜÅ·'3ê]ˆ„?ì+í³£NkÅv¿7_
|
||||||
|
“ã;,«Êµºa9[§Á
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,9 +1,11 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg LoWIvj4OQjNPaGbtQYSUEKtkqvcVa2pPisjyXL6ajy0
|
-> ssh-ed25519 QSDXqg MSy+VhPuDpa27TVJiIMdTjEoso41n768EhR3xzQQpgY
|
||||||
ZfLdRcsWa4Nc6HdiWO1GCgSgHm7aZeUdEDCjUCn6CuY
|
vmzBPGJ0p10rq8I7dC3+aIZ0BnI9Y9s55kaPTVLM/Ls
|
||||||
-> ssh-ed25519 n8n9DQ e7DWlUZdaDPgoS0Ylnxtf80IN+QMtCJ48oI4Z4U9+0I
|
-> X25519 Y9q6F+lxCywb/EKVGr23o3lDariEA2gT1+mlUI75yBQ
|
||||||
/2ZleHBcAkWh8Udt6D2QgBOCTKkqH3GIsGsGexpAaxA
|
FS86DVHDG1iwb5EoY+agq7cgpTzC94QwUEL859UvNoM
|
||||||
-> ssh-ed25519 BTp6UA bgTa1+cFzW07nPhe/5GKW1RreVO5IqIzvPZTYpnrGjY
|
-> ssh-ed25519 n8n9DQ H67QnwjkUMV5vBXTVNonnNuNUfqDjeT1jlh+frpkUgk
|
||||||
7F4HnAnHVZX+dfOpc5mPB4/TTgPgw8hiIyVTEbffRQw
|
UyGk2g8qmkl8oLTFPaPg+drNMKHtbDoPrZ8AgMvUWzM
|
||||||
--- IrCqHtOIS3c5By3cBTPQAGpM2GzCu61AhiavRjozk7o
|
-> ssh-ed25519 BTp6UA nuSVT7cP/UdyZPFzFjuo4kWcUzWM9zibc+4KN4gPBDM
|
||||||
<EFBFBD>hカ<15>ネ都・ケ5+RBi}マ黍<EFBE8F>ヘ瀨 ハN$wト:カ![オs<EFBDB5>ツ、゙<EFBDA4>ホア、<EFBDB1>.ョgR・ト「>
|
BBx6wq5MKk5KoFcGt3SEuDNC1jaITZpy3pCE/oUF7vE
|
||||||
|
--- hCZOhBFtLe/I8tRJNtRB2F7fNxLTi7sOo5ZQxCNUha4
|
||||||
|
R-¸¾ê\v¹á/ǨÿÌñ9¬
Ùgñø1·O8ŒÏ&x˜Ôj‘ÈÜ”ŒËâØÁùà8êXvL@6w‚ëj?šfô
|
Binary file not shown.
Binary file not shown.
11
secrets/restic/env.age
Normal file
11
secrets/restic/env.age
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 QSDXqg PybnzljzRzswiQPSo1I10lSPRjXHd8rVFSNDH1ZsUig
|
||||||
|
RzLFaSgJWuDDBS+eTmz0J2aVWjTWV50laojbkyzp4fM
|
||||||
|
-> X25519 ZCS4baMlt3oGpkHjdeQibFt4oxum00sHV55sW5yW+3I
|
||||||
|
oT/YlQ4sAYkOC4V6+PfK+CYgDT2l/fOlQJ+sVaBVYV4
|
||||||
|
-> ssh-ed25519 n8n9DQ anOLNIDopvdtK7A8BH/bzcz3plEzULJW73BvGS9aSmQ
|
||||||
|
YPzmwoT/Ltnu5GvicbCs9qqN5CjlsoHClN3seAQdRSo
|
||||||
|
-> ssh-ed25519 BTp6UA 8H6CnD8TJUP5acPMs/9Yvnc9cu2kx1blrK/oDlts4Fg
|
||||||
|
O6JIlYDxQB10liQ8tqIqi/Gya3k0v/pcIKbI0VBUyn0
|
||||||
|
--- PjFfEkfF3yWY4QolKjwCv6Mj30AcoL4cE0qKlgaUV5o
|
||||||
|
„h8°^&C¹“ôÏ<C3B4>=‘“&âö«éÒò#Þo…Y–+{j<>ÒÞ]Œ“Vïî<C3AF>ÝEîQ <09>[M±«òb»wsྼe¨£lГ0}3Ò´ÚCæ5HK<uïÄò{Ãa´ôbÎ,KoD[“
탗_ך¨
|
12
secrets/restic/pass.age
Normal file
12
secrets/restic/pass.age
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 QSDXqg +vE6VbbU5XgX0XkEWh9crm+5mdtURyQAqVffU7EXfFg
|
||||||
|
UAx9/0QBx+liLIHc2S6Z/JZmtIcBuzxYOlM9YMC4CfI
|
||||||
|
-> X25519 KcSKOsQyykcTUtcJYhkU+s6b9xzEQp5nxxdC2lmd8xU
|
||||||
|
oZUIGnUXg5bYCCqeNSHs2cXF4LnxGIYC0HyapGoaF0k
|
||||||
|
-> ssh-ed25519 n8n9DQ E47ziDXHHPcsQtaHPT17XkgoCcvCQcyFIluEycWfQWw
|
||||||
|
MHpLKSfAUAuVoCxcrpH87dJKnq0qK0Nvek9QIpdLPpE
|
||||||
|
-> ssh-ed25519 BTp6UA JlHAZaDZkZoL8jHepRFB6CpfmgNPD/gNeXBMXzQuVmY
|
||||||
|
XadtJ2aBU5f6mxAb7iCvBRvTr8skt+1OMIqJ2DOr8JI
|
||||||
|
--- m/WIZdO5VuSKn3rj6f0ZY5+P8dejPOf1N8niALApC08
|
||||||
|
}L<>vÆ"Ë{ã÷%¤ð²ñwáÏÌÑFk7ó¯0™Ð.‚ŒËÀÁ“b2VCêí{…ò‰åîêœ')ˆowóD/þÅ`ö¨*ìñr Ã
|
||||||
|
xî…ÌdD’ß±À&¥<ß
|
11
secrets/searx/env.age
Normal file
11
secrets/searx/env.age
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 QSDXqg fNc/NTcJ2bRYE0SIvKFIJG8mo9RrAvSxAB97RC+Gywg
|
||||||
|
10OeGwUb1GaxvS6gOMh4yIJsTAq1Q7QoLasHp0OSzEo
|
||||||
|
-> X25519 YSMcNwnLlqTImvRejhbowWVxTTo1bkhUL6BrQbXq7ns
|
||||||
|
LLIOfY+u/qT/PVBIniGh+WztTwmHGJuY8cgWuqGEkhg
|
||||||
|
-> ssh-ed25519 n8n9DQ Up+IltuGUo5c9MOGjrNV8tZH/CicwxKmRAJrlNL+bWw
|
||||||
|
P3d/iYWJNDl5FedbSXUrtVtgZ8YJTx0BHToIzZqMnxM
|
||||||
|
-> ssh-ed25519 BTp6UA 5fO9KgLtLJ2DEY4YuW5Ybt/BtziL1JmRUwJ0xYyMVhE
|
||||||
|
uOqXkfhkjG9ocjEzboWKe39+18q+Dr1WRIppQA4B/h4
|
||||||
|
--- rjsMU+9R21buoWlPfLWXxcBk5mHsSZ0H9uLgZTyS/9s
|
||||||
|
@~ø‹WÅOì4@U’.ÎcRÄšªbýÎ:øD¥<>ô΀ÕÅ)]XÑ—¤HßP]^þÖ<C2AD>úFœ‹4º=%–8K@biçѼىžÝqÔfö”j”1OZ_n¦
|
|
@ -1,7 +1,13 @@
|
||||||
let
|
let
|
||||||
user_eyjhb = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPuma8g+U8Wh+4mLvZoV9V+ngPqxjuIG4zhsbaTeXq65 eyjhb@chronos";
|
user_eyjhb = [
|
||||||
user_rendal = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGee4uz+HDOj4Y4ANOhWJhoc4mMLP1gz6rpKoMueQF2J rendal@popper";
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPuma8g+U8Wh+4mLvZoV9V+ngPqxjuIG4zhsbaTeXq65 eyjhb@chronos"
|
||||||
users = [ user_eyjhb user_rendal ];
|
# BREAK IN CASE OF EMERGENCY (secret key for age below) hQIMA5HKM7J/GmnvARAAjjoDyDr3mSfrM+jmrIaXj40U/IaDJzNyFI0pnqurjR9MlmSmm0gMm/nbqZWDt3HKP6YsbMb+YvDGA2kNf7SJ7EWK/dldmXkfWojlJvGUwDtNam3rqoyTdyoscG7E43PukTC8us5LY4izLJxESKy0b6hUAY6vocD4zRKqY+EaKjgIa1AUpwOD/qMIosh1m2irUV9+li0RsxCPUt/e59Tdo/mj7gGZ7iXnxwLFM59d+rKTuh3r4cm2VDgm1zF0wnusCtkMNtJEnOE0Nu6TOK7YQ0EX7AHy3Wr2cItAskFzcZY2IFxaO3TXEYj08dvtIgCuyhtxxFGihw2HzTZMlsByT2lbVpPDthgNAAnMGtqKRwkPzem/S6vITwlP2J1/iIu0olbs1lDHixym2kXua46q93M3CFP6ZKPo6o0C12Tx9Cj/nHv9gfd2TsFPucqlwvZsWzT/WVRpzYRBgWXMB1/z62fK4JuGPOtppNbcVzUH86HDcZ9CiYh6NTGJmP8OWg832GT3L1VrcY3e63FOUEgusyBsUeHE54TRfg6MPxUPmkVuVB6+r/ika9HdM8d4/jve/Yu0OUmQFpSfBodGmxtE8Wmqd9heQ6n4CPopR5U2aFayC3ES/5/82qHZDlFvB7U5HPlH8gn0AJR+KmP7qnsqz+NI/f4/yIYXC52wIkeFHxDSwDkBudK/7gJFsmfEBNzzN+F9LwdA69CwDVDV9zCSrzsEHsAkTxJ/unkDvij3Dv4MqTFKWvqC+smlsR1/i/p7+tB1PZRUe9B9spBP76fygnpF3HqRmXkT2C+qEJ5nlGr3pK46oXP8d85nyTO3e5X97mM2hxmhKzpiX+aFjHN9LYLUQAmzHTPz0Lktnj7STf1ve8LZCvppvQzAuqTcbpaer9Qx4moNWr7NtJuPM0vT+adc6AbrY6bgdewisSP1QD34tVvNcUVLdICUSoTUI0eEfmQop5ba0ayFDsotTFZXNtl3Qmb/OpUit7f88wL98EjbK+904dtAOUs/9FE=
|
||||||
|
"age1xnll9l56j0clh9e9r7ha9sy7sjdcxnhtaxljz2p96ectktq33vgsvteua6"
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
user_rendal = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGee4uz+HDOj4Y4ANOhWJhoc4mMLP1gz6rpKoMueQF2J rendal@popper" ];
|
||||||
|
users = user_eyjhb ++ user_rendal;
|
||||||
|
|
||||||
|
|
||||||
system_gerd = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJosDVq8j4V50/z6nj2OMBPhqda95HOS1hKLGvo8viLQ";
|
system_gerd = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJosDVq8j4V50/z6nj2OMBPhqda95HOS1hKLGvo8viLQ";
|
||||||
|
@ -42,4 +48,14 @@ in
|
||||||
|
|
||||||
# matrix-synapse
|
# matrix-synapse
|
||||||
"matrix-synapse/config-authelia-secret.age".publicKeys = defaultAccess;
|
"matrix-synapse/config-authelia-secret.age".publicKeys = defaultAccess;
|
||||||
|
|
||||||
|
# wger
|
||||||
|
"wger/env.age".publicKeys = defaultAccess;
|
||||||
|
|
||||||
|
# restic
|
||||||
|
"restic/env.age".publicKeys = defaultAccess;
|
||||||
|
"restic/pass.age".publicKeys = defaultAccess;
|
||||||
|
|
||||||
|
# searx
|
||||||
|
"searx/env.age".publicKeys = defaultAccess;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg 9NdZ2auQY3xgJyg6bk9IXc54E7s0iBIEaqTJPze6aVo
|
-> ssh-ed25519 QSDXqg pfgiaD4SEf6zNTAkR2rEvIxbzj9ShEZRQzr4Jh5v0Ew
|
||||||
sO+XRHIh8+BzH21AuIdSN8V9eZK8bL7synmip7OF+e8
|
22KV1flM3fnMmrLultlW0gfnh7bH6AxFe+4R0ieEyrU
|
||||||
-> ssh-ed25519 n8n9DQ RT5uVILvBUdHPxCcJ1pYgiiCGTgIpn1UrAYnrpgy3RM
|
-> X25519 g/uqAKvSMq8DEmobebJQd6zul8tQtajy9RgBc9AC2UA
|
||||||
HfHBx5QVn8ahHk1NjxZWsfrD2W2D0E25mQrZGkiX6WQ
|
K5ICYY30PVRFxNYyKT0PTK7I3ALVNVydCeFYOMBGax0
|
||||||
-> ssh-ed25519 BTp6UA AY1xVYOt+wTYPBSweXv7QvpJOsEawEBufpI6nR0+0Ds
|
-> ssh-ed25519 n8n9DQ HtNO+ClXwDyAG//RuTrZZIidcFqoq+M/Xt0jBHehnhA
|
||||||
OxwPG/E5EatvwUgvhqfW8dVG/r4fjbf0Nfawz/BBgD8
|
umeo/xqseBvfKNy7qe8Wrkr4xCwymiW+zlMgxYvHGo0
|
||||||
--- 6tjacKndq1+4t3+EEnl+Sr2qDIlrhvE4WqtxPXZTUVM
|
-> ssh-ed25519 BTp6UA Xs2dPjjLaIBo6cWs/oFNiBtwWmc0WVm71LJkOSCBrV4
|
||||||
ô×°ëÝà8L󡈸zÆW™¼–n—Ö”
|
w3/cp0Z8ODoBTNgWl8E/z2xge1vdybpmAsc5poLrzdQ
|
||||||
voQ–OA¼S˜io×ÑO •h\î÷Ï<C3B7>É/hF1–8¿ #bƒ
|
--- 1Mem//qXpGotxgPb4N2hwxrNcKICgggVEKd/ghqZOaQ
|
||||||
|
[Ütºˆ6ˆm´àÙL…=þØ/ÎJ{Ã/ÕxEý¢QÇÃ’Þ‹x¼¡gûF¶¡†ruÒ9¢<39>ñù|øzŠ„ùüúS´
|
|
@ -1,10 +1,11 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 QSDXqg ntHsb1DKwiuswQq/BKhTw0mmvlnvanpPhjdY3lD4NWo
|
-> ssh-ed25519 QSDXqg zXCmzKXdRLnwMmIle54sOj4XVS5CI4FsNnA1EgorbzI
|
||||||
uXVQHnjMlPBFPgbAB9oLARUMZ78EW15Kv7YgKXeL/YU
|
A5oJSf6jXfLE+5C2hAX6f6Q0Em5CuWnkZQ/E3ikynNA
|
||||||
-> ssh-ed25519 n8n9DQ BSnWJrmod7N8R0HxhDE+M/pycuTXzE2WsYzWAi846Ww
|
-> X25519 NV19ktGN64nxgNQcnedYgL+WMSRhaTx5NIiJUaKCWkE
|
||||||
G4c3sZ2holV5VBpPIibkfXXSW638uv26Cow2LFdEkLw
|
6iVlgBJxO3UzJYuJJ2riqFz6ziXzvaaKCsnopdvv/QM
|
||||||
-> ssh-ed25519 BTp6UA 0mA1o+wWla0IFOqaZNVNdgzOqc1fawUde9dqEUi4tws
|
-> ssh-ed25519 n8n9DQ 8hxpbyHwgoml0AwjuidIwKBLSLBpSsjkNpGe0Yl/ITI
|
||||||
OGhqQu4ogpRo5jHZsYCyNb4VaQOfeKdOJpI8r8LcVVs
|
oNddyuP5XFeWIG614RH2p5MGQPCUbKY+Be9EPSIOB/Y
|
||||||
--- /RpUgDJ08NIK80JCW+UhEODKdXBpkPchxetQuTpcH94
|
-> ssh-ed25519 BTp6UA Vh2qtUDXAyLx2F4Pa/rQGOjR99qt0Tf/acshYncR0Fs
|
||||||
âl©ÎY |-LX á@Gû[[ˆÆˆÈ;œO`ó.c‡ÚE#µJÅM]ÇzïK^ÎéJ…Ä{ÿh§½RAPvNî0¿¤³Gÿ'
|
nxhW5+EiJ9nCimkyEZwvItjMu5b4YFcqgd9n258ruLU
|
||||||
èu#¹Ú_é
|
--- nc8xoR3Obt7JEmk3XNdBtzl0woCpjuH7FRcXGS4Dr1A
|
||||||
|
TJyz~þb£Éc¡J¸)SRcœ+$âóÁ°û™Ú Iâ>fY<66>¶2ž
s$<24>§N"Ó2ƒO¾^Ndø½ƒéj“Ä4±~™º>K–NŠ|£5é}Œç
|
11
secrets/wger/env.age
Normal file
11
secrets/wger/env.age
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
age-encryption.org/v1
|
||||||
|
-> 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°
|
|
@ -22,6 +22,11 @@ in {
|
||||||
# only allow PFS-enabled ciphers with AES256
|
# only allow PFS-enabled ciphers with AES256
|
||||||
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
|
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
|
||||||
|
|
||||||
|
# disable access logs
|
||||||
|
commonHttpConfig= ''
|
||||||
|
access_log off;
|
||||||
|
'';
|
||||||
|
|
||||||
# setup a default site
|
# setup a default site
|
||||||
virtualHosts.default = {
|
virtualHosts.default = {
|
||||||
default = lib.mkDefault true;
|
default = lib.mkDefault true;
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
compression = "zstd";
|
compression = "zstd";
|
||||||
|
|
||||||
# default to backup all databadatabases
|
# default to backup all databases
|
||||||
databases = config.services.postgresql.ensureDatabases;
|
databases = config.services.postgresql.ensureDatabases;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
30
shared/applications/server/restic.nix
Normal file
30
shared/applications/server/restic.nix
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
services.restic = {
|
||||||
|
# enable = true;
|
||||||
|
|
||||||
|
backups.main = {
|
||||||
|
repository = "b2:situla-${config.mine.shared.settings.brand_lower}:.";
|
||||||
|
|
||||||
|
passwordFile = config.age.secrets."restic-pass".path;
|
||||||
|
environmentFile = config.age.secrets."restic-env".path;
|
||||||
|
|
||||||
|
# take all `.*/safe/.*` and `.*/backup/.*` zfs volumes
|
||||||
|
paths = let
|
||||||
|
backupPaths = lib.filterAttrs (n: _:
|
||||||
|
(lib.hasInfix "/safe/" n) || (lib.hasInfix "/backup/" n)
|
||||||
|
) config.mine.zfsMounts;
|
||||||
|
in lib.attrValues backupPaths;
|
||||||
|
|
||||||
|
initialize = true;
|
||||||
|
runCheck = true;
|
||||||
|
pruneOpts = [
|
||||||
|
"--keep-last 7"
|
||||||
|
"--keep-weekly 4"
|
||||||
|
"--keep-monthly 6"
|
||||||
|
"--keep-yearly 2"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -6,7 +6,13 @@
|
||||||
settings.PasswordAuthentication = false;
|
settings.PasswordAuthentication = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
journald.extraConfig = "SystemMaxUse=100M";
|
journald = {
|
||||||
|
storage = "volatile";
|
||||||
|
extraConfig = ''
|
||||||
|
SystemMaxUse=100M
|
||||||
|
MaxRetentionSec=1d
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nix = {
|
nix = {
|
||||||
|
@ -46,6 +52,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = [
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCpGGdH8BpCM9pUSANM9vYA4b/V2zGJK+GGpi8N/Qp+j32TRD6UsA0g42o4tL72Hsv3PKUU5vaZaXjeSSZYpwaNCe1aR7ehBesEvcgeiU66jBQ6JfMoArF+ZreveXQvtYeqcN6Iyijcu7vyqWIcybT5yOEiylQhB2bUd5lVR9KDAW3z6zhiVPxGmC8D09uZVxsGPfAPxyKvRs6Jkq0d67nDI9yUOtRJEdMvrDDhGzHQhKRuxl+NHtYCOa9octFZMcpEssmUOS97KNgBhglSZlz4a5PKUO7NmLZEgrCjw/aAKyepRenB3a7R/20lJvsN4YsIAR/rVH6bdrYhWKOjUrXm3PFPBs7CxdMP9qs4LEM1AMJ0dTw40AE94HfvilEV3HV+WSjen1dcHJNrSQiOAfXZPVjkkmnrum6p3R1gPcezhrGuWZv/RDgJIflo6Kd3heCe9gk1tV/lYswm5l9Cpg5gIUiMd01UfXI4FvxFQcE4AIBs8UHOhorIbjDbNTeZoBxXZFWMRUTVNR37hZRBnp/Ept0WOsIhlqi0V/oGRAilVy2a0Xs9dwX785W8Q9g5weT+fUR71huTjEEQnz7/VGcOPE64mD3yh7rmxYi6wMjoG6/NxzRBs4KRux5q+MAHxl0jDCgV+0fx78xtlH9Zb3/d5cgZ4TwPeIElS4g1b5FFBQ== eyjhb@key"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPuma8g+U8Wh+4mLvZoV9V+ngPqxjuIG4zhsbaTeXq65 eyjhb@chronos"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPuma8g+U8Wh+4mLvZoV9V+ngPqxjuIG4zhsbaTeXq65 eyjhb@chronos"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGee4uz+HDOj4Y4ANOhWJhoc4mMLP1gz6rpKoMueQF2J rendal@popper"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGee4uz+HDOj4Y4ANOhWJhoc4mMLP1gz6rpKoMueQF2J rendal@popper"
|
||||||
];
|
];
|
||||||
|
|
|
@ -18,4 +18,7 @@ in {
|
||||||
mine.shared.settings.domain_sld = "fricloud";
|
mine.shared.settings.domain_sld = "fricloud";
|
||||||
# mine.shared.settings.domain = "${config.mine.shared.settings.domain_sld}.${config.mine.shared.settings.domain_tld}";
|
# mine.shared.settings.domain = "${config.mine.shared.settings.domain_sld}.${config.mine.shared.settings.domain_tld}";
|
||||||
mine.shared.settings.domain = "fricloud.dk";
|
mine.shared.settings.domain = "fricloud.dk";
|
||||||
|
|
||||||
|
mine.shared.settings.brand_lower = "fricloud";
|
||||||
|
mine.shared.settings.brand = "Fricloud";
|
||||||
}
|
}
|
||||||
|
|
42
shared/pkgs/lldap-cli.nix
Normal file
42
shared/pkgs/lldap-cli.nix
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
stdenv
|
||||||
|
, lib
|
||||||
|
, fetchFromGitHub
|
||||||
|
, makeWrapper
|
||||||
|
, bash
|
||||||
|
, gnugrep
|
||||||
|
, gnused
|
||||||
|
, jq
|
||||||
|
, curl
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
pname = "lldap-cli";
|
||||||
|
version = "unstable-2024-08-31";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "Zepmann";
|
||||||
|
repo = "lldap-cli";
|
||||||
|
rev = "6eb61cef179696633cafe080a018cd085d3c3f64";
|
||||||
|
sha256 = "sha256-Jchj4vqlGWmjFtdMwZAnI4VyBh+/p6rgZrpA77xlSb4=";
|
||||||
|
};
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
bash
|
||||||
|
gnugrep
|
||||||
|
gnused
|
||||||
|
jq
|
||||||
|
curl
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
makeWrapper
|
||||||
|
];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp lldap-cli $out/bin/lldap-cli
|
||||||
|
wrapProgram $out/bin/lldap-cli \
|
||||||
|
--prefix PATH : ${lib.makeBinPath buildInputs}
|
||||||
|
'';
|
||||||
|
}
|
|
@ -57,8 +57,8 @@ in {
|
||||||
];
|
];
|
||||||
|
|
||||||
routes = [
|
routes = [
|
||||||
{routeConfig = {Destination = "172.31.1.1";};}
|
{ Destination = "172.31.1.1"; }
|
||||||
{routeConfig = {Destination = "fe80::1";};}
|
{ Destination = "fe80::1"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,16 +8,16 @@ in sources // {
|
||||||
src = sources.nixpkgs;
|
src = sources.nixpkgs;
|
||||||
name = "nixpkgs-patched";
|
name = "nixpkgs-patched";
|
||||||
patches = [
|
patches = [
|
||||||
# tmp teeworlds fetchpatch to inject secrets
|
# tmp - lldap: 0.5.1-unstable-2024-10-30 -> 0.6.1
|
||||||
(pkgs.fetchpatch {
|
(pkgs.fetchpatch {
|
||||||
url = "https://github.com/NixOS/nixpkgs/pull/334590.patch";
|
url = "https://github.com/NixOS/nixpkgs/pull/359835.patch";
|
||||||
sha256 = "sha256-5Uf/jLV0CJFbWyPmkpF4kEVISvoG+fujvTAFIR0a2ek=";
|
sha256 = "sha256-2C9l4v9MaUJyiaB+kslTsSjsqTZ7RlcfMNlRzZblMik=";
|
||||||
})
|
|
||||||
# stalwart-mail
|
|
||||||
(pkgs.fetchpatch {
|
|
||||||
url = "https://github.com/NixOS/nixpkgs/pull/333507.patch";
|
|
||||||
sha256 = "sha256-HAbfKQRnOjdK/rJ5wuePw4hEVQoFz9N0YujxBxROGo0=";
|
|
||||||
})
|
})
|
||||||
|
# tmp - stalwart-mail.webadmin: pin wasm-bindgen-cli version
|
||||||
|
# (pkgs.fetchpatch {
|
||||||
|
# url = "https://github.com/NixOS/nixpkgs/pull/353360.patch";
|
||||||
|
# sha256 = "sha256-WPNnvVmtySyEk58kVIYWVx3VN8MhX4v2ITLLnUGhpz4=";
|
||||||
|
# })
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "disko",
|
"repo": "disko",
|
||||||
"rev": "276a0d055a720691912c6a34abb724e395c8e38a",
|
"rev": "2814a5224a47ca19e858e027f7e8bff74a8ea9f1",
|
||||||
"sha256": "1bdf6p7p4rdzkchg67w5vhfg23qs3381cn4x7v16qnv6hqid0i8s",
|
"sha256": "1ayxw37arc92frzq0080w7kixdmqbq4jm8a19nrgivb70ra1mqys",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/nix-community/disko/archive/276a0d055a720691912c6a34abb724e395c8e38a.tar.gz",
|
"url": "https://github.com/nix-community/disko/archive/2814a5224a47ca19e858e027f7e8bff74a8ea9f1.tar.gz",
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
},
|
},
|
||||||
"impermanence": {
|
"impermanence": {
|
||||||
|
@ -29,28 +29,22 @@
|
||||||
"homepage": "",
|
"homepage": "",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "impermanence",
|
"repo": "impermanence",
|
||||||
"rev": "23c1f06316b67cb5dabdfe2973da3785cfe9c34a",
|
"rev": "3ed3f0eaae9fcc0a8331e77e9319c8a4abd8a71a",
|
||||||
"sha256": "1c99hc2mv0f5rjxj97wcypyrpi5i3xmpi3sd2fnw2481jxgqn5h3",
|
"sha256": "1k30ig9b5bx51f0y617yvcn61bgpahf8r0i55mnl3hy6nqjbfw07",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/nix-community/impermanence/archive/23c1f06316b67cb5dabdfe2973da3785cfe9c34a.tar.gz",
|
"url": "https://github.com/nix-community/impermanence/archive/3ed3f0eaae9fcc0a8331e77e9319c8a4abd8a71a.tar.gz",
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
},
|
},
|
||||||
"nixos-mailserver": {
|
|
||||||
"branch": "nixos-24.05",
|
|
||||||
"repo": "git@gitlab.com:simple-nixos-mailserver/nixos-mailserver.git",
|
|
||||||
"rev": "29916981e7b3b5782dc5085ad18490113f8ff63b",
|
|
||||||
"type": "git"
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"branch": "nixos-unstable",
|
"branch": "nixos-unstable",
|
||||||
"description": "Nix Packages collection",
|
"description": "Nix Packages collection",
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
|
"rev": "ac35b104800bff9028425fec3b6e8a41de2bbfff",
|
||||||
"sha256": "1ds3yjcy52l8d3rkxr3b7h9c0c3nly079bgakjaasnfjj3xprrwr",
|
"sha256": "1fbj7shlmviilmgz5z2gp59j6xwgdr01jfh75qhixx06kib4305p",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/c3aa7b8938b17aebd2deecf7be0636000d62a2b9.tar.gz",
|
"url": "https://github.com/NixOS/nixpkgs/archive/ac35b104800bff9028425fec3b6e8a41de2bbfff.tar.gz",
|
||||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue