2024-03-27 10:42:11 +01:00
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
2024-03-27 10:42:11 +01:00
snakeoil_key = {
2024-04-03 14:40:19 +02:00
id = " G K 2 2 a 1 5 2 0 1 a c a c b d 5 1 c d 4 3 e 3 2 7 " ;
secret = " 8 2 b 2 b 4 c b e f 2 7 b f 8 9 1 7 b 3 5 0 d 5 b 1 0 a 8 7 c 9 2 f a 9 c 8 b 1 3 a 4 1 5 a e e e a 4 9 7 2 6 c f 3 3 5 d 7 4 e " ;
2024-03-27 10:42:11 +01:00
} ;
in
# TODO: expand to a multi-machine setup
{ config , lib , pkgs , . . . }: {
# add in options to ensure creation of buckets and keys
2024-03-27 10:59:50 +01:00
options =
let
2024-04-03 14:40:19 +02:00
inherit ( lib ) types mkOption mkEnableOption ;
2024-03-27 10:59:50 +01:00
in {
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 " C O R S R u l e s " ;
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
} ;
} ) ;
} ;
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
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 = [ ] ;
} ;
} ;
} ) ;
} ;
} ;
} ;
2024-03-27 10:42:11 +01:00
config = {
virtualisation . vmVariant = {
virtualisation . diskSize = 2048 ;
virtualisation . forwardPorts = [
{
from = " h o s t " ;
host . port = 3901 ;
guest . port = 3901 ;
}
{
from = " h o s t " ;
host . port = 3902 ;
guest . port = 3902 ;
}
] ;
} ;
2024-05-25 01:02:12 +02:00
2024-04-03 14:40:19 +02:00
environment . systemPackages = [ pkgs . minio-client pkgs . awscli ] ;
2024-03-27 10:42:11 +01:00
networking . firewall . allowedTCPPorts = [ 3901 3902 ] ;
services . garage = {
enable = true ;
package = pkgs . garage_0_9 ;
settings = {
replication_mode = " n o n e " ;
# TODO: use a secret file
rpc_secret = " d 5 7 6 c 4 4 7 8 c c 7 d 0 d 9 4 c f c 1 2 7 1 3 8 c b b 8 2 0 1 8 b 0 1 5 5 c 0 3 7 d 1 c 8 2 7 d f b 6 c 3 6 b e 5 f 6 6 2 5 " ;
# TODO: why does this have to be set? is there not a sensible default?
rpc_bind_addr = " [ : : ] : 3 9 0 1 " ;
rpc_public_addr = " [ : : 1 ] : 3 9 0 1 " ;
s3_api . api_bind_addr = " [ : : ] : 3 9 0 0 " ;
s3_web . bind_addr = " [ : : ] : 3 9 0 2 " ;
s3_web . root_domain = " . w e b . g a r a g e . l o c a l h o s t " ;
index = " i n d e x . h t m l " ;
s3_api . s3_region = " g a r a g e " ;
s3_api . root_domain = " . s 3 . g a r a g e . l o c a l h o s t " ;
} ;
} ;
systemd . services . ensure-garage = {
after = [ " g a r a g e . s e r v i c e " ] ;
wantedBy = [ " g a r a g e . s e r v i c e " ] ;
2024-04-03 14:40:19 +02:00
path = [ config . services . garage . package pkgs . perl pkgs . awscli ] ;
2024-03-27 10:42:11 +01:00
script = ''
set - xeuo pipefail
# give garage time to start up
sleep 3
2024-03-27 10:59:50 +01:00
2024-03-27 10:42:11 +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)
2024-03-27 10:42:11 +01:00
# 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
2024-03-27 10:42:11 +01:00
GARAGE_ID = $ ( garage node id 2 > /dev/null | perl - ne ' / ( . * ) @ . * / && print $ 1 ' )
garage layout assign - z g1 - c 1 G $ 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-04-03 14:40:19 +02:00
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 } ;
2024-03-27 10:59:50 +01:00
$ {
2024-04-03 14:40:19 +02:00
lib . concatStringsSep " \n " ( lib . mapAttrsToList ( bucket : { website , aliases , corsRules }: ''
2024-05-25 01:02:12 +02:00
# garage bucket info tells us if the bucket already exists
garage bucket info $ { bucket } || garage bucket create $ { bucket }
# TODO: should this --deny the website if `website` is false?
2024-03-27 10:59:50 +01:00
$ { lib . optionalString website ''
garage bucket website - - allow $ { bucket }
'' }
2024-05-25 01:02:12 +02:00
2024-04-03 14:40:19 +02:00
$ { lib . concatStringsSep " \n " ( map ( alias : ''
garage bucket alias $ { bucket } $ { alias }
'' ) a l i a s e s ) }
$ { lib . optionalString corsRules . enable ''
2024-05-25 01:02:12 +02:00
# TODO: can i turn this whole thing into one builtins.toJSON?
2024-04-03 14:40:19 +02:00
export CORS = $ { lib . concatStrings [
" ' "
'' { " C O R S R u l e s " : [ { ''
'' " A l l o w e d H e a d e r s " : ${ builtins . toJSON corsRules . allowedHeaders } , ''
'' " A l l o w e d M e t h o d s " : ${ builtins . toJSON corsRules . allowedMethods } , ''
'' " A l l o w e d O r i g i n s " : ${ builtins . toJSON corsRules . allowedOrigins } ''
'' } ] } ''
" ' "
] }
2024-05-25 01:02:12 +02:00
2024-04-03 14:40:19 +02:00
garage bucket allow - - read - - write - - owner $ { bucket } - - key tmp
aws - - endpoint http://s3.garage.localhost:3900 s3api put-bucket-cors - - bucket $ { bucket } - - cors-configuration $ CORS
garage bucket deny - - read - - write - - owner $ { bucket } - - key tmp
'' }
2024-03-27 10:59:50 +01:00
'' ) c o n f i g . s e r v i c e s . g a r a g e . e n s u r e B u c k e t s )
}
$ {
lib . concatStringsSep " \n " ( lib . mapAttrsToList ( key : { id , secret , ensureAccess }: ''
garage key import - - yes - n $ { key } $ { id } $ { secret }
$ {
lib . concatStringsSep " \n " ( lib . mapAttrsToList ( bucket : { read , write , owner }: ''
garage bucket allow $ { lib . optionalString read " - - r e a d " } $ { lib . optionalString write " - - w r i t e " } $ { lib . optionalString owner " - - o w n e r " } $ { bucket } - - key $ { key }
'' ) e n s u r e A c c e s s )
}
'' ) c o n f i g . s e r v i c e s . g a r a g e . e n s u r e K e y s )
}
2024-05-25 01:02:12 +02:00
garage key delete $ { snakeoil_key . id } - - yes
2024-03-27 10:42:11 +01:00
'' ;
} ;
} ;
}