simple-nixos-fediverse/fediversity/garage.nix

221 lines
8.3 KiB
Nix
Raw Normal View History

let
# generate one using openssl (somehow)
2024-05-25 01:02:12 +02:00
# XXX: when importing, garage tells you importing is only meant for keys previously generated by garage. is it okay to generate them using openssl? it seems to work fine
snakeoil_key = {
2024-04-03 14:40:19 +02:00
id = "GK22a15201acacbd51cd43e327";
secret = "82b2b4cbef27bf8917b350d5b10a87c92fa9c8b13a415aeeea49726cf335d74e";
};
in
# TODO: expand to a multi-machine setup
{ config, lib, pkgs, ... }:
let
2024-09-17 17:31:58 +02:00
inherit (builtins) toString;
inherit (lib) types mkOption mkEnableOption optionalString concatStringsSep;
inherit (lib.strings) escapeShellArg;
inherit (lib.attrsets) filterAttrs mapAttrs';
cfg = config.services.garage;
fedicfg = config.fediversity.internal.garage;
concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset);
ensureBucketScriptFn = bucket: { website, aliases, corsRules }:
let
bucketArg = escapeShellArg bucket;
corsRulesJSON = escapeShellArg (builtins.toJSON {
CORSRules = [{
AllowedHeaders = corsRules.allowedHeaders;
AllowedMethods = corsRules.allowedMethods;
AllowedOrigins = corsRules.allowedOrigins;
}];
});
in ''
# garage bucket info tells us if the bucket already exists
garage bucket info ${bucketArg} || garage bucket create ${bucketArg}
# TODO: should this --deny the website if `website` is false?
${optionalString website ''
garage bucket website --allow ${bucketArg}
''}
${concatStringsSep "\n" (map (alias: ''
garage bucket alias ${bucketArg} ${escapeShellArg alias}
'') aliases)}
${optionalString corsRules.enable ''
garage bucket allow --read --write --owner ${bucketArg} --key tmp
2024-07-18 12:44:13 +02:00
# TODO: endpoin-url should not be hard-coded
aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url ${fedicfg.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON}
garage bucket deny --read --write --owner ${bucketArg} --key tmp
''}
'';
ensureBucketsScript = concatMapAttrs ensureBucketScriptFn cfg.ensureBuckets;
ensureAccessScriptFn = key: bucket: { read, write, owner }: ''
garage bucket allow ${optionalString read "--read"} ${optionalString write "--write"} ${optionalString owner "--owner"} \
${escapeShellArg bucket} --key ${escapeShellArg key}
'';
ensureKeyScriptFn = key: {id, secret, ensureAccess}: ''
2024-09-20 18:35:22 +02:00
## FIXME: Check whether the key exist and skip this step if that is the case. Get rid of this `|| :`
garage key import --yes -n ${escapeShellArg key} ${escapeShellArg id} ${escapeShellArg secret} || :
${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
'';
ensureKeysScript = concatMapAttrs ensureKeyScriptFn cfg.ensureKeys;
in
{
# add in options to ensure creation of buckets and keys
options = {
2024-03-27 10:59:50 +01:00
services.garage = {
ensureBuckets = mkOption {
type = types.attrsOf (types.submodule {
options = {
website = mkOption {
type = types.bool;
default = false;
};
2024-05-25 01:02:12 +02:00
# I think setting corsRules should allow another website to show images from your bucket
2024-04-03 14:40:19 +02:00
corsRules = {
enable = mkEnableOption "CORS Rules";
allowedHeaders = mkOption {
type = types.listOf types.str;
default = [];
};
allowedMethods = mkOption {
type = types.listOf types.str;
default = [];
};
allowedOrigins = mkOption {
type = types.listOf types.str;
default = [];
};
};
aliases = mkOption {
type = types.listOf types.str;
default = [];
};
2024-03-27 10:59:50 +01:00
};
});
default = {};
2024-03-27 10:59:50 +01:00
};
ensureKeys = mkOption {
type = types.attrsOf (types.submodule {
2024-04-03 14:40:19 +02:00
# TODO: these should be managed as secrets, not in the nix store
2024-03-27 10:59:50 +01:00
options = {
id = mkOption {
2024-05-25 01:02:12 +02:00
type = types.str;
2024-03-27 10:59:50 +01:00
};
secret = mkOption {
2024-05-25 01:02:12 +02:00
type = types.str;
2024-03-27 10:59:50 +01:00
};
# TODO: assert at least one of these is true
# NOTE: this currently needs to be done at the top level module
2024-03-27 10:59:50 +01:00
ensureAccess = mkOption {
type = types.attrsOf (types.submodule {
options = {
read = mkOption {
type = types.bool;
default = false;
};
write = mkOption {
type = types.bool;
default = false;
};
owner = mkOption {
type = types.bool;
default = false;
};
};
});
default = [];
};
};
});
default = {};
2024-03-27 10:59:50 +01:00
};
};
};
config = lib.mkIf config.fediversity.enable {
environment.systemPackages = [ pkgs.minio-client pkgs.awscli ];
2024-09-17 17:31:58 +02:00
networking.firewall.allowedTCPPorts = [
fedicfg.rpc.port
2024-09-17 17:31:58 +02:00
];
services.garage = {
enable = true;
package = pkgs.garage_0_9;
settings = {
replication_mode = "none";
# TODO: use a secret file
rpc_secret = "d576c4478cc7d0d94cfc127138cbb82018b0155c037d1c827dfb6c36be5f6625";
# TODO: why does this have to be set? is there not a sensible default?
rpc_bind_addr = "[::]:${toString fedicfg.rpc.port}";
rpc_public_addr = "[::1]:${toString fedicfg.rpc.port}";
s3_api.api_bind_addr = "[::]:${toString fedicfg.api.port}";
2024-09-23 18:11:04 +02:00
s3_web.bind_addr = "[::]:${toString fedicfg.web.internalPort}";
s3_web.root_domain = ".${fedicfg.web.rootDomain}";
index = "index.html";
s3_api.s3_region = "garage";
s3_api.root_domain = ".${fedicfg.api.domain}";
2024-09-23 17:55:54 +02:00
};
};
2024-09-23 18:39:15 +02:00
## Create a proxy from <bucket>.web.garage.<domain> to localhost:3902 for
## each bucket that has `website = true`.
services.nginx.virtualHosts =
let
value = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:3902";
extraConfig = ''
proxy_set_header Host $host;
'';
};
};
in mapAttrs'
(bucket: _: {name = fedicfg.web.domainForBucket bucket; inherit value;})
(filterAttrs (_: {website, ...}: website) cfg.ensureBuckets);
2024-09-24 14:40:35 +02:00
systemd.services.ensure-garage = {
after = [ "garage.service" ];
wantedBy = [ "garage.service" ];
2024-09-05 15:32:34 +02:00
serviceConfig = {
Type = "oneshot";
};
path = [ cfg.package pkgs.perl pkgs.awscli ];
script = ''
set -xeuo pipefail
2024-09-17 17:31:58 +02:00
# Give Garage time to start up by waiting until somethings speaks HTTP
# behind Garage's API URL.
until ${pkgs.curl}/bin/curl -sio /dev/null ${fedicfg.api.url}; do sleep 1; done
2024-03-27 10:59:50 +01:00
# XXX: this is very sensitive to being a single instance
2024-05-25 01:02:12 +02:00
# (doing the bare minimum to get garage up and running)
# also, it's crazy that we have to parse command output like this
2024-05-25 01:02:12 +02:00
# TODO: talk to garage maintainer about making this nicer to work with in Nix
# before I do that though, I should figure out how setting it up across multiple machines will work
GARAGE_ID=$(garage node id 2>/dev/null | perl -ne '/(.*)@.*/ && print $1')
garage layout assign -z g1 -c 1G $GARAGE_ID
LAYOUT_VER=$(garage layout show | perl -ne '/Current cluster layout version: (\d*)/ && print $1')
garage layout apply --version $((LAYOUT_VER + 1))
2024-04-03 14:40:19 +02:00
# XXX: this is a hack because we want to write to the buckets here but we're not guaranteed any access keys
2024-05-25 01:02:12 +02:00
# TODO: generate this key here rather than using a well-known key
2024-09-20 18:35:22 +02:00
# TODO: if the key already exists, we get an error; hacked with this `|| :` which needs to be removed
garage key import --yes -n tmp ${snakeoil_key.id} ${snakeoil_key.secret} || :
2024-04-03 14:40:19 +02:00
export AWS_ACCESS_KEY_ID=${snakeoil_key.id};
export AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret};
${ensureBucketsScript}
${ensureKeysScript}
2024-05-25 01:02:12 +02:00
# garage doesn't like re-adding keys that once existed, so we can't delete / recreate it every time
# garage key delete ${snakeoil_key.id} --yes
'';
};
};
}