diff --git a/machines/gerd.nix b/machines/gerd.nix index 8347e5f..7435bd5 100644 --- a/machines/gerd.nix +++ b/machines/gerd.nix @@ -14,6 +14,8 @@ ./gerd/services/murmur.nix ./gerd/services/hedgedoc.nix + ./gerd/services/member-website + # ./gerd/services/owncast.nix ]; diff --git a/machines/gerd/services/authelia/authelia-nginx.nix b/machines/gerd/services/authelia/authelia-nginx.nix index 9a13625..f927e4c 100644 --- a/machines/gerd/services/authelia/authelia-nginx.nix +++ b/machines/gerd/services/authelia/authelia-nginx.nix @@ -76,20 +76,22 @@ let error_page 401 =302 https://auth.fricloud.dk/?rd=$target_url; ''; in { - mine.shared.lib.authelia.mkProtectedWebsite = virtualHostConfig: lib.recursiveUpdate { + mine.shared.lib.authelia.mkProtectedWebsite = { vhostConfig, endpoint ? "/" }: lib.recursiveUpdate { forceSSL = true; enableACME = true; extraConfig = "include ${autheliaLocation};"; - locations."/" = { + locations."${endpoint}" = { extraConfig = "include ${autheliaRequest};"; }; - } virtualHostConfig; + } vhostConfig; services.nginx.virtualHosts."test.fricloud.dk" = config.mine.shared.lib.authelia.mkProtectedWebsite { - locations."/".root = pkgs.writeTextDir "index.html" '' - ACCESS GRANTED! - ''; + vhostConfig = { + locations."/".root = pkgs.writeTextDir "index.html" '' + ACCESS GRANTED! + ''; + }; }; } diff --git a/machines/gerd/services/member-website/app.py b/machines/gerd/services/member-website/app.py new file mode 100755 index 0000000..c234ef2 --- /dev/null +++ b/machines/gerd/services/member-website/app.py @@ -0,0 +1,116 @@ +#!/usr/bin/env nix-shell +#!nix-shell --pure -i python3 -p "python3.withPackages (ps: with ps; [ flask ])" +from typing import Any + +from flask import Flask, request +from flask import render_template, render_template_string +import argparse +import logging +import json +import sys + +logging.basicConfig() +logger = logging.getLogger(__name__) + +app = Flask(__name__) + +services_data: dict[str, Any] = {} + +tmpl_index = """ + +Members Area +Welcome to the members area {% if user.name %} {{ user.name }} {% else %} {{ user.username }} {% endif %}! + +

Services

+{% for name, info in services.items() %} +

{{ info.name }}

+ {{ info.description }} +
+ {{ info.url }} +
+  Package name: {{ info.package.name }}
+  Package version: {{ info.package.version }}
+  Package homepage: {{ info.package.meta.homepage }}
+  License: {{ info.package.meta.license.spdxId }} ({{ info.package.meta.license.shortName }})
+  Unfree: {{ info.package.meta.unfree }}
+
+  
+
+{% endfor %} +""" + + +def extract_secrets() -> dict[str, str]: + new_args = {} + # read all secrets + for service in services_data.values(): + for k, v in service.get("secrets", {}).items(): + try: + fcontent = open(v, "r").read() + except Exception as e: + logger.exception("unable to open secret file", e) + continue + + isEnv: bool = False + if v.endswith("env"): + isEnv = True + + if not isEnv: + new_args[k.upper()] = fcontent + continue + + # parse env file + for line in fcontent.splitlines(): + line = line.strip() + if not line: + continue + + envkey, envvalue = line.split("=", maxsplit=1) + + if envvalue[0] == '"' or envvalue[1] == "'": + envvalue = envvalue[1:-1] + + new_args[envkey.upper()] = envvalue + + return new_args + + +@app.route("/") +def index(): + # extract user information + user_info = { + "username": request.headers.get("Remote-User"), + "name": request.headers.get("Remote-Name"), + "groups": request.headers.get("Remote-Groups"), + "email": request.headers.get("Remote-Email"), + } + tmpl_firstpass = render_template_string( + tmpl_index, + services=services_data, + user=user_info, + ) + return render_template_string( + tmpl_firstpass, + user=user_info, + secrets=extract_secrets(), + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--debug", + type=bool, + action=argparse.BooleanOptionalAction, + default=False, + ) + parser.add_argument("--listen", type=str, default="127.0.0.1") + parser.add_argument("--port", type=int, default=5000) + parser.add_argument("--meta-json", type=str, required=True) + args = parser.parse_args() + + if args.debug: + logger.setLevel(logging.DEBUG) + + services_data = json.loads(open(args.meta_json, "r").read()) + app.run(host=args.listen, port=args.port, debug=args.debug) diff --git a/machines/gerd/services/member-website/default.nix b/machines/gerd/services/member-website/default.nix new file mode 100644 index 0000000..bcaeeb4 --- /dev/null +++ b/machines/gerd/services/member-website/default.nix @@ -0,0 +1,27 @@ +{ config, pkgs, ... }: + +let + urlpath = "/members"; + metaJSONFile = (pkgs.formats.json {}).generate "meta-service-info.json" config.mine.shared.meta; + port = 5050; +in { + systemd.services.website-member = { + description = "members area website"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ]; + serviceConfig = { + ExecStart = let + pythonEnv = pkgs.python3.withPackages(ps: with ps; [ flask ]); + in "${pythonEnv}/bin/python ${./app.py} --port ${builtins.toString port} --meta-json ${metaJSONFile}"; + Restart = "always"; + }; + }; + + services.nginx.virtualHosts."${config.mine.shared.settings.domain}" = config.mine.shared.lib.authelia.mkProtectedWebsite { + endpoint = urlpath; + vhostConfig.locations."${urlpath}" = { + extraConfig = "rewrite ^${urlpath}(.*)$ /$1 break;"; + proxyPass = "http://localhost:${builtins.toString port}"; + }; + }; +}