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, ... }:
|
{ lib, config, pkgs, ... }:
|
||||||
let
|
let
|
||||||
colorVariant = " black";
|
|
||||||
gtkThemeFromScheme = import ./gtk-theme-gen.nix { inherit pkgs config; };
|
gtkThemeFromScheme = import ./gtk-theme-gen.nix { inherit pkgs config; };
|
||||||
wallpaperGen = import ./wallpaper-gen.nix { inherit pkgs config; };
|
wallpaperGen = import ./wallpaper-gen.nix { inherit pkgs config; };
|
||||||
in {
|
in {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
# suspend to RAM (deep) rather than `s2idle`
|
# suspend to RAM (deep) rather than `s2idle`
|
||||||
boot.kernelParams = [ "mem_sleep_default=deep" ];
|
boot.kernelParams = [ "mem_sleep_default=deep" ];
|
||||||
# suspend-then-hibernate
|
# suspend-then-hibernate
|
||||||
systemd.sleep.extraConfig = ''
|
systemd.sleep.settings.Sleep = {
|
||||||
HibernateDelaySec=30m
|
HibernateDelaySec = "30m";
|
||||||
SuspendState=mem
|
SuspendState = "mem";
|
||||||
'';
|
};
|
||||||
|
|
||||||
services.logind.settings.Login.HandleLidSwitch = "suspend-then-hibernate";
|
services.logind.settings.Login.HandleLidSwitch = "suspend-then-hibernate";
|
||||||
# Hibernate on power button pressed
|
# Hibernate on power button pressed
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{ ... }: {
|
{ pkgs, ... }: {
|
||||||
systemd.services.systemd-udevd.restartIfChanged = false;
|
systemd.services.systemd-udevd.restartIfChanged = false;
|
||||||
|
|
||||||
services.udev = {
|
services.udev = {
|
||||||
packages = [ ];
|
packages = with pkgs; [ android-udev-rules ];
|
||||||
extraRules = ''
|
extraRules = ''
|
||||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="2104", ATTRS{idProduct}=="0127", GROUP="plugdev", TAG+="uaccess"
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="2104", ATTRS{idProduct}=="0127", GROUP="plugdev", TAG+="uaccess"
|
||||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="2104", ATTRS{idProduct}=="0118", GROUP="plugdev", TAG+="uaccess"
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="2104", ATTRS{idProduct}=="0118", GROUP="plugdev", TAG+="uaccess"
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
{ pkgs, config, lib, ... }: {
|
{ pkgs, config, lib, ... }: {
|
||||||
|
|
||||||
config = lib.mkIf (config.syscfg.make.develop) {
|
config = lib.mkIf (config.syscfg.make.develop) {
|
||||||
programs.adb.enable = true;
|
|
||||||
# services.udev.packages = [
|
|
||||||
# pkgs.android-udev-rules
|
|
||||||
# ];
|
|
||||||
programs.wireshark.enable = true;
|
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, ... }:
|
{ lib, config, pkgs, ... }:
|
||||||
let
|
let
|
||||||
ollamaPkg = pkgs.ollama-rocm;
|
ollamaPkg = pkgs.ollama-vulkan;
|
||||||
in{
|
in{
|
||||||
|
|
||||||
config = lib.mkIf (config.syscfg.make.develop) {
|
config = lib.mkIf (config.syscfg.make.develop) {
|
||||||
services.ollama = {
|
services.ollama = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = ollamaPkg;
|
package = ollamaPkg;
|
||||||
acceleration = "rocm";
|
loadModels = [ ];
|
||||||
loadModels = [ "deepseek-v2:lite" "qwen2.5-coder:7b" "qwen2.5-coder:1.5b" ];
|
|
||||||
syncModels = true;
|
syncModels = true;
|
||||||
};
|
};
|
||||||
environment.systemPackages = with pkgs; [ ollamaPkg ];
|
environment.systemPackages = with pkgs; [ ollamaPkg ];
|
||||||
|
|||||||
@@ -2,11 +2,23 @@
|
|||||||
let
|
let
|
||||||
version = "2026.2.2";
|
version = "2026.2.2";
|
||||||
serverCfg = config.syscfg.server;
|
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 {
|
authentikData = builder.mkData {
|
||||||
name = "authentik"; dir = "authentik"; vars = {
|
name = "authentik"; dir = "authentik"; vars = {
|
||||||
AUTHENTIK_DOMAIN = "${containerCfg.subdomain}.${serverCfg.domain}";
|
AUTHENTIK_DOMAIN = "${containerCfg.subdomain}.${serverCfg.domain}";
|
||||||
COOKIE_DOMAIN = "${serverCfg.domain}";
|
COOKIE_DOMAIN = "${serverCfg.domain}";
|
||||||
AUTHENTIK_LDAP_DC_DOMAIN = "dc=ldap," + (lib.concatMapStringsSep "," (x: "dc=${x}") (lib.splitString "." 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?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 {})
|
// (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/media:/media"
|
||||||
"${serverCfg.path.config}/authentik/templates:/templates"
|
"${serverCfg.path.config}/authentik/templates:/templates"
|
||||||
"${authentikData}:/blueprints/custom:ro"
|
"${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/media:/media"
|
||||||
"${serverCfg.path.config}/authentik/templates:/templates"
|
"${serverCfg.path.config}/authentik/templates:/templates"
|
||||||
"${authentikData}:/blueprints/custom:ro"
|
"${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="${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/authentik.yaml
|
||||||
|
$AK apply_blueprint /blueprints/custom/branding.yaml
|
||||||
$AK apply_blueprint /blueprints/custom/traefik.yaml
|
$AK apply_blueprint /blueprints/custom/traefik.yaml
|
||||||
$AK apply_blueprint /blueprints/custom/ldap.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
|
let
|
||||||
version = "latest";
|
version = "latest";
|
||||||
serverCfg = config.syscfg.server;
|
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" {
|
settings = pkgs.writers.writeYAML "settings.yaml" {
|
||||||
title = "My Self-Hosted Dashboard";
|
title = "My Self-Hosted Dashboard";
|
||||||
description = "";
|
description = "";
|
||||||
startUrl = "https://${containerCfg.subdomain}.${serverCfg.domain}";
|
startUrl = "https://${containerCfg.subdomain}.${serverCfg.domain}";
|
||||||
background = {
|
background = {
|
||||||
# image = "...";
|
image = "/media/${backgroundFileName}";
|
||||||
blur = "sm"; #sm, md, xl,...
|
blur = "sm"; #sm, md, xl,...
|
||||||
# saturate = "";
|
# saturate = "";
|
||||||
# brightness = "";
|
# brightness = "";
|
||||||
@@ -278,6 +282,7 @@ in {
|
|||||||
"${services}:/app/config/services.yaml:ro"
|
"${services}:/app/config/services.yaml:ro"
|
||||||
"${widgets}:/app/config/widgets.yaml:ro"
|
"${widgets}:/app/config/widgets.yaml:ro"
|
||||||
"${bookmarks}:/app/config/bookmarks.yaml:ro"
|
"${bookmarks}:/app/config/bookmarks.yaml:ro"
|
||||||
|
"${backgroundImage}:${backgroundMount}:ro"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
let
|
let
|
||||||
version = "31";
|
version = "31";
|
||||||
serverCfg = config.syscfg.server;
|
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 {
|
in {
|
||||||
requires = {
|
requires = {
|
||||||
secrets = [ name ];
|
secrets = [ name ];
|
||||||
@@ -50,6 +60,10 @@ in {
|
|||||||
volumes = [
|
volumes = [
|
||||||
"${serverCfg.path.config}/nextcloud:/var/www/html"
|
"${serverCfg.path.config}/nextcloud:/var/www/html"
|
||||||
"${serverCfg.path.cloud}:/var/www/html/data"
|
"${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,20 +182,20 @@ in {
|
|||||||
'') containerCfg.extra.federatedServers or [])}
|
'') containerCfg.extra.federatedServers or [])}
|
||||||
$OCC config:app:set systemtags allow_user_creating --value="no"
|
$OCC config:app:set systemtags allow_user_creating --value="no"
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "Nextcloud is already installed. Skipping setup."
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Applying Theme..."
|
echo "Applying Theme..."
|
||||||
$OCC config:app:set theming url --value="https://${containerCfg.subdomain}.${serverCfg.domain}"
|
$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 ? 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}"''}
|
${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 background_color --value="${serverCfg.colorScheme.palette.base02}"
|
||||||
$OCC config:app:set theming primary_color --value="${serverCfg.colorScheme.palette.base0C}"
|
$OCC config:app:set theming primary_color --value="${serverCfg.colorScheme.palette.base0C}"
|
||||||
|
$OCC theming:config logo "${logoPngMount}"
|
||||||
#$OCC theming:config logo {serverCfg.colorScheme.logo}
|
$OCC theming:config logoheader "${logoSvgMount}"
|
||||||
#$OCC theming:config logoheader {serverCfg.colorScheme.logo}
|
$OCC theming:config favicon "${logoIcoMount}"
|
||||||
#$OCC theming:config background {serverCfg.colorScheme.bg}
|
$OCC theming:config background "${backgroundMount}"
|
||||||
|
|
||||||
else
|
|
||||||
echo "Nextcloud is already installed. Skipping setup."
|
|
||||||
fi
|
|
||||||
|
|
||||||
$OCC config:app:set serverinfo token --value="$HOMEPAGE_VAR_NEXTCLOUD_API"
|
$OCC config:app:set serverinfo token --value="$HOMEPAGE_VAR_NEXTCLOUD_API"
|
||||||
|
|
||||||
|
|||||||
@@ -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@"
|
||||||
@@ -85,7 +85,9 @@ in {
|
|||||||
description = "Run ${e.name} setup";
|
description = "Run ${e.name} setup";
|
||||||
after = [ "podman-${e.name}-${e.trigger}.service" ];
|
after = [ "podman-${e.name}-${e.trigger}.service" ];
|
||||||
wants = [ "podman-${e.name}-${e.trigger}.service" ];
|
wants = [ "podman-${e.name}-${e.trigger}.service" ];
|
||||||
|
partOf = [ "podman-${e.name}-${e.trigger}.service" ];
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
restartTriggers = [ e.script ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
TimeoutStartSec = "360s";
|
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;}; }) ];
|
type = types.oneOf [ types.bool (types.submodule { options = import ./server.nix {inherit lib;}; }) ];
|
||||||
default = false;
|
default = false;
|
||||||
};
|
};
|
||||||
|
media = mkOption {
|
||||||
|
type = types.submodule { options = import ./media.nix { inherit lib; }; };
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
extra = import ./extra.nix {inherit lib;};
|
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:
|
in forEachSystem (system:
|
||||||
let
|
let
|
||||||
overlays = import ../overlays { inherit inputs pkgs; };
|
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;
|
pkgs = import inputs.nixpkgs { inherit system overlays; } // overrides;
|
||||||
in {
|
in {
|
||||||
default = import ./devsh { inherit pkgs; };
|
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";
|
suwayomi.subdomain = "manga";
|
||||||
calibre.subdomain = "books";
|
calibre.subdomain = "books";
|
||||||
selfmark = { subdomain = "arr"; subpath = "selfmark"; };
|
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";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||