diff --git a/infra/test-machines/test12/attic.nix b/infra/test-machines/test12/attic.nix deleted file mode 100644 index c8075207..00000000 --- a/infra/test-machines/test12/attic.nix +++ /dev/null @@ -1,203 +0,0 @@ -{lib, tf, ...}: -{ - services.postgresql = { - enable = true; - authentication = lib.mkForce '' - local all all trust - ''; - ensureDatabases = [ - "atticd" - ]; - ensureUsers = [ - { - name = "atticd"; - ensureDBOwnership = true; - } - ]; - }; - - services.atticd = { - enable = true; - # one `monolithic` and any number of `api-server` nodes - mode = "monolithic"; - environmentFile = "/var/lib/secrets/attic_env"; - # https://github.com/zhaofengli/attic/blob/main/server/src/config-template.toml - settings = { - # Socket address to listen on - # listen = "[::]:8080"; - # listen = "0.0.0.0:8080"; - listen = "127.0.0.1:8080"; - - # Allowed `Host` headers - # - # This _must_ be configured for production use. If unconfigured or the - # list is empty, all `Host` headers are allowed. - allowed-hosts = []; - - # The canonical API endpoint of this server - # - # This is the endpoint exposed to clients in `cache-config` responses. - # - # This _must_ be configured for production use. If not configured, the - # API endpoint is synthesized from the client's `Host` header which may - # be insecure. - # - # The API endpoint _must_ end with a slash (e.g., `https://domain.tld/attic/` - # not `https://domain.tld/attic`). - api-endpoint = "https://${tf.resource.hetznerdns_zone.main.name}/"; - - # Whether to soft-delete caches - # - # If this is enabled, caches are soft-deleted instead of actually - # removed from the database. Note that soft-deleted caches cannot - # have their names reused as long as the original database records - # are there. - #soft-delete-caches = false; - - # Whether to require fully uploading a NAR if it exists in the global cache. - # - # If set to false, simply knowing the NAR hash is enough for - # an uploader to gain access to an existing NAR in the global - # cache. - #require-proof-of-possession = true; - - # Database connection - database = { - # Connection URL - # - # For production use it's recommended to use PostgreSQL. - # url = "postgresql:///atticd:password@127.0.0.1:5432/atticd"; - url = "postgresql:///atticd?host=/run/postgresql"; - - # Whether to enable sending on periodic heartbeat queries - # - # If enabled, a heartbeat query will be sent every minute - #heartbeat = false; - }; - - # File storage configuration - storage = { - # Storage type - # - # Can be "local" or "s3". - type = "s3"; - - # ## Local storage - - # The directory to store all files under - # path = "%storage_path%"; - - # ## S3 Storage (set type to "s3" and uncomment below) - - # The AWS region - region = tf.resource.cloudflare_r2_bucket.atticd.location; # is this even used for R2? - - # The name of the bucket - bucket = tf.resource.cloudflare_r2_bucket.atticd.name; - - # Custom S3 endpoint - # - # Set this if you are using an S3-compatible object storage (e.g., Minio). - endpoint = "https://2b56368370c7a8e7f41328f0b8d4040a.r2.cloudflarestorage.com"; - - # Credentials - # - # If unset, the credentials are read from the `AWS_ACCESS_KEY_ID` and - # `AWS_SECRET_ACCESS_KEY` environment variables. - # storage.credentials = { - # access_key_id = ""; - # secret_access_key = ""; - # }; - }; - - # Data chunking - # - # Warning: If you change any of the values here, it will be - # difficult to reuse existing chunks for newly-uploaded NARs - # since the cutpoints will be different. As a result, the - # deduplication ratio will suffer for a while after the change. - chunking = { - # The minimum NAR size to trigger chunking - # - # If 0, chunking is disabled entirely for newly-uploaded NARs. - # If 1, all NARs are chunked. - nar-size-threshold = 65536; # chunk files that are 64 KiB or larger - - # The preferred minimum size of a chunk, in bytes - min-size = 16384; # 16 KiB - - # The preferred average size of a chunk, in bytes - avg-size = 65536; # 64 KiB - - # The preferred maximum size of a chunk, in bytes - max-size = 262144; # 256 KiB - }; - - # Compression - compression = { - # Compression type - # - # Can be "none", "brotli", "zstd", or "xz" - type = "zstd"; - - # Compression level - #level = 8; - }; - - # Garbage collection - garbage-collection = { - # The frequency to run garbage collection at - # - # By default it's 12 hours. You can use natural language - # to specify the interval, like "1 day". - # - # If zero, automatic garbage collection is disabled, but - # it can still be run manually with `atticd --mode garbage-collector-once`. - interval = "12 hours"; - - # Default retention period - # - # Zero (default) means time-based garbage-collection is - # disabled by default. You can enable it on a per-cache basis. - #default-retention-period = "6 months"; - }; - - jwt = { - # WARNING: Changing _anything_ in this section will break any existing - # tokens. If you need to regenerate them, ensure that you use the the - # correct secret and include the `iss` and `aud` claims. - - # JWT `iss` claim - # - # Set this to the JWT issuer that you want to validate. - # If this is set, all received JWTs will validate that the `iss` claim - # matches this value. - #token-bound-issuer = "some-issuer"; - - # JWT `aud` claim - # - # Set this to the JWT audience(s) that you want to validate. - # If this is set, all received JWTs will validate that the `aud` claim - # contains at least one of these values. - #token-bound-audiences = ["some-audience1", "some-audience2"]; - }; - - # jwt.signing = { - # # JWT RS256 secret key - # # - # # Set this to the base64-encoded private half of an RSA PEM PKCS1 key. - # # TODO - # # You can also set it via the `ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64` - # # environment variable. - # token-rs256-secret-base64 = "%token_rs256_secret_base64%"; - - # # JWT HS256 secret key - # # - # # Set this to the base64-encoded HMAC secret key. - # # You can also set it via the `ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64` - # # environment variable. - # #token-hs256-secret-base64 = ""; - # }; - }; - }; -} diff --git a/infra/test-machines/test12/default.nix b/infra/test-machines/test12/default.nix index 92b34c96..77b19217 100644 --- a/infra/test-machines/test12/default.nix +++ b/infra/test-machines/test12/default.nix @@ -19,7 +19,7 @@ nixos.module = { imports = [ - ./attic.nix + ../../../services/fediversity/attic ]; }; } diff --git a/npins/sources.json b/npins/sources.json index 4971590b..a7787a83 100644 --- a/npins/sources.json +++ b/npins/sources.json @@ -80,6 +80,19 @@ "url": "https://api.github.com/repos/bigskysoftware/htmx/tarball/v2.0.4", "hash": "1c4zm3b7ym01ijydiss4amd14mv5fbgp1n71vqjk4alc35jlnqy2" }, + "nix-templating": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "lassulus", + "repo": "nix-templating" + }, + "branch": "master", + "submodules": false, + "revision": "437fd19b727e963560980fc4026f79400c440e39", + "url": "https://github.com/lassulus/nix-templating/archive/437fd19b727e963560980fc4026f79400c440e39.tar.gz", + "hash": "000gdd9a4w6gh9lgklsb4dzchgd0fpdkxlhgvpmw0m6ssmrxivkb" + }, "nix-unit": { "type": "Git", "repository": { @@ -105,6 +118,19 @@ "revision": "f33a4d26226c05d501b9d4d3e5e60a3a59991921", "url": "https://github.com/nixos/nixpkgs/archive/f33a4d26226c05d501b9d4d3e5e60a3a59991921.tar.gz", "hash": "1b6dm1sn0bdpcsmxna0zzspjaixa2dald08005fry5jrbjvwafdj" + }, + "vars": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "lassulus", + "repo": "vars" + }, + "branch": "main", + "submodules": false, + "revision": "856c18f0e7b95e262ac88ba9ddebf506a16fd4a5", + "url": "https://github.com/lassulus/vars/archive/856c18f0e7b95e262ac88ba9ddebf506a16fd4a5.tar.gz", + "hash": "095dmc67pf5idj4pgnibjbgfxpkm73px3sc6hylc9j0sqh3379q7" } }, "version": 5 diff --git a/services/fediversity/attic/default.nix b/services/fediversity/attic/default.nix new file mode 100644 index 00000000..b137c2ce --- /dev/null +++ b/services/fediversity/attic/default.nix @@ -0,0 +1,298 @@ +{ + lib, + pkgs, + config, + sources, + ... +}: +let + inherit (lib) mkIf mkMerge; + inherit + (import "${sources.nix-templating}/lib.nix" { + inherit pkgs lib; + nix_templater = pkgs.callPackage "${sources.nix-templating}/pkgs/nix_templater" { }; + }) + fileContents + template + ; +in +{ + imports = with sources; [ + ./options.nix + "${vars}/options.nix" + "${vars}/backends/on-machine.nix" + ]; + + config = mkMerge [ + (mkIf + ( + config.fediversity.garage.enable + && config.fediversity.attic.s3AccessKeyFile != null + && config.fediversity.attic.s3SecretKeyFile != null + ) + { + fediversity.garage = { + ensureBuckets = { + attic = { + website = true; + # TODO: these are too broad, after getting everything to work narrow it down to the domain we actually want + corsRules = { + enable = true; + allowedHeaders = [ "*" ]; + allowedMethods = [ "GET" ]; + allowedOrigins = [ "*" ]; + }; + }; + }; + ensureKeys = { + attic = { + inherit (config.fediversity.attic) s3AccessKeyFile s3SecretKeyFile; + ensureAccess = { + peertube-videos = { + read = true; + write = true; + owner = true; + }; + peertube-playlists = { + read = true; + write = true; + owner = true; + }; + }; + }; + }; + }; + } + ) + (mkIf config.fediversity.attic.enable { + + services.postgresql = { + enable = true; + authentication = lib.mkForce '' + local all all trust + ''; + ensureDatabases = [ + "atticd" + ]; + ensureUsers = [ + { + name = "atticd"; + ensureDBOwnership = true; + } + ]; + }; + + # open up access to the mastodon web interface. 80 is necessary if only for ACME + networking.firewall.allowedTCPPorts = [ + 80 + 443 + 8080 + ]; + + vars.generators.attic = { + runtimeInputs = [ pkgs.openssl ]; + files.token.secret = true; + script = '' + genrsa -traditional 4096 | base64 -w0 > $out/token + ''; + }; + + services.atticd = { + enable = true; + # one `monolithic` and any number of `api-server` nodes + mode = "monolithic"; + + environmentFile = "${ + template { + name = "attic.env"; + outPath = "./attic.env"; + text = '' + ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=${fileContents config.vars.generators.attic.files.token.path} + AWS_ACCESS_KEY_ID=${config.fediversity.attic.ensureKeys.mastodon.id} + AWS_SECRET_ACCESS_KEY=${config.fediversity.attic.ensureKeys.mastodon.secret} + ''; + } + }/bin/attic.env"; + + # https://github.com/zhaofengli/attic/blob/main/server/src/config-template.toml + settings = { + # Socket address to listen on + # listen = "[::]:8080"; + listen = "0.0.0.0:8080"; + # listen = "127.0.0.1:8080"; + + # Allowed `Host` headers + # + # This _must_ be configured for production use. If unconfigured or the + # list is empty, all `Host` headers are allowed. + allowed-hosts = [ ]; + + # The canonical API endpoint of this server + # + # This is the endpoint exposed to clients in `cache-config` responses. + # + # This _must_ be configured for production use. If not configured, the + # API endpoint is synthesized from the client's `Host` header which may + # be insecure. + # + # The API endpoint _must_ end with a slash (e.g., `https://domain.tld/attic/` + # not `https://domain.tld/attic`). + api-endpoint = "https://${config.fediversity.attic.domain}/"; + + # Whether to soft-delete caches + # + # If this is enabled, caches are soft-deleted instead of actually + # removed from the database. Note that soft-deleted caches cannot + # have their names reused as long as the original database records + # are there. + #soft-delete-caches = false; + + # Whether to require fully uploading a NAR if it exists in the global cache. + # + # If set to false, simply knowing the NAR hash is enough for + # an uploader to gain access to an existing NAR in the global + # cache. + #require-proof-of-possession = true; + + # Database connection + database = { + # Connection URL + # + # For production use it's recommended to use PostgreSQL. + # url = "postgresql:///atticd:password@127.0.0.1:5432/atticd"; + url = "postgresql:///atticd?host=/run/postgresql"; + + # Whether to enable sending on periodic heartbeat queries + # + # If enabled, a heartbeat query will be sent every minute + #heartbeat = false; + }; + + # File storage configuration + storage = { + # Storage type + # + # Can be "local" or "s3". + type = "s3"; + + # ## Local storage + + # The directory to store all files under + # path = "%storage_path%"; + + # ## S3 Storage (set type to "s3" and uncomment below) + + # The AWS region + region = "garage"; + + # The name of the bucket + bucket = "attic"; + + # Custom S3 endpoint + # + # Set this if you are using an S3-compatible object storage (e.g., Minio). + endpoint = config.fediversity.garage.api.url; + + # Credentials + # + # If unset, the credentials are read from the `AWS_ACCESS_KEY_ID` and + # `AWS_SECRET_ACCESS_KEY` environment variables. + # storage.credentials = { + # access_key_id = ""; + # secret_access_key = ""; + # }; + }; + + # Data chunking + # + # Warning: If you change any of the values here, it will be + # difficult to reuse existing chunks for newly-uploaded NARs + # since the cutpoints will be different. As a result, the + # deduplication ratio will suffer for a while after the change. + chunking = { + # The minimum NAR size to trigger chunking + # + # If 0, chunking is disabled entirely for newly-uploaded NARs. + # If 1, all NARs are chunked. + nar-size-threshold = 65536; # chunk files that are 64 KiB or larger + + # The preferred minimum size of a chunk, in bytes + min-size = 16384; # 16 KiB + + # The preferred average size of a chunk, in bytes + avg-size = 65536; # 64 KiB + + # The preferred maximum size of a chunk, in bytes + max-size = 262144; # 256 KiB + }; + + # Compression + compression = { + # Compression type + # + # Can be "none", "brotli", "zstd", or "xz" + type = "zstd"; + + # Compression level + #level = 8; + }; + + # Garbage collection + garbage-collection = { + # The frequency to run garbage collection at + # + # By default it's 12 hours. You can use natural language + # to specify the interval, like "1 day". + # + # If zero, automatic garbage collection is disabled, but + # it can still be run manually with `atticd --mode garbage-collector-once`. + interval = "12 hours"; + + # Default retention period + # + # Zero (default) means time-based garbage-collection is + # disabled by default. You can enable it on a per-cache basis. + #default-retention-period = "6 months"; + }; + + jwt = { + # WARNING: Changing _anything_ in this section will break any existing + # tokens. If you need to regenerate them, ensure that you use the the + # correct secret and include the `iss` and `aud` claims. + + # JWT `iss` claim + # + # Set this to the JWT issuer that you want to validate. + # If this is set, all received JWTs will validate that the `iss` claim + # matches this value. + #token-bound-issuer = "some-issuer"; + + # JWT `aud` claim + # + # Set this to the JWT audience(s) that you want to validate. + # If this is set, all received JWTs will validate that the `aud` claim + # contains at least one of these values. + #token-bound-audiences = ["some-audience1", "some-audience2"]; + }; + + # jwt.signing = { + # # JWT RS256 secret key + # # + # # Set this to the base64-encoded private half of an RSA PEM PKCS1 key. + # # TODO + # # You can also set it via the `ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64` + # # environment variable. + # token-rs256-secret-base64 = "%token_rs256_secret_base64%"; + + # # JWT HS256 secret key + # # + # # Set this to the base64-encoded HMAC secret key. + # # You can also set it via the `ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64` + # # environment variable. + # #token-hs256-secret-base64 = ""; + # }; + }; + }; + }) + ]; +} diff --git a/services/fediversity/attic/options.nix b/services/fediversity/attic/options.nix new file mode 100644 index 00000000..efb9c690 --- /dev/null +++ b/services/fediversity/attic/options.nix @@ -0,0 +1,14 @@ +{ config, lib, ... }: + +{ + options.fediversity.attic = + (import ../sharedOptions.nix { + inherit config lib; + serviceName = "attic"; + serviceDocName = "Attic Nix Cache server"; + }) + // + + { + }; +}