forked from fediversity/fediversity
		
	closes #93. note that this includes classes: - `nixos` - `nixosTest` - `nixops4Resource` - `nixops4Deployment` .. and my (made-up, as per the [docs](https://ryantm.github.io/nixpkgs/module-system/module-system/#module-system-lib-evalModules-param-class)): - `nix-unit` - `package` .. while i did not manage to cover: - service tests, given `pkgs.nixosTest` seemed to not actually like `_class = "nixosTest"` (?!) ... nor #93's mentioned destructured arguments for that matter, as per Fediversity/Fediversity#93 (comment) - let me know if that is still desired as well. Reviewed-on: Fediversity/Fediversity#398 Reviewed-by: Valentin Gagarin <valentin.gagarin@tweag.io> Co-authored-by: Kiara Grouwstra <kiara@procolix.eu> Co-committed-by: Kiara Grouwstra <kiara@procolix.eu>
		
			
				
	
	
		
			212 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| let
 | |
|   # generate one using openssl (somehow)
 | |
|   # 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 = {
 | |
|     id = "GK22a15201acacbd51cd43e327";
 | |
|     secret = "82b2b4cbef27bf8917b350d5b10a87c92fa9c8b13a415aeeea49726cf335d74e";
 | |
|   };
 | |
| in
 | |
| 
 | |
| {
 | |
|   config,
 | |
|   lib,
 | |
|   pkgs,
 | |
|   ...
 | |
| }:
 | |
| 
 | |
| let
 | |
|   inherit (builtins) toString;
 | |
|   inherit (lib) optionalString concatStringsSep mkIf;
 | |
|   inherit (lib.strings) escapeShellArg;
 | |
|   inherit (lib.attrsets) filterAttrs mapAttrs';
 | |
|   concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset);
 | |
| 
 | |
|   cfg = config.services.garage;
 | |
| 
 | |
|   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
 | |
|         # TODO: endpoin-url should not be hard-coded
 | |
|         aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url ${config.fediversity.garage.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON}
 | |
|         garage bucket deny --read --write --owner ${bucketArg} --key tmp
 | |
|       ''}
 | |
|     '';
 | |
| 
 | |
|   ensureBucketsScript = concatMapAttrs ensureBucketScriptFn config.fediversity.garage.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:
 | |
|     {
 | |
|       s3AccessKeyFile,
 | |
|       s3SecretKeyFile,
 | |
|       ensureAccess,
 | |
|     }:
 | |
|     ''
 | |
|       ## 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} $(cat ${escapeShellArg s3AccessKeyFile}) $(cat ${escapeShellArg s3SecretKeyFile}) || :
 | |
|       ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
 | |
|     '';
 | |
| 
 | |
|   ensureKeysScript = concatMapAttrs ensureKeyScriptFn config.fediversity.garage.ensureKeys;
 | |
| 
 | |
| in
 | |
| {
 | |
|   _class = "nixos";
 | |
| 
 | |
|   imports = [ ./options.nix ];
 | |
| 
 | |
|   config = mkIf config.fediversity.garage.enable {
 | |
|     environment.systemPackages = [
 | |
|       pkgs.minio-client
 | |
|       pkgs.awscli
 | |
|     ];
 | |
| 
 | |
|     ## REVIEW: Do we want to reverse proxy the RPC and API ports? In fact,
 | |
|     ## shouldn't we just get rid of RPC at all, we're not using it.
 | |
|     networking.firewall.allowedTCPPorts = [
 | |
|       80
 | |
|       443
 | |
|       config.fediversity.garage.api.port
 | |
|       config.fediversity.garage.rpc.port
 | |
|     ];
 | |
| 
 | |
|     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 config.fediversity.garage.rpc.port}";
 | |
|         rpc_public_addr = "[::1]:${toString config.fediversity.garage.rpc.port}";
 | |
|         s3_api.api_bind_addr = "[::]:${toString config.fediversity.garage.api.port}";
 | |
|         s3_web.bind_addr = "[::]:${toString config.fediversity.garage.web.internalPort}";
 | |
|         s3_web.root_domain = ".${config.fediversity.garage.web.rootDomain}";
 | |
|         index = "index.html";
 | |
| 
 | |
|         s3_api.s3_region = "garage";
 | |
|         s3_api.root_domain = ".${config.fediversity.garage.api.domain}";
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     services.nginx.enable = true;
 | |
| 
 | |
|     ## 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 = ''
 | |
|               ## copied from https://garagehq.deuxfleurs.fr/documentation/cookbook/reverse-proxy/
 | |
|               proxy_set_header Host $host;
 | |
|               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | |
|               # Disable buffering to a temporary file.
 | |
|               proxy_max_temp_file_size 0;
 | |
| 
 | |
|               ## NOTE: This page suggests many more options for the object storage
 | |
|               ## proxy. We should take a look.
 | |
|               ## https://docs.joinmastodon.org/admin/optional/object-storage-proxy/
 | |
|             '';
 | |
|           };
 | |
|         };
 | |
|       in
 | |
|       mapAttrs' (bucket: _: {
 | |
|         name = config.fediversity.garage.web.domainForBucket bucket;
 | |
|         inherit value;
 | |
|       }) (filterAttrs (_: { website, ... }: website) config.fediversity.garage.ensureBuckets);
 | |
| 
 | |
|     systemd.services.ensure-garage = {
 | |
|       after = [ "garage.service" ];
 | |
|       wantedBy = [ "garage.service" ];
 | |
|       serviceConfig = {
 | |
|         Type = "oneshot";
 | |
|       };
 | |
|       path = [
 | |
|         cfg.package
 | |
|         pkgs.perl
 | |
|         pkgs.awscli
 | |
|       ];
 | |
|       script = ''
 | |
|         set -xeuo pipefail
 | |
| 
 | |
|         # 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 ${config.fediversity.garage.api.url}; do sleep 1; done
 | |
| 
 | |
|         # XXX: this is very sensitive to being a single instance
 | |
|         # (doing the bare minimum to get garage up and running)
 | |
|         # also, it's crazy that we have to parse command output like this
 | |
|         # 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))
 | |
| 
 | |
|         # XXX: this is a hack because we want to write to the buckets here but we're not guaranteed any access keys
 | |
|         # TODO: generate this key here rather than using a well-known key
 | |
|         # 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} || :
 | |
|         export AWS_ACCESS_KEY_ID=${snakeoil_key.id};
 | |
|         export AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret};
 | |
| 
 | |
|         ${ensureBucketsScript}
 | |
|         ${ensureKeysScript}
 | |
| 
 | |
|         # 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
 | |
|       '';
 | |
|     };
 | |
|   };
 | |
| }
 |