Compare commits
21 Commits
c25e75a1b6
...
d3f549751f
| Author | SHA1 | Date | |
|---|---|---|---|
| d3f549751f | |||
| edb48ace8a | |||
| 215b546128 | |||
| 35e41fa630 | |||
| 210cf2dc9a | |||
| 809dd65eb0 | |||
| a8f730b4af | |||
| 023620de83 | |||
| eb2258a850 | |||
| 720d8bf154 | |||
| 6da8823f85 | |||
| 4d8fc16bbc | |||
| f2f9e95198 | |||
| 07c748071e | |||
| b489d719da | |||
| 9e042ebeaf | |||
| d4887fbd64 | |||
| 3642b2f41b | |||
| d9f7c63b8a | |||
| 0b5fd3ac41 | |||
| e9f5942202 |
@@ -1,6 +1,5 @@
|
||||
{ lib, config, pkgs, ... }:
|
||||
let
|
||||
colorVariant = " black";
|
||||
gtkThemeFromScheme = import ./gtk-theme-gen.nix { inherit pkgs config; };
|
||||
wallpaperGen = import ./wallpaper-gen.nix { inherit pkgs config; };
|
||||
in {
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
# suspend to RAM (deep) rather than `s2idle`
|
||||
boot.kernelParams = [ "mem_sleep_default=deep" ];
|
||||
# suspend-then-hibernate
|
||||
systemd.sleep.extraConfig = ''
|
||||
HibernateDelaySec=30m
|
||||
SuspendState=mem
|
||||
'';
|
||||
systemd.sleep.settings.Sleep = {
|
||||
HibernateDelaySec = "30m";
|
||||
SuspendState = "mem";
|
||||
};
|
||||
|
||||
services.logind.settings.Login.HandleLidSwitch = "suspend-then-hibernate";
|
||||
# Hibernate on power button pressed
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{ ... }: {
|
||||
{ pkgs, ... }: {
|
||||
systemd.services.systemd-udevd.restartIfChanged = false;
|
||||
|
||||
services.udev = {
|
||||
packages = [ ];
|
||||
packages = with pkgs; [ android-udev-rules ];
|
||||
extraRules = ''
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="2104", ATTRS{idProduct}=="0127", GROUP="plugdev", TAG+="uaccess"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="2104", ATTRS{idProduct}=="0118", GROUP="plugdev", TAG+="uaccess"
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
{ pkgs, config, lib, ... }: {
|
||||
|
||||
config = lib.mkIf (config.syscfg.make.develop) {
|
||||
programs.adb.enable = true;
|
||||
# services.udev.packages = [
|
||||
# pkgs.android-udev-rules
|
||||
# ];
|
||||
|
||||
programs.wireshark.enable = true;
|
||||
|
||||
environment.systemPackages = with pkgs; [ wget dconf wireshark ];
|
||||
environment.systemPackages = with pkgs; [ wget dconf wireshark mtr android-tools ];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
{ lib, config, pkgs, ... }:
|
||||
let
|
||||
ollamaPkg = pkgs.ollama-rocm;
|
||||
ollamaPkg = pkgs.ollama-vulkan;
|
||||
in{
|
||||
|
||||
config = lib.mkIf (config.syscfg.make.develop) {
|
||||
services.ollama = {
|
||||
enable = true;
|
||||
package = ollamaPkg;
|
||||
acceleration = "rocm";
|
||||
loadModels = [ "deepseek-v2:lite" "qwen2.5-coder:7b" "qwen2.5-coder:1.5b" ];
|
||||
loadModels = [ ];
|
||||
syncModels = true;
|
||||
};
|
||||
environment.systemPackages = with pkgs; [ ollamaPkg ];
|
||||
|
||||
@@ -2,11 +2,23 @@
|
||||
let
|
||||
version = "2026.2.2";
|
||||
serverCfg = config.syscfg.server;
|
||||
mediaCfg = config.syscfg.media;
|
||||
authentikBackground = if mediaCfg.banner.png != null then mediaCfg.banner.png else mediaCfg.bg;
|
||||
logoSvgFileName = builtins.baseNameOf (toString mediaCfg.logo.svg);
|
||||
logoIcoFileName = builtins.baseNameOf (toString mediaCfg.logo.ico);
|
||||
backgroundFileName = builtins.baseNameOf (toString authentikBackground);
|
||||
logoSvgMount = "/media/custom/${logoSvgFileName}";
|
||||
logoIcoMount = "/media/custom/${logoIcoFileName}";
|
||||
backgroundMount = "/media/custom/${backgroundFileName}";
|
||||
authentikData = builder.mkData {
|
||||
name = "authentik"; dir = "authentik"; vars = {
|
||||
AUTHENTIK_DOMAIN = "${containerCfg.subdomain}.${serverCfg.domain}";
|
||||
COOKIE_DOMAIN = "${serverCfg.domain}";
|
||||
AUTHENTIK_LDAP_DC_DOMAIN = "dc=ldap," + (lib.concatMapStringsSep "," (x: "dc=${x}") (lib.splitString "." serverCfg.domain));
|
||||
AUTHENTIK_BRANDING_TITLE = if containerCfg.extra ? name then containerCfg.extra.name else "authentik";
|
||||
AUTHENTIK_BRANDING_LOGO = "custom/${logoSvgFileName}";
|
||||
AUTHENTIK_BRANDING_FAVICON = "custom/${logoIcoFileName}";
|
||||
AUTHENTIK_BRANDING_BACKGROUND = "custom/${backgroundFileName}";
|
||||
}
|
||||
// (if serverCfg.containers?jellyfin then { JELLYFIN_DOMAIN = "${serverCfg.containers.jellyfin.subdomain}.${serverCfg.domain}";} else {})
|
||||
// (if serverCfg.containers?gitea then { GITEA_DOMAIN = "${serverCfg.containers.gitea.subdomain}.${serverCfg.domain}";} else {})
|
||||
@@ -60,6 +72,9 @@ in {
|
||||
"${serverCfg.path.config}/authentik/media:/media"
|
||||
"${serverCfg.path.config}/authentik/templates:/templates"
|
||||
"${authentikData}:/blueprints/custom:ro"
|
||||
"${mediaCfg.logo.svg}:${logoSvgMount}:ro"
|
||||
"${mediaCfg.logo.ico}:${logoIcoMount}:ro"
|
||||
"${authentikBackground}:${backgroundMount}:ro"
|
||||
];
|
||||
};
|
||||
};
|
||||
@@ -82,6 +97,9 @@ in {
|
||||
"${serverCfg.path.config}/authentik/media:/media"
|
||||
"${serverCfg.path.config}/authentik/templates:/templates"
|
||||
"${authentikData}:/blueprints/custom:ro"
|
||||
"${mediaCfg.logo.svg}:${logoSvgMount}:ro"
|
||||
"${mediaCfg.logo.ico}:${logoIcoMount}:ro"
|
||||
"${authentikBackground}:${backgroundMount}:ro"
|
||||
];
|
||||
};
|
||||
};
|
||||
@@ -103,6 +121,7 @@ in {
|
||||
AK="${pkgs.podman}/bin/podman --events-backend=none exec --env-file ${config.sops.secrets."CUSTOM".path} -e DOMAIN=${serverCfg.domain} -u root authentik-worker ak"
|
||||
|
||||
$AK apply_blueprint /blueprints/custom/authentik.yaml
|
||||
$AK apply_blueprint /blueprints/custom/branding.yaml
|
||||
$AK apply_blueprint /blueprints/custom/traefik.yaml
|
||||
$AK apply_blueprint /blueprints/custom/ldap.yaml
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
{ config, containerCfg, pkgs, lib, builder, name, ... }:
|
||||
let
|
||||
serverCfg = config.syscfg.server;
|
||||
mediaCfg = config.syscfg.media;
|
||||
port = 8080;
|
||||
cacheControl = containerCfg.extra.cacheControl or "public, max-age=86400";
|
||||
priority = toString (containerCfg.extra.priority or 1000);
|
||||
logoSvgFileName = builtins.baseNameOf (toString mediaCfg.logo.svg);
|
||||
logoSvgMount = "/assets/${logoSvgFileName}";
|
||||
configFile = pkgs.writeText "favicon-config.json" (builtins.toJSON {
|
||||
inherit cacheControl;
|
||||
mappings = containerCfg.extra.mappings or {};
|
||||
default = containerCfg.extra.default or null;
|
||||
logoScale = containerCfg.extra.logoScale or 0.72;
|
||||
});
|
||||
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||
cairosvg
|
||||
pillow
|
||||
]);
|
||||
serverScript = pkgs.writeText "favicon-server.py" ''
|
||||
import base64
|
||||
import hashlib
|
||||
from io import BytesIO
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import cairosvg
|
||||
from PIL import Image
|
||||
|
||||
|
||||
CONFIG_PATH = os.environ.get("FAVICON_CONFIG", "/config/config.json")
|
||||
LOGO_PATH = os.environ.get("FAVICON_LOGO", "/assets/logo.svg")
|
||||
CACHE_DIR = Path(os.environ.get("FAVICON_CACHE_DIR", "/cache"))
|
||||
LISTEN_HOST = os.environ.get("FAVICON_LISTEN_HOST", "0.0.0.0")
|
||||
LISTEN_PORT = int(os.environ.get("FAVICON_PORT", "8080"))
|
||||
DEFAULT_CACHE_CONTROL = "public, max-age=86400"
|
||||
PATH_PATTERN = re.compile(
|
||||
r"(?i)(?:^|/)(?:"
|
||||
r"favicon(?:-[0-9]+x[0-9]+)?\.(?:ico|png|svg)"
|
||||
r"|apple-touch-icon(?:-precomposed)?\.png"
|
||||
r"|android-chrome-(?:192x192|512x512)\.png"
|
||||
r"|mstile-150x150\.png"
|
||||
r")$"
|
||||
)
|
||||
|
||||
|
||||
with open(CONFIG_PATH, "r", encoding="utf-8") as fh:
|
||||
APP_CONFIG = json.load(fh)
|
||||
with open(LOGO_PATH, "rb") as fh:
|
||||
LOGO_BYTES = fh.read()
|
||||
LOGO_DATA_URI = "data:image/svg+xml;base64," + base64.b64encode(LOGO_BYTES).decode("ascii")
|
||||
|
||||
|
||||
def _normalize_host(host):
|
||||
return (host or "").split(":", 1)[0].strip().lower()
|
||||
|
||||
|
||||
def _pick_profile(host):
|
||||
mappings = APP_CONFIG.get("mappings", {})
|
||||
exact = mappings.get(host)
|
||||
if exact is not None:
|
||||
return exact
|
||||
|
||||
wildcard_matches = []
|
||||
for pattern, profile in mappings.items():
|
||||
if pattern.startswith("*.") and host.endswith(pattern[1:].lower()):
|
||||
wildcard_matches.append((pattern.count("."), profile))
|
||||
|
||||
if wildcard_matches:
|
||||
wildcard_matches.sort(key=lambda item: item[0], reverse=True)
|
||||
return wildcard_matches[0][1]
|
||||
|
||||
return APP_CONFIG.get("default")
|
||||
|
||||
|
||||
def _color(value, fallback):
|
||||
return value if isinstance(value, str) and value else fallback
|
||||
|
||||
|
||||
def _badge_text(profile):
|
||||
badge = profile.get("icon") or profile.get("label") or profile.get("text") or ""
|
||||
badge = str(badge).strip()
|
||||
if not badge:
|
||||
return ""
|
||||
return badge[:3].upper()
|
||||
|
||||
|
||||
def _logo_scale():
|
||||
scale = APP_CONFIG.get("logoScale", 0.72)
|
||||
try:
|
||||
scale = float(scale)
|
||||
except (TypeError, ValueError):
|
||||
return 0.72
|
||||
return max(0.2, min(0.9, scale))
|
||||
|
||||
|
||||
def _render_svg(profile):
|
||||
bg = _color(profile.get("background"), "#111827")
|
||||
fg = _color(profile.get("foreground"), "#f8fafc")
|
||||
accent = _color(profile.get("accent"), "#38bdf8")
|
||||
badge = _badge_text(profile)
|
||||
|
||||
canvas = 256
|
||||
panel = 188
|
||||
panel_offset = (canvas - panel) // 2
|
||||
logo_size = int(canvas * _logo_scale())
|
||||
logo_offset = (canvas - logo_size) // 2
|
||||
badge_svg = ""
|
||||
if badge:
|
||||
badge_svg = f"""
|
||||
<g>
|
||||
<circle cx="196" cy="60" r="34" fill="{accent}" />
|
||||
<text x="196" y="72"
|
||||
font-family="DejaVu Sans, sans-serif"
|
||||
font-size="34"
|
||||
font-weight="700"
|
||||
text-anchor="middle"
|
||||
fill="{fg}">{badge}</text>
|
||||
</g>
|
||||
"""
|
||||
|
||||
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{canvas}" height="{canvas}" viewBox="0 0 {canvas} {canvas}">
|
||||
<defs>
|
||||
<radialGradient id="glow" cx="84%" cy="16%" r="75%">
|
||||
<stop offset="0%" stop-color="{accent}" stop-opacity="0.34" />
|
||||
<stop offset="100%" stop-color="{accent}" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width="{canvas}" height="{canvas}" rx="56" fill="{bg}" />
|
||||
<rect width="{canvas}" height="{canvas}" rx="56" fill="url(#glow)" />
|
||||
<rect x="{panel_offset}" y="{panel_offset}" width="{panel}" height="{panel}" rx="44" fill="#ffffff" fill-opacity="0.08" stroke="{accent}" stroke-opacity="0.6" stroke-width="6" />
|
||||
<rect x="42" y="214" width="172" height="10" rx="5" fill="{accent}" fill-opacity="0.9" />
|
||||
<image href="{LOGO_DATA_URI}" x="{logo_offset}" y="{logo_offset}" width="{logo_size}" height="{logo_size}" preserveAspectRatio="xMidYMid meet" />
|
||||
{badge_svg}
|
||||
</svg>"""
|
||||
|
||||
|
||||
def _target_spec(path):
|
||||
name = Path(path).name.lower()
|
||||
if name.endswith(".svg"):
|
||||
return ("svg", None)
|
||||
if name.endswith(".ico"):
|
||||
return ("ico", 64)
|
||||
|
||||
known_sizes = {
|
||||
"apple-touch-icon.png": 180,
|
||||
"apple-touch-icon-precomposed.png": 180,
|
||||
"android-chrome-192x192.png": 192,
|
||||
"android-chrome-512x512.png": 512,
|
||||
"mstile-150x150.png": 150,
|
||||
"favicon.png": 256,
|
||||
}
|
||||
if name in known_sizes:
|
||||
return ("png", known_sizes[name])
|
||||
|
||||
match = re.search(r"([0-9]{2,4})x([0-9]{2,4})", name)
|
||||
if match:
|
||||
return ("png", int(match.group(1)))
|
||||
return ("png", 256)
|
||||
|
||||
|
||||
def _cache_key(host, spec, profile):
|
||||
payload = json.dumps(
|
||||
{"host": host, "spec": spec, "profile": profile, "logo": hashlib.sha256(LOGO_BYTES).hexdigest()},
|
||||
sort_keys=True,
|
||||
).encode("utf-8")
|
||||
return hashlib.sha256(payload).hexdigest()
|
||||
|
||||
|
||||
def _mime(ext):
|
||||
return {
|
||||
"svg": "image/svg+xml",
|
||||
"png": "image/png",
|
||||
"ico": "image/x-icon",
|
||||
}[ext]
|
||||
|
||||
|
||||
def _generate_asset(host, profile, path):
|
||||
spec = _target_spec(path)
|
||||
cache_name = f"{_cache_key(host, spec, profile)}.{spec[0]}"
|
||||
target = CACHE_DIR / cache_name
|
||||
if target.exists():
|
||||
return target, _mime(spec[0])
|
||||
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
svg = _render_svg(profile).encode("utf-8")
|
||||
if spec[0] == "svg":
|
||||
target.write_bytes(svg)
|
||||
return target, _mime("svg")
|
||||
|
||||
png_bytes = cairosvg.svg2png(bytestring=svg, output_width=spec[1], output_height=spec[1])
|
||||
if spec[0] == "png":
|
||||
target.write_bytes(png_bytes)
|
||||
return target, _mime("png")
|
||||
|
||||
image = Image.open(BytesIO(png_bytes))
|
||||
image.save(target, format="ICO", sizes=[(64, 64), (48, 48), (32, 32), (16, 16)])
|
||||
image.close()
|
||||
return target, _mime("ico")
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
server_version = "favicon-router/1.0"
|
||||
|
||||
def _serve(self, include_body):
|
||||
path = urlsplit(self.path).path
|
||||
if not PATH_PATTERN.search(path):
|
||||
self.send_error(404)
|
||||
return
|
||||
|
||||
host = _normalize_host(self.headers.get("Host", ""))
|
||||
profile = _pick_profile(host)
|
||||
if not profile:
|
||||
self.send_error(404, "No favicon mapping for host")
|
||||
return
|
||||
|
||||
asset_path, content_type = _generate_asset(host, profile, path)
|
||||
payload = asset_path.read_bytes()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", content_type)
|
||||
self.send_header("Content-Length", str(len(payload)))
|
||||
self.send_header("Cache-Control", APP_CONFIG.get("cacheControl", DEFAULT_CACHE_CONTROL))
|
||||
self.end_headers()
|
||||
if include_body:
|
||||
self.wfile.write(payload)
|
||||
|
||||
def do_GET(self):
|
||||
self._serve(include_body=True)
|
||||
|
||||
def do_HEAD(self):
|
||||
self._serve(include_body=False)
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
print("%s - - [%s] %s" % (self.address_string(), self.log_date_time_string(), fmt % args))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
httpd = ThreadingHTTPServer((LISTEN_HOST, LISTEN_PORT), Handler)
|
||||
httpd.serve_forever()
|
||||
'';
|
||||
image = pkgs.dockerTools.streamLayeredImage {
|
||||
name = "favicon";
|
||||
tag = "1";
|
||||
contents = [
|
||||
pythonEnv
|
||||
pkgs.fontconfig
|
||||
pkgs.dejavu_fonts
|
||||
pkgs.cacert
|
||||
pkgs.tzdata
|
||||
];
|
||||
config = {
|
||||
Entrypoint = [ "${pythonEnv}/bin/python3" serverScript ];
|
||||
ExposedPorts = { "${toString port}/tcp" = { }; };
|
||||
WorkingDir = "/";
|
||||
};
|
||||
};
|
||||
in {
|
||||
runtime = {
|
||||
paths = [
|
||||
{
|
||||
path = "${serverCfg.path.config}/favicon";
|
||||
mode = "0755";
|
||||
dirs = [ "cache" ];
|
||||
}
|
||||
];
|
||||
|
||||
containers = {
|
||||
server = builder.mkContainer {
|
||||
imageStream = image;
|
||||
port = port;
|
||||
extraEnv = {
|
||||
FAVICON_CONFIG = "/config/config.json";
|
||||
FAVICON_LOGO = logoSvgMount;
|
||||
FAVICON_CACHE_DIR = "/cache";
|
||||
FAVICON_PORT = toString port;
|
||||
FONTCONFIG_FILE = "${pkgs.fontconfig.out}/etc/fonts/fonts.conf";
|
||||
FONTCONFIG_PATH = "${pkgs.fontconfig.out}/etc/fonts";
|
||||
};
|
||||
extraLabels = {
|
||||
"traefik.enable" = "true";
|
||||
"traefik.http.routers.${name}.entrypoints" = "web-secure";
|
||||
"traefik.http.routers.${name}.rule" = "HostRegexp(`{host:.+}`) && PathRegexp(`^/(?:.*\\/)?(?:favicon(?:-[0-9]+x[0-9]+)?\\.(?:ico|png|svg)|apple-touch-icon(?:-precomposed)?\\.png|android-chrome-(?:192x192|512x512)\\.png|mstile-150x150\\.png)$`)";
|
||||
"traefik.http.routers.${name}.priority" = priority;
|
||||
"traefik.http.routers.${name}.tls" = "true";
|
||||
"traefik.http.services.${name}.loadbalancer.server.port" = toString port;
|
||||
};
|
||||
overrides = {
|
||||
volumes = [
|
||||
"${configFile}:/config/config.json:ro"
|
||||
"${serverCfg.path.config}/favicon/cache:/cache"
|
||||
"${mediaCfg.logo.svg}:${logoSvgMount}:ro"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -2,13 +2,17 @@
|
||||
let
|
||||
version = "latest";
|
||||
serverCfg = config.syscfg.server;
|
||||
mediaCfg = config.syscfg.media;
|
||||
backgroundImage = if mediaCfg.banner.png != null then mediaCfg.banner.png else mediaCfg.bg;
|
||||
backgroundFileName = builtins.baseNameOf (toString backgroundImage);
|
||||
backgroundMount = "/app/public/media/${backgroundFileName}";
|
||||
|
||||
settings = pkgs.writers.writeYAML "settings.yaml" {
|
||||
title = "My Self-Hosted Dashboard";
|
||||
description = "";
|
||||
startUrl = "https://${containerCfg.subdomain}.${serverCfg.domain}";
|
||||
background = {
|
||||
# image = "...";
|
||||
image = "/media/${backgroundFileName}";
|
||||
blur = "sm"; #sm, md, xl,...
|
||||
# saturate = "";
|
||||
# brightness = "";
|
||||
@@ -278,6 +282,7 @@ in {
|
||||
"${services}:/app/config/services.yaml:ro"
|
||||
"${widgets}:/app/config/widgets.yaml:ro"
|
||||
"${bookmarks}:/app/config/bookmarks.yaml:ro"
|
||||
"${backgroundImage}:${backgroundMount}:ro"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
{ config, containerCfg, pkgs, lib, builder, name,... }:
|
||||
let
|
||||
version = "31";
|
||||
serverCfg = config.syscfg.server;
|
||||
version = "31";
|
||||
serverCfg = config.syscfg.server;
|
||||
mediaCfg = config.syscfg.media;
|
||||
backgroundImage = if mediaCfg.banner.png != null then mediaCfg.banner.png else mediaCfg.bg;
|
||||
backgroundFileName = builtins.baseNameOf (toString backgroundImage);
|
||||
logoPngFileName = builtins.baseNameOf (toString mediaCfg.logo.png);
|
||||
logoSvgFileName = builtins.baseNameOf (toString mediaCfg.logo.svg);
|
||||
logoIcoFileName = builtins.baseNameOf (toString mediaCfg.logo.ico);
|
||||
logoPngMount = "/var/www/html/theming/${logoPngFileName}";
|
||||
logoSvgMount = "/var/www/html/theming/${logoSvgFileName}";
|
||||
logoIcoMount = "/var/www/html/theming/${logoIcoFileName}";
|
||||
backgroundMount = "/var/www/html/theming/${backgroundFileName}";
|
||||
in {
|
||||
requires = {
|
||||
secrets = [ name ];
|
||||
@@ -50,6 +60,10 @@ in {
|
||||
volumes = [
|
||||
"${serverCfg.path.config}/nextcloud:/var/www/html"
|
||||
"${serverCfg.path.cloud}:/var/www/html/data"
|
||||
"${mediaCfg.logo.png}:${logoPngMount}:ro"
|
||||
"${mediaCfg.logo.svg}:${logoSvgMount}:ro"
|
||||
"${mediaCfg.logo.ico}:${logoIcoMount}:ro"
|
||||
"${backgroundImage}:${backgroundMount}:ro"
|
||||
];
|
||||
};
|
||||
};
|
||||
@@ -168,21 +182,21 @@ in {
|
||||
'') containerCfg.extra.federatedServers or [])}
|
||||
$OCC config:app:set systemtags allow_user_creating --value="no"
|
||||
|
||||
echo "Applying Theme..."
|
||||
$OCC config:app:set theming url --value="https://${containerCfg.subdomain}.${serverCfg.domain}"
|
||||
${lib.optionalString (containerCfg.extra ? name) ''$OCC config:app:set theming name --value="${containerCfg.extra.name}"''}
|
||||
${lib.optionalString (containerCfg.extra ? slogan) ''$OCC config:app:set theming slogan --value="${containerCfg.extra.slogan}"''}
|
||||
$OCC config:app:set theming background_color --value="${serverCfg.colorScheme.palette.base02}"
|
||||
$OCC config:app:set theming primary_color --value="${serverCfg.colorScheme.palette.base0C}"
|
||||
|
||||
#$OCC theming:config logo {serverCfg.colorScheme.logo}
|
||||
#$OCC theming:config logoheader {serverCfg.colorScheme.logo}
|
||||
#$OCC theming:config background {serverCfg.colorScheme.bg}
|
||||
|
||||
else
|
||||
echo "Nextcloud is already installed. Skipping setup."
|
||||
fi
|
||||
|
||||
echo "Applying Theme..."
|
||||
$OCC config:app:set theming url --value="https://${containerCfg.subdomain}.${serverCfg.domain}"
|
||||
${lib.optionalString (containerCfg.extra ? name) ''$OCC config:app:set theming name --value="${containerCfg.extra.name}"''}
|
||||
${lib.optionalString (containerCfg.extra ? slogan) ''$OCC config:app:set theming slogan --value="${containerCfg.extra.slogan}"''}
|
||||
$OCC config:app:set theming background_color --value="${serverCfg.colorScheme.palette.base02}"
|
||||
$OCC config:app:set theming primary_color --value="${serverCfg.colorScheme.palette.base0C}"
|
||||
$OCC theming:config logo "${logoPngMount}"
|
||||
$OCC theming:config logoheader "${logoSvgMount}"
|
||||
$OCC theming:config favicon "${logoIcoMount}"
|
||||
$OCC theming:config background "${backgroundMount}"
|
||||
|
||||
$OCC config:app:set serverinfo token --value="$HOMEPAGE_VAR_NEXTCLOUD_API"
|
||||
|
||||
echo "Maintenance..."
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
version: 1
|
||||
metadata:
|
||||
name: "Branding Setup"
|
||||
entries:
|
||||
- model: authentik_brands.brand
|
||||
identifiers:
|
||||
domain: "@AUTHENTIK_DOMAIN@"
|
||||
attrs:
|
||||
domain: "@AUTHENTIK_DOMAIN@"
|
||||
branding_title: "@AUTHENTIK_BRANDING_TITLE@"
|
||||
branding_logo: "@AUTHENTIK_BRANDING_LOGO@"
|
||||
branding_favicon: "@AUTHENTIK_BRANDING_FAVICON@"
|
||||
branding_default_flow_background: "@AUTHENTIK_BRANDING_BACKGROUND@"
|
||||
@@ -81,11 +81,13 @@ in {
|
||||
}) allVMConfigs
|
||||
// mkNamedUnits (e: {
|
||||
name = "${e.name}-setup";
|
||||
value = {
|
||||
value = {
|
||||
description = "Run ${e.name} setup";
|
||||
after = [ "podman-${e.name}-${e.trigger}.service" ];
|
||||
wants = [ "podman-${e.name}-${e.trigger}.service" ];
|
||||
partOf = [ "podman-${e.name}-${e.trigger}.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ e.script ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
TimeoutStartSec = "360s";
|
||||
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 959 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
@@ -28,6 +28,10 @@ in with lib; {
|
||||
type = types.oneOf [ types.bool (types.submodule { options = import ./server.nix {inherit lib;}; }) ];
|
||||
default = false;
|
||||
};
|
||||
media = mkOption {
|
||||
type = types.submodule { options = import ./media.nix { inherit lib; }; };
|
||||
default = {};
|
||||
};
|
||||
extra = import ./extra.nix {inherit lib;};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
{ lib, ... }:
|
||||
let
|
||||
mediaDir = ../media;
|
||||
mainDir = mediaDir + "/main";
|
||||
maybePath = rel:
|
||||
let path = mediaDir + "/${rel}";
|
||||
in if builtins.pathExists path then path else null;
|
||||
mainImages =
|
||||
let
|
||||
names = builtins.attrNames (builtins.readDir mainDir);
|
||||
isImage = name: builtins.match ".*\\.(png|jpg|jpeg|webp|svg)$" name != null;
|
||||
in
|
||||
map (name: mainDir + "/${name}") (lib.sort builtins.lessThan (lib.filter isImage names));
|
||||
in
|
||||
with lib; {
|
||||
logo = {
|
||||
ico = mkOption {
|
||||
type = types.path;
|
||||
default = mediaDir + "/logo.ico";
|
||||
};
|
||||
svg = mkOption {
|
||||
type = types.path;
|
||||
default = mediaDir + "/logo.svg";
|
||||
};
|
||||
png = mkOption {
|
||||
type = types.path;
|
||||
default = mediaDir + "/logo.png";
|
||||
};
|
||||
};
|
||||
|
||||
banner = {
|
||||
svg = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = maybePath "banner.svg";
|
||||
};
|
||||
png = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = maybePath "banner.png";
|
||||
};
|
||||
};
|
||||
|
||||
main = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = if mainImages != [ ] then mainImages else lib.optional (maybePath "main/bg.png" != null) (mediaDir + "/main/bg.png");
|
||||
};
|
||||
|
||||
bg = mkOption {
|
||||
type = types.path;
|
||||
default = let bg = maybePath "main/bg.png"; in if bg != null then bg else builtins.head mainImages;
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,7 @@ let
|
||||
in forEachSystem (system:
|
||||
let
|
||||
overlays = import ../overlays { inherit inputs pkgs; };
|
||||
overrides = { custom = import ../pkgs { inherit pkgs; }; };
|
||||
overrides = { custom = import ../packages { inherit pkgs; }; };
|
||||
pkgs = import inputs.nixpkgs { inherit system overlays; } // overrides;
|
||||
in {
|
||||
default = import ./devsh { inherit pkgs; };
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{ ... }:{
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
|
||||
|
||||
AUTHENTIK_DB_PASSWORD=NTQRO0rhPCd4L3HLNK4AT09Npz+ks1jyRC6AOyo5u+k=
|
||||
AUTHENTIK_SECRET_KEY=9Zw8Sy8257iJmRdBhUKGiq3d7uYAkhC9smuDUClE8aR1iPdpHHds+K2D1Zy3lwj2Hjnasu5jnopkhwnABWDu8A==
|
||||
|
||||
|
||||
AUTHENTIK_EMAIL_PASSWORD=w+g:cPU+e.<q,f<mj3DFPxXxo4h2SVS9.;,T<!Sra>y!mNcAsiAp4jPCLTmjte2d
|
||||
|
||||
|
||||
ETHERPAD_DB_PASSWORD=d43352c3906516bf4c34d63316509cb4b1621167af84c81b60689779a62b2348
|
||||
ETHERPAD_ADMIN_PASSWORD=Hackme55#
|
||||
|
||||
COLLABORA_USER=...
|
||||
COLLABORA_PASSWORD=...
|
||||
@@ -61,6 +61,41 @@
|
||||
suwayomi.subdomain = "manga";
|
||||
calibre.subdomain = "books";
|
||||
selfmark = { subdomain = "arr"; subpath = "selfmark"; };
|
||||
|
||||
favicon.extra = {
|
||||
mappings = {
|
||||
"home" = {
|
||||
label = "H";
|
||||
background = "#0f172a";
|
||||
foreground = "#f8fafc";
|
||||
accent = "#22c55e";
|
||||
};
|
||||
"cloud" = {
|
||||
label = "C";
|
||||
background = "#0b1220";
|
||||
foreground = "#f8fafc";
|
||||
accent = "#38bdf8";
|
||||
};
|
||||
"sso" = {
|
||||
label = "S";
|
||||
background = "#1f1630";
|
||||
foreground = "#fff7ed";
|
||||
accent = "#f59e0b";
|
||||
};
|
||||
"metrum" = {
|
||||
label = "M";
|
||||
background = "#1a1d29";
|
||||
foreground = "#f8fafc";
|
||||
accent = "#a78bfa";
|
||||
};
|
||||
};
|
||||
default = {
|
||||
label = "?";
|
||||
background = "#111827";
|
||||
foreground = "#f9fafb";
|
||||
accent = "#6b7280";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||