{ lib, config, ... }: let inherit (lib) mkOption mkIf types mapAttrs; cfgParent = config.bagel.s3; cfg = config.bagel.s3.reverse-proxy; mkTarget = { name, bucket ? name }: { mount = { host = "${name}.${cfgParent.webRootDomain}"; path = [ "/" ]; }; actions.GET = { enabled = true; config = { # e.g. /2.90 will 404, so it will redirect to /2.90/ if it is a directory redirectWithTrailingSlashForNotFoundFile = true; indexDocument = "index.html"; }; }; bucket = { name = bucket; region = "garage"; s3Endpoint = "https://${cfgParent.s3RootDomain}"; credentials = { accessKey.env = "AWS_ACCESS_KEY_ID"; secretKey.env = "AWS_SECRET_KEY"; }; }; }; in { options.bagel.s3.reverse-proxy = { targets = mkOption { type = types.attrsOf (types.submodule ({ name, ... }: { bucket = mkOption { type = types.str; default = name; }; })); default = { }; }; port = mkOption { type = types.port; default = 10652; }; }; config = mkIf cfg.enable { age.secrets.s3-revproxy-api-key-env.file = ./s3-revproxy-env.age; # this solves garage supporting neither anonymous access nor automatic # directory indexing by simply ignoring garage's web server and replacing it # with overengineered golang instead. services.s3-revproxy = { enable = true; settings = { templates = { helpers = [ ./s3-revproxy-templates/_helpers.tpl ]; notFoundError = { headers = { "Content-Type" = "{{ template \"main.headers.contentType\" . }}"; }; status = "404"; }; folderList = { path = ./s3-revproxy-templates/folder-list.tpl; headers = { "Content-Type" = "{{ template \"main.headers.contentType\" . }}"; }; # empty s3 directories are not real and cannot hurt you. # due to redirectWithTrailingSlashForNotFoundFile, garbage file names # get redirected as folders, which then appear as empty, yielding # poor UX. status = '' {{- if eq (len .Entries) 0 -}} 404 {{- else -}} 200 {{- end -}} ''; }; }; /* For metrics and debugging (e.g. pulling the config) internalServer = { listenAddr = "127.0.0.1"; port = 1337; }; */ server = { listenAddr = "127.0.0.1"; port = cfg.port; # it's going right into nginx, so no point compress.enabled = false; cors = { enabled = true; allowMethods = [ "GET" ]; allowOrigins = [ "*" ]; }; }; targets = mapAttrs mkTarget cfg.targets; }; environmentFile = config.age.secrets.s3-revproxy-api-key-env.path; }; }; }