infra/services/lix-s3/s3-revproxy.nix
raito a3f6f573a2 feat: add cache.lix.systems
Introduces a full-fleged self-hosted S3 NixOS module with various knobs
to perform NGINX-based redirects and s3-revproxy endpoints.

Signed-off-by: Raito Bezarius <masterancpp@gmail.com>
2024-10-07 17:10:57 +02:00

107 lines
3 KiB
Nix

{ 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;
};
};
}