Compare commits

..

51 commits

Author SHA1 Message Date
Nicolas Jeannerod b1a5e16432
Add pre-commit hooks for formatting and dead code 2024-11-11 17:39:20 +01:00
Nicolas Jeannerod 661f81b3f9
Cleanup dead code 2024-11-11 17:28:35 +01:00
Nicolas Jeannerod 7007da1775
Format everything, RFC-style 2024-11-11 17:25:42 +01:00
Nicolas Jeannerod 49473c43c8
Proxy Peertube behind Nginx 2024-11-11 17:10:58 +01:00
Nicolas Jeannerod 4f8ba4bf3c
Require secrets file also when on metal 2024-11-11 17:10:44 +01:00
Nicolas Jeannerod 8e03b4b34e
Fix typo 2024-11-11 16:36:33 +01:00
Nicolas Jeannerod c1dcdfe493
Open port 80, necessary for ACME 2024-11-11 16:34:24 +01:00
Nicolas Jeannerod f53a27baee
Number of cores also when on metal 2024-11-11 16:16:27 +01:00
Nicolas Jeannerod 2d522f51f5
Support installing host keys in the installer 2024-11-08 17:35:25 +01:00
Nicolas Jeannerod f04b71047c
Slight rework of the installer 2024-11-07 18:36:43 +01:00
Nicolas Jeannerod cd194f818d
Turn off the machine once if install is successful 2024-11-07 12:02:09 +01:00
Nicolas Jeannerod 007c168081 Fix Mastodon/Garage test 2024-10-30 19:44:07 +00:00
Nicolas Jeannerod fb342b02fb Also forward SSH port 2024-10-30 18:38:39 +00:00
Nicolas Jeannerod 96acf1f10d Use recommended proxy settings for Garage 2024-10-30 18:37:45 +00:00
Nicolas Jeannerod e299978508 Avoid clashes of security.acme.defaults options 2024-10-30 18:37:06 +00:00
Nicolas Jeannerod 0b5e3ca40e
Bump Taeer's nixpkgs 2024-10-29 17:13:51 +01:00
Nicolas Jeannerod 1de8f5bc17 Some fixes for Pixelfed on metal (#27) 2024-10-29 17:09:19 +01:00
Taeer Bar-Yam b36166ccc0 fix test to not use ACME/SSL (again) 2024-10-01 17:08:09 -04:00
Nicolas Jeannerod 4c8d380e9e
Proxy all buckets that have website = true 2024-10-01 18:18:47 +02:00
Nicolas Jeannerod 247a4258b2
No certificate for Garage web root domain 2024-10-01 18:04:53 +02:00
Nicolas Jeannerod be756ab8d3
Faster compression and note on isoName 2024-10-01 13:29:06 +02:00
Nicolas Jeannerod dd9b481b78
Expose mkInstaller 2024-10-01 13:14:56 +02:00
Nicolas Jeannerod 3cfc4370f7 Add missing module in tests 2024-10-01 09:40:38 +00:00
Nicolas Jeannerod e9b5de893d Create automatic installation ISOs (#26)
Co-authored-by: Taeer Bar-Yam <taeer.bar-yam@moduscreate.com>
Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
Reviewed-on: Fediversity/simple-nixos-fediverse#26
Co-authored-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
Co-committed-by: Nicolas “Niols” Jeannerod <nicolas.jeannerod@moduscreate.com>
2024-10-01 10:02:01 +02:00
Nicolas Jeannerod 7b36774b80
We are way past that! 2024-09-27 11:48:49 +02:00
Taeer Bar-Yam 4da997b3af fix frivolous errors in garage test 2024-09-26 01:41:06 -04:00
Taeer Bar-Yam fa53ecac53 fix the overlay 2024-09-25 11:25:21 -04:00
Taeer Bar-Yam d910dfe788 take bleeding edge pixelfed 2024-09-25 00:40:53 -04:00
Nicolas Jeannerod b461a44707
Not localhost 2024-09-24 16:59:37 +02:00
Nicolas Jeannerod fc18582a1b
Make Garage API domain be localhost 2024-09-24 16:42:53 +02:00
Nicolas Jeannerod e6b58b656b
Remove SSL in Garage VM 2024-09-24 14:56:33 +02:00
Nicolas Jeannerod bf303ff1d1
Remove SSL in VM 2024-09-24 14:52:13 +02:00
Nicolas Jeannerod a600829d56
s/port/internalPort 2024-09-24 14:42:18 +02:00
Nicolas Jeannerod 042cb2d517
Move Garage VM stuff out of main file 2024-09-24 14:40:35 +02:00
Nicolas Jeannerod 050042d255
domainForBucket 2024-09-24 14:23:29 +02:00
Nicolas Jeannerod 6b45256839
s/urlFor/urlForBucket 2024-09-24 14:17:56 +02:00
Taeer Bar-Yam 51a294a659 acme fixup 2 2024-09-23 12:39:55 -04:00
Taeer Bar-Yam 2116ac6b27 acme fixup 2024-09-23 12:39:15 -04:00
Taeer Bar-Yam 3e4b486921 httpS 2024-09-23 12:22:40 -04:00
Taeer Bar-Yam db39623eeb ADD http:// to proxypass 2024-09-23 12:18:22 -04:00
Taeer Bar-Yam ffb941687a remove http:// from nginx server name 2024-09-23 12:14:40 -04:00
Taeer Bar-Yam 2657e2130f mv {,internal}port 2024-09-23 12:11:04 -04:00
Taeer Bar-Yam ca8310dce3 had two 'cfg's. changed one to 'fedicfg' 2024-09-23 12:09:16 -04:00
Taeer Bar-Yam e093632222 ; 2024-09-23 11:58:49 -04:00
Taeer Bar-Yam 2501c480fb proxy garage web to port 80 2024-09-23 11:55:54 -04:00
Nicolas Jeannerod 011f166fd3
Exceptionally use non-staging LetsEncrypt servers 2024-09-20 18:55:00 +02:00
Nicolas Jeannerod 3bb9569eb4
ACME 2024-09-20 18:51:21 +02:00
Nicolas Jeannerod 6323e0adc8
Also open HTTPS port 2024-09-20 18:44:47 +02:00
Nicolas Jeannerod 55a6377b12
Ignore errors of garage key import 2024-09-20 18:39:32 +02:00
Nicolas Jeannerod 9be8232083
[HACK] comment out virtualisation 2024-09-20 18:25:21 +02:00
Nicolas Jeannerod c9665b927f
Move stuff from pixelfed-vm to pixelfed 2024-09-20 17:56:40 +02:00
20 changed files with 1133 additions and 534 deletions

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ result*
output output
todo todo
/.pre-commit-config.yaml

View file

@ -46,6 +46,26 @@ NOTE: it sometimes takes a while for the services to start up, and in the meanti
```bash ```bash
pixelfed-manage user:create --name=test --username=test --email=test@test.com --password=testtest --confirm_email=1 pixelfed-manage user:create --name=test --username=test --email=test@test.com --password=testtest --confirm_email=1
``` ```
# Building an installer image
Build an installer image for the desired configuration, e.g. for `peertube`:
```bash
nix build .#installers.peertube
```
Upload the image in `./result` to Proxmox when creating a VM.
Booting the image will format the disk and install NixOS with the desired configuration.
# Deploying an updated machine configuration
> TODO: There is currently no way to specify an actual target machine by name.
Assuming you have SSH configuration with access to the remote `root` user stored for a machine called e.g. `peertube`, deploy the configuration by the same name:
```bash
nix run .#deploy.peertube
```
## debugging notes ## debugging notes

13
deploy.nix Normal file
View file

@ -0,0 +1,13 @@
{ writeShellApplication }:
name: _config:
writeShellApplication {
name = "deploy";
text = ''
result="$(nix build --print-out-paths ${./.}#nixosConfigurations#${name} --eval-store auto --store ssh-ng://${name})"
# shellcheck disable=SC2087
ssh ${name} << EOF
nix-env -p /nix/var/nix/profiles/system --set "$result"
"$result"/bin/switch-to-configuration switch
EOF
'';
}

36
disk-layout.nix Normal file
View file

@ -0,0 +1,36 @@
{ ... }:
{
disko.devices.disk.main = {
device = "/dev/sda";
type = "disk";
content = {
type = "gpt";
partitions = {
MBR = {
priority = 0;
size = "1M";
type = "EF02";
};
ESP = {
priority = 1;
size = "500M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
priority = 2;
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
}

View file

@ -2,10 +2,11 @@
let let
inherit (builtins) toString; inherit (builtins) toString;
inherit (lib) mkOption mkEnableOption; inherit (lib) mkOption mkEnableOption mkForce;
inherit (lib.types) types; inherit (lib.types) types;
in { in
{
imports = [ imports = [
./garage.nix ./garage.nix
./mastodon.nix ./mastodon.nix
@ -31,9 +32,27 @@ in {
pixelfed.enable = mkEnableOption "default Fediversity Pixelfed configuration"; pixelfed.enable = mkEnableOption "default Fediversity Pixelfed configuration";
peertube.enable = mkEnableOption "default Fediversity PeerTube configuration"; peertube.enable = mkEnableOption "default Fediversity PeerTube configuration";
temp = mkOption {
description = "options that are only used while developing; should be removed eventually";
default = { };
type = types.submodule {
options = {
cores = mkOption {
description = "number of cores; should be obtained from NixOps4";
type = types.int;
};
peertubeSecretsFile = mkOption {
description = "should it be provided by NixOps4? or maybe we should just ask for a main secret from which to derive all the others?";
type = types.path;
};
};
};
};
internal = mkOption { internal = mkOption {
description = "options that are only meant to be used internally; change at your own risk"; description = "options that are only meant to be used internally; change at your own risk";
default = {}; default = { };
type = types.submodule { type = types.submodule {
options = { options = {
garage = { garage = {
@ -64,17 +83,17 @@ in {
type = types.str; type = types.str;
default = "web.garage.${config.fediversity.domain}"; default = "web.garage.${config.fediversity.domain}";
}; };
port = mkOption { internalPort = mkOption {
type = types.int; type = types.int;
default = 3902; default = 3902;
}; };
rootDomainAndPort = mkOption { domainForBucket = mkOption {
type = types.str;
default = "${config.fediversity.internal.garage.web.rootDomain}:${toString config.fediversity.internal.garage.web.port}";
};
urlFor = mkOption {
type = types.functionTo types.str; type = types.functionTo types.str;
default = bucket: "http://${bucket}.${config.fediversity.internal.garage.web.rootDomainAndPort}"; default = bucket: "${bucket}.${config.fediversity.internal.garage.web.rootDomain}";
};
urlForBucket = mkOption {
type = types.functionTo types.str;
default = bucket: "http://${config.fediversity.internal.garage.web.domainForBucket bucket}";
}; };
}; };
}; };
@ -89,7 +108,7 @@ in {
}; };
mastodon.domain = mkOption { mastodon.domain = mkOption {
type = types.str; type = types.str;
default = "mastdodon.${config.fediversity.domain}"; default = "mastodon.${config.fediversity.domain}";
}; };
peertube.domain = mkOption { peertube.domain = mkOption {
type = types.str; type = types.str;
@ -100,4 +119,19 @@ in {
}; };
}; };
}; };
config = {
## FIXME: This should clearly go somewhere else; and we should have a
## `staging` vs. `production` setting somewhere.
security.acme = {
acceptTerms = true;
defaults.email = "nicolas.jeannerod+fediversity@moduscreate.com";
# defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory";
};
## NOTE: For a one-machine deployment, this removes the need to provide an
## `s3.garage.<domain>` domain. However, this will quickly stop working once
## we go to multi-machines deployment.
fediversity.internal.garage.api.domain = mkForce "s3.garage.localhost";
};
} }

View file

@ -8,25 +8,49 @@ let
in in
# TODO: expand to a multi-machine setup # TODO: expand to a multi-machine setup
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
inherit (builtins) toString; inherit (builtins) toString;
inherit (lib) types mkOption mkEnableOption optionalString concatStringsSep; inherit (lib)
types
mkOption
mkEnableOption
optionalString
concatStringsSep
;
inherit (lib.strings) escapeShellArg; inherit (lib.strings) escapeShellArg;
inherit (lib.attrsets) filterAttrs mapAttrs';
cfg = config.services.garage; cfg = config.services.garage;
fedicfg = config.fediversity.internal.garage;
concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset); concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset);
ensureBucketScriptFn = bucket: { website, aliases, corsRules }: ensureBucketScriptFn =
bucket:
{
website,
aliases,
corsRules,
}:
let let
bucketArg = escapeShellArg bucket; bucketArg = escapeShellArg bucket;
corsRulesJSON = escapeShellArg (builtins.toJSON { corsRulesJSON = escapeShellArg (
CORSRules = [{ builtins.toJSON {
CORSRules = [
{
AllowedHeaders = corsRules.allowedHeaders; AllowedHeaders = corsRules.allowedHeaders;
AllowedMethods = corsRules.allowedMethods; AllowedMethods = corsRules.allowedMethods;
AllowedOrigins = corsRules.allowedOrigins; AllowedOrigins = corsRules.allowedOrigins;
}]; }
}); ];
in '' }
);
in
''
# garage bucket info tells us if the bucket already exists # garage bucket info tells us if the bucket already exists
garage bucket info ${bucketArg} || garage bucket create ${bucketArg} garage bucket info ${bucketArg} || garage bucket create ${bucketArg}
@ -35,24 +59,41 @@ let
garage bucket website --allow ${bucketArg} garage bucket website --allow ${bucketArg}
''} ''}
${concatStringsSep "\n" (map (alias: '' ${concatStringsSep "\n" (
map (alias: ''
garage bucket alias ${bucketArg} ${escapeShellArg alias} garage bucket alias ${bucketArg} ${escapeShellArg alias}
'') aliases)} '') aliases
)}
${optionalString corsRules.enable '' ${optionalString corsRules.enable ''
garage bucket allow --read --write --owner ${bucketArg} --key tmp garage bucket allow --read --write --owner ${bucketArg} --key tmp
# TODO: endpoin-url should not be hard-coded # TODO: endpoin-url should not be hard-coded
aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url ${config.fediversity.internal.garage.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON} 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 garage bucket deny --read --write --owner ${bucketArg} --key tmp
''} ''}
''; '';
ensureBucketsScript = concatMapAttrs ensureBucketScriptFn cfg.ensureBuckets; ensureBucketsScript = concatMapAttrs ensureBucketScriptFn cfg.ensureBuckets;
ensureAccessScriptFn = key: bucket: { read, write, owner }: '' ensureAccessScriptFn =
key: bucket:
{
read,
write,
owner,
}:
''
garage bucket allow ${optionalString read "--read"} ${optionalString write "--write"} ${optionalString owner "--owner"} \ garage bucket allow ${optionalString read "--read"} ${optionalString write "--write"} ${optionalString owner "--owner"} \
${escapeShellArg bucket} --key ${escapeShellArg key} ${escapeShellArg bucket} --key ${escapeShellArg key}
''; '';
ensureKeyScriptFn = key: {id, secret, ensureAccess}: '' ensureKeyScriptFn =
garage key import --yes -n ${escapeShellArg key} ${escapeShellArg id} ${escapeShellArg secret} key:
{
id,
secret,
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} ${escapeShellArg id} ${escapeShellArg secret} || :
${concatMapAttrs (ensureAccessScriptFn key) ensureAccess} ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
''; '';
ensureKeysScript = concatMapAttrs ensureKeyScriptFn cfg.ensureKeys; ensureKeysScript = concatMapAttrs ensureKeyScriptFn cfg.ensureKeys;
@ -63,7 +104,8 @@ in
options = { options = {
services.garage = { services.garage = {
ensureBuckets = mkOption { ensureBuckets = mkOption {
type = types.attrsOf (types.submodule { type = types.attrsOf (
types.submodule {
options = { options = {
website = mkOption { website = mkOption {
type = types.bool; type = types.bool;
@ -74,27 +116,29 @@ in
enable = mkEnableOption "CORS Rules"; enable = mkEnableOption "CORS Rules";
allowedHeaders = mkOption { allowedHeaders = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [ ];
}; };
allowedMethods = mkOption { allowedMethods = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [ ];
}; };
allowedOrigins = mkOption { allowedOrigins = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [ ];
}; };
}; };
aliases = mkOption { aliases = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [ ];
}; };
}; };
}); }
default = {}; );
default = { };
}; };
ensureKeys = mkOption { ensureKeys = mkOption {
type = types.attrsOf (types.submodule { type = types.attrsOf (
types.submodule {
# TODO: these should be managed as secrets, not in the nix store # TODO: these should be managed as secrets, not in the nix store
options = { options = {
id = mkOption { id = mkOption {
@ -106,7 +150,8 @@ in
# TODO: assert at least one of these is true # TODO: assert at least one of these is true
# NOTE: this currently needs to be done at the top level module # NOTE: this currently needs to be done at the top level module
ensureAccess = mkOption { ensureAccess = mkOption {
type = types.attrsOf (types.submodule { type = types.attrsOf (
types.submodule {
options = { options = {
read = mkOption { read = mkOption {
type = types.bool; type = types.bool;
@ -121,36 +166,26 @@ in
default = false; default = false;
}; };
}; };
}); }
default = []; );
default = [ ];
}; };
}; };
}); }
default = {}; );
default = { };
}; };
}; };
}; };
config = lib.mkIf config.fediversity.enable { config = lib.mkIf config.fediversity.enable {
virtualisation.diskSize = 2048; environment.systemPackages = [
virtualisation.forwardPorts = [ pkgs.minio-client
{ pkgs.awscli
from = "host";
host.port = config.fediversity.internal.garage.rpc.port;
guest.port = config.fediversity.internal.garage.rpc.port;
}
{
from = "host";
host.port = config.fediversity.internal.garage.web.port;
guest.port = config.fediversity.internal.garage.web.port;
}
]; ];
environment.systemPackages = [ pkgs.minio-client pkgs.awscli ];
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
config.fediversity.internal.garage.rpc.port fedicfg.rpc.port
config.fediversity.internal.garage.web.port
]; ];
services.garage = { services.garage = {
enable = true; enable = true;
@ -160,30 +195,59 @@ in
# TODO: use a secret file # TODO: use a secret file
rpc_secret = "d576c4478cc7d0d94cfc127138cbb82018b0155c037d1c827dfb6c36be5f6625"; rpc_secret = "d576c4478cc7d0d94cfc127138cbb82018b0155c037d1c827dfb6c36be5f6625";
# TODO: why does this have to be set? is there not a sensible default? # TODO: why does this have to be set? is there not a sensible default?
rpc_bind_addr = "[::]:${toString config.fediversity.internal.garage.rpc.port}"; rpc_bind_addr = "[::]:${toString fedicfg.rpc.port}";
rpc_public_addr = "[::1]:${toString config.fediversity.internal.garage.rpc.port}"; rpc_public_addr = "[::1]:${toString fedicfg.rpc.port}";
s3_api.api_bind_addr = "[::]:${toString config.fediversity.internal.garage.api.port}"; s3_api.api_bind_addr = "[::]:${toString fedicfg.api.port}";
s3_web.bind_addr = "[::]:${toString config.fediversity.internal.garage.web.port}"; s3_web.bind_addr = "[::]:${toString fedicfg.web.internalPort}";
s3_web.root_domain = ".${config.fediversity.internal.garage.web.rootDomain}"; s3_web.root_domain = ".${fedicfg.web.rootDomain}";
index = "index.html"; index = "index.html";
s3_api.s3_region = "garage"; s3_api.s3_region = "garage";
s3_api.root_domain = ".${config.fediversity.internal.garage.api.domain}"; s3_api.root_domain = ".${fedicfg.api.domain}";
}; };
}; };
## 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;
'';
};
};
in
mapAttrs' (bucket: _: {
name = fedicfg.web.domainForBucket bucket;
inherit value;
}) (filterAttrs (_: { website, ... }: website) cfg.ensureBuckets);
systemd.services.ensure-garage = { systemd.services.ensure-garage = {
after = [ "garage.service" ]; after = [ "garage.service" ];
wantedBy = [ "garage.service" ]; wantedBy = [ "garage.service" ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
}; };
path = [ cfg.package pkgs.perl pkgs.awscli ]; path = [
cfg.package
pkgs.perl
pkgs.awscli
];
script = '' script = ''
set -xeuo pipefail set -xeuo pipefail
# Give Garage time to start up by waiting until somethings speaks HTTP # Give Garage time to start up by waiting until somethings speaks HTTP
# behind Garage's API URL. # behind Garage's API URL.
until ${pkgs.curl}/bin/curl -sio /dev/null ${config.fediversity.internal.garage.api.url}; do sleep 1; done until ${pkgs.curl}/bin/curl -sio /dev/null ${fedicfg.api.url}; do sleep 1; done
# XXX: this is very sensitive to being a single instance # XXX: this is very sensitive to being a single instance
# (doing the bare minimum to get garage up and running) # (doing the bare minimum to get garage up and running)
@ -197,7 +261,8 @@ in
# XXX: this is a hack because we want to write to the buckets here but we're not guaranteed any access keys # 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: generate this key here rather than using a well-known key
garage key import --yes -n tmp ${snakeoil_key.id} ${snakeoil_key.secret} # 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_ACCESS_KEY_ID=${snakeoil_key.id};
export AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret}; export AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret};

View file

@ -5,7 +5,11 @@ let
}; };
in in
{ config, lib, pkgs, ... }: {
config,
lib,
...
}:
lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) { lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
#### garage setup #### garage setup
@ -46,7 +50,7 @@ lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
AWS_ACCESS_KEY_ID = snakeoil_key.id; AWS_ACCESS_KEY_ID = snakeoil_key.id;
AWS_SECRET_ACCESS_KEY = snakeoil_key.secret; AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
S3_PROTOCOL = "http"; S3_PROTOCOL = "http";
S3_HOSTNAME = config.fediversity.internal.garage.web.rootDomainAndPort; S3_HOSTNAME = config.fediversity.internal.garage.web.rootDomain;
# by default it tries to use "<S3_HOSTNAME>/<S3_BUCKET>" # by default it tries to use "<S3_HOSTNAME>/<S3_BUCKET>"
S3_ALIAS_HOST = "${S3_BUCKET}.${S3_HOSTNAME}"; S3_ALIAS_HOST = "${S3_BUCKET}.${S3_HOSTNAME}";
# SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/ # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
@ -57,8 +61,11 @@ lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
#### mastodon setup #### mastodon setup
# open up access to the mastodon web interface # open up access to the mastodon web interface. 80 is necessary if only for ACME
networking.firewall.allowedTCPPorts = [ 443 ]; networking.firewall.allowedTCPPorts = [
80
443
];
services.mastodon = { services.mastodon = {
enable = true; enable = true;
@ -66,6 +73,10 @@ lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
localDomain = config.fediversity.internal.mastodon.domain; localDomain = config.fediversity.internal.mastodon.domain;
configureNginx = true; configureNginx = true;
# from the documentation: recommended is the amount of your CPU cores minus
# one. but it also must be a positive integer
streamingProcesses = lib.max 1 (config.fediversity.temp.cores - 1);
# TODO: configure a mailserver so this works # TODO: configure a mailserver so this works
smtp = { smtp = {
fromAddress = "noreply@${config.fediversity.internal.mastodon.domain}"; fromAddress = "noreply@${config.fediversity.internal.mastodon.domain}";

View file

@ -5,10 +5,17 @@ let
}; };
in in
{ config, lib, pkgs, ... }: {
config,
lib,
...
}:
lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) { lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
networking.firewall.allowedTCPPorts = [ 80 9000 ]; networking.firewall.allowedTCPPorts = [
80
443
];
services.garage = { services.garage = {
ensureBuckets = { ensureBuckets = {
@ -59,7 +66,8 @@ lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
# TODO: in most of nixpkgs, these are true by default. upstream that unless there's a good reason not to. # TODO: in most of nixpkgs, these are true by default. upstream that unless there's a good reason not to.
redis.createLocally = true; redis.createLocally = true;
database.createLocally = true; database.createLocally = true;
configureNginx = true;
secrets.secretsFile = config.fediversity.temp.peertubeSecretsFile;
settings = { settings = {
object_storage = { object_storage = {
@ -74,17 +82,17 @@ lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
web_videos = rec { web_videos = rec {
bucket_name = "peertube-videos"; bucket_name = "peertube-videos";
prefix = ""; prefix = "";
base_url = config.fediversity.internal.garage.web.urlFor bucket_name; base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
}; };
videos = rec { videos = rec {
bucket_name = "peertube-videos"; bucket_name = "peertube-videos";
prefix = ""; prefix = "";
base_url = config.fediversity.internal.garage.web.urlFor bucket_name; base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
}; };
streaming_playlists = rec { streaming_playlists = rec {
bucket_name = "peertube-playlists"; bucket_name = "peertube-playlists";
prefix = ""; prefix = "";
base_url = config.fediversity.internal.garage.web.urlFor bucket_name; base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
}; };
}; };
}; };
@ -94,4 +102,12 @@ lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
AWS_ACCESS_KEY_ID=${snakeoil_key.id} AWS_ACCESS_KEY_ID=${snakeoil_key.id}
AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret} AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret}
''; '';
## Proxying through Nginx
services.peertube.configureNginx = true;
services.nginx.virtualHosts.${config.services.peertube.localDomain} = {
forceSSL = true;
enableACME = true;
};
} }

View file

@ -5,7 +5,12 @@ let
}; };
in in
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) { lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
services.garage = { services.garage = {
@ -38,16 +43,37 @@ lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
services.pixelfed = { services.pixelfed = {
enable = true; enable = true;
domain = config.fediversity.internal.pixelfed.domain; domain = config.fediversity.internal.pixelfed.domain;
# TODO: secrets management!!!
secretFile = pkgs.writeText "secrets.env" ''
APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
'';
## Taeer feels like this way of configuring Nginx is odd; there should
## instead be a `services.pixefed.nginx.enable` option and the actual Nginx
## configuration should be in `services.nginx`. See eg. `pretix`.
##
## TODO: If that indeed makes sense, upstream.
nginx = {
forceSSL = true;
enableACME = true;
# locations."/public/".proxyPass = "${config.fediversity.internal.garage.web.urlForBucket "pixelfed"}/public/";
};
}; };
services.pixelfed.settings = { services.pixelfed.settings = {
## NOTE: This depends on the targets, eg. universities might want control
## over who has an account. We probably want a universal
## `fediversity.openRegistration` option.
OPEN_REGISTRATION = true;
# DANGEROUSLY_SET_FILESYSTEM_DRIVER = "s3"; # DANGEROUSLY_SET_FILESYSTEM_DRIVER = "s3";
FILESYSTEM_CLOUD = "s3"; FILESYSTEM_CLOUD = "s3";
PF_ENABLE_CLOUD = true; PF_ENABLE_CLOUD = true;
AWS_ACCESS_KEY_ID = snakeoil_key.id; AWS_ACCESS_KEY_ID = snakeoil_key.id;
AWS_SECRET_ACCESS_KEY = snakeoil_key.secret; AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
AWS_DEFAULT_REGION = "garage"; AWS_DEFAULT_REGION = "garage";
AWS_URL = config.fediversity.internal.garage.web.urlFor "pixelfed"; AWS_URL = config.fediversity.internal.garage.web.urlForBucket "pixelfed";
AWS_BUCKET = "pixelfed"; AWS_BUCKET = "pixelfed";
AWS_ENDPOINT = config.fediversity.internal.garage.api.url; AWS_ENDPOINT = config.fediversity.internal.garage.api.url;
AWS_USE_PATH_STYLE_ENDPOINT = false; AWS_USE_PATH_STYLE_ENDPOINT = false;
@ -59,4 +85,8 @@ lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
after = [ "ensure-garage.service" ]; after = [ "ensure-garage.service" ];
}; };
networking.firewall.allowedTCPPorts = [
80
443
];
} }

View file

@ -1,12 +1,151 @@
{ {
"nodes": { "nodes": {
"disko": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1727347829,
"narHash": "sha256-y7cW6TjJKy+tu7efxeWI6lyg4VVx/9whx+OmrhmRShU=",
"owner": "nix-community",
"repo": "disko",
"rev": "1879e48907c14a70302ff5d0539c3b9b6f97feaa",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "disko",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": "nixpkgs_2",
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1730814269,
"narHash": "sha256-fWPHyhYE6xvMI1eGY3pwBTq85wcy1YXqdzTZF+06nOg=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "d70155fdc00df4628446352fc58adc640cd705c2",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1723726852, "lastModified": 1725194671,
"narHash": "sha256-lRzlx4fPRtzA+dgz9Rh4WK5yAW3TsAXx335DQqxY2XY=", "narHash": "sha256-tLGCFEFTB5TaOKkpfw3iYT9dnk4awTP/q4w+ROpMfuw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b833ff01a0d694b910daca6e2ff4a3f26dee478c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-latest": {
"locked": {
"lastModified": 1727220152,
"narHash": "sha256-6ezRTVBZT25lQkvaPrfJSxYLwqcbNWm6feD/vG1FO0o=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "24959f933187217890b206788a85bfa73ba75949",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1730741070,
"narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1730768919,
"narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1730137230,
"narHash": "sha256-0kW6v0alzWIc/Dc/DoVZ7A9qNScv77bj/zYTKI67HZM=",
"owner": "radvendii", "owner": "radvendii",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9286249a1673cf5b14a4793e22dd44b70cb69a0d", "rev": "df815998652a1d00ce7c059a1e5ef7d7c0548c90",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -16,9 +155,30 @@
"type": "github" "type": "github"
} }
}, },
"pixelfed": {
"flake": false,
"locked": {
"lastModified": 1719823820,
"narHash": "sha256-CKjqnxp7p2z/13zfp4HQ1OAmaoUtqBKS6HFm6TV8Jwg=",
"owner": "pixelfed",
"repo": "pixelfed",
"rev": "4c245cf429330d01fcb8ebeb9aa8c84a9574a645",
"type": "github"
},
"original": {
"owner": "pixelfed",
"ref": "v0.12.3",
"repo": "pixelfed",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs" "disko": "disko",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_3",
"nixpkgs-latest": "nixpkgs-latest",
"pixelfed": "pixelfed"
} }
} }
}, },

130
flake.nix
View file

@ -1,77 +1,108 @@
{ {
description = "Testing mastodon configurations";
inputs = { inputs = {
nixpkgs.url = "github:radvendii/nixpkgs/nixos_rebuild_tests"; nixpkgs.url = "github:radvendii/nixpkgs/nixos_rebuild_tests";
nixpkgs-latest.url = "github:nixos/nixpkgs";
git-hooks.url = "github:cachix/git-hooks.nix";
pixelfed = {
url = "github:pixelfed/pixelfed?ref=v0.12.3";
flake = false;
};
disko.url = "github:nix-community/disko";
}; };
outputs = inputs@{ self, nixpkgs }: outputs =
{
self,
nixpkgs,
nixpkgs-latest,
git-hooks,
pixelfed,
disko,
}:
let let
system = "x86_64-linux"; system = "x86_64-linux";
lib = nixpkgs.lib;
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
in { pkgsLatest = nixpkgs-latest.legacyPackages.${system};
bleedingFediverseOverlay = (
packages.${system} = { _: _: {
pixelfed = pkgs.pixelfed.overrideAttrs (old: { pixelfed = pkgsLatest.pixelfed.overrideAttrs (old: {
src = pixelfed;
patches = (old.patches or [ ]) ++ [ ./fediversity/pixelfed-group-permissions.patch ]; patches = (old.patches or [ ]) ++ [ ./fediversity/pixelfed-group-permissions.patch ];
}); });
}; ## TODO: give mastodon, peertube the same treatment
}
);
in
{
nixosModules = { nixosModules = {
## Fediversity modules ## Bleeding-edge fediverse packages
fediversity = { pkgs, ... }: { bleedingFediverse = {
imports = [ ./fediversity ]; nixpkgs.overlays = [ bleedingFediverseOverlay ];
services.pixelfed.package = self.packages.${pkgs.stdenv.hostPlatform.system}.pixelfed;
}; };
## Fediversity modules
fediversity = import ./fediversity;
## VM-specific modules ## VM-specific modules
interactive-vm = { interactive-vm = import ./vm/interactive-vm.nix;
imports = [ garage-vm = import ./vm/garage-vm.nix;
./vm/interactive-vm.nix mastodon-vm = import ./vm/mastodon-vm.nix;
self.nixosModules.fediversity peertube-vm = import ./vm/peertube-vm.nix;
]; pixelfed-vm = import ./vm/pixelfed-vm.nix;
};
mastodon-vm = { disk-layout = import ./disk-layout.nix;
imports = [
./vm/mastodon-vm.nix
self.nixosModules.fediversity
];
};
peertube-vm = {
imports = [
./vm/peertube-vm.nix
self.nixosModules.fediversity
];
};
pixelfed-vm = {
imports = [
./vm/pixelfed-vm.nix
self.nixosModules.fediversity
];
};
}; };
nixosConfigurations = { nixosConfigurations = {
mastodon = nixpkgs.lib.nixosSystem { mastodon = nixpkgs.lib.nixosSystem {
inherit system; inherit system;
modules = with self.nixosModules; [ fediversity interactive-vm mastodon-vm ]; modules = with self.nixosModules; [
disko.nixosModules.default
disk-layout
bleedingFediverse
fediversity
interactive-vm
garage-vm
mastodon-vm
];
}; };
peertube = nixpkgs.lib.nixosSystem { peertube = nixpkgs.lib.nixosSystem {
inherit system; inherit system;
modules = with self.nixosModules; [ fediversity interactive-vm peertube-vm ]; modules = with self.nixosModules; [
disko.nixosModules.default
disk-layout
bleedingFediverse
fediversity
interactive-vm
garage-vm
peertube-vm
];
}; };
pixelfed = nixpkgs.lib.nixosSystem { pixelfed = nixpkgs.lib.nixosSystem {
inherit system; inherit system;
modules = with self.nixosModules; [ fediversity interactive-vm pixelfed-vm ]; modules = with self.nixosModules; [
disko.nixosModules.default
disk-layout
bleedingFediverse
fediversity
interactive-vm
garage-vm
pixelfed-vm
];
}; };
all = nixpkgs.lib.nixosSystem { all = nixpkgs.lib.nixosSystem {
inherit system; inherit system;
modules = with self.nixosModules; [ modules = with self.nixosModules; [
disko.nixosModules.default
disk-layout
bleedingFediverse
fediversity fediversity
interactive-vm interactive-vm
garage-vm
peertube-vm peertube-vm
pixelfed-vm pixelfed-vm
mastodon-vm mastodon-vm
@ -79,15 +110,34 @@
}; };
}; };
## Fully-feature ISO installer
mkInstaller = import ./installer.nix;
installers = lib.mapAttrs (_: config: self.mkInstaller nixpkgs config) self.nixosConfigurations;
deploy =
let
deployCommand = (pkgs.callPackage ./deploy.nix { });
in
lib.mapAttrs (name: config: deployCommand name config) self.nixosConfigurations;
checks.${system} = { checks.${system} = {
mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; }; mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; };
pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; }; pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; };
pre-commit = git-hooks.lib.${system}.run {
src = ./.;
hooks = {
nixfmt-rfc-style.enable = true;
deadnix.enable = true;
};
};
}; };
devShells.${system}.default = pkgs.mkShell { devShells.${system}.default = pkgs.mkShell {
inputs = with pkgs; [ inputs = with pkgs; [
nil nil
]; ];
shellHook = self.checks.${system}.pre-commit.shellHook;
}; };
}; };
} }

61
installer.nix Normal file
View file

@ -0,0 +1,61 @@
/**
Convert a NixOS configuration to one for a minimal installer ISO
WARNING: Running this installer will format the target disk!
*/
{
nixpkgs,
hostKeys ? { },
}:
machine:
let
inherit (builtins) concatStringsSep attrValues mapAttrs;
installer =
{
config,
pkgs,
lib,
...
}:
let
bootstrap = pkgs.writeShellApplication {
name = "bootstrap";
runtimeInputs = with pkgs; [ nixos-install-tools ];
text = ''
${machine.config.system.build.diskoScript}
nixos-install --no-root-password --no-channel-copy --system ${machine.config.system.build.toplevel}
${concatStringsSep "\n" (
attrValues (
mapAttrs (kind: keys: ''
cp ${keys.private} /mnt/etc/ssh/ssh_host_${kind}_key
chmod 600 /mnt/etc/ssh/ssh_host_${kind}_key
cp ${keys.public} /mnt/etc/ssh/ssh_host_${kind}_key.pub
chmod 644 /mnt/etc/ssh/ssh_host_${kind}_key.pub
'') hostKeys
)
)}
poweroff
'';
};
in
{
imports = [
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
];
nixpkgs.hostPlatform = "x86_64-linux";
services.getty.autologinUser = lib.mkForce "root";
programs.bash.loginShellInit = nixpkgs.lib.getExe bootstrap;
isoImage = {
compressImage = false;
squashfsCompression = "lz4";
isoName = lib.mkForce "installer.iso";
## ^^ FIXME: Use a more interesting name or keep the default name and
## use `isoImage.isoName` in the tests.
};
};
in
(nixpkgs.lib.nixosSystem { modules = [ installer ]; }).config.system.build.isoImage

View file

@ -1,11 +1,16 @@
{ pkgs, self }: { pkgs, self }:
let let
lib = pkgs.lib; lib = pkgs.lib;
rebuildableTest = import ./rebuildableTest.nix pkgs;
seleniumScript = pkgs.writers.writePython3Bin "selenium-script" ## FIXME: this binding was not used, but maybe we want a side-effect or something?
# rebuildableTest = import ./rebuildableTest.nix pkgs;
seleniumScript =
pkgs.writers.writePython3Bin "selenium-script"
{ {
libraries = with pkgs.python3Packages; [ selenium ]; libraries = with pkgs.python3Packages; [ selenium ];
} '' }
''
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options from selenium.webdriver.firefox.options import Options
@ -35,9 +40,16 @@ pkgs.nixosTest {
name = "test-mastodon-garage"; name = "test-mastodon-garage";
nodes = { nodes = {
server = { config, ... }: { server =
{ config, ... }:
{
virtualisation.memorySize = lib.mkVMOverride 4096; virtualisation.memorySize = lib.mkVMOverride 4096;
imports = with self.nixosModules; [ mastodon-vm ]; imports = with self.nixosModules; [
bleedingFediverse
fediversity
garage-vm
mastodon-vm
];
# TODO: pair down # TODO: pair down
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
python3 python3
@ -57,7 +69,9 @@ pkgs.nixosTest {
}; };
}; };
testScript = { nodes, ... }: '' testScript =
{ nodes, ... }:
''
import re import re
import time import time
@ -121,8 +135,8 @@ pkgs.nixosTest {
raise Exception("mastodon did not send a content security policy header") raise Exception("mastodon did not send a content security policy header")
csp = csp_match.group(1) csp = csp_match.group(1)
# the img-src content security policy should include the garage server # the img-src content security policy should include the garage server
## TODO: use `nodes.server.fediversity.internal.garage.api.url` same as above, but beware of escaping the regex. ## TODO: use `nodes.server.fediversity.internal.garage.api.url` same as above, but beware of escaping the regex. Be careful with port 80 though.
garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost:3902.*", csp) garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost.*", csp)
if garage_csp is None: if garage_csp is None:
raise Exception("Mastodon's content security policy does not include garage server. image will not be displayed properly on mastodon.") raise Exception("Mastodon's content security policy does not include garage server. image will not be displayed properly on mastodon.")

View file

@ -1,7 +1,9 @@
{ pkgs, self }: { pkgs, self }:
let let
lib = pkgs.lib; lib = pkgs.lib;
rebuildableTest = import ./rebuildableTest.nix pkgs;
## FIXME: this binding was not used but maybe we want a side effect or something?
# rebuildableTest = import ./rebuildableTest.nix pkgs;
email = "test@test.com"; email = "test@test.com";
password = "testtest"; password = "testtest";
@ -50,10 +52,12 @@ let
driver.quit() driver.quit()
''; '';
seleniumScriptPostPicture = pkgs.writers.writePython3Bin "selenium-script-post-picture" seleniumScriptPostPicture =
pkgs.writers.writePython3Bin "selenium-script-post-picture"
{ {
libraries = with pkgs.python3Packages; [ selenium ]; libraries = with pkgs.python3Packages; [ selenium ];
} '' }
''
import os import os
import time import time
${seleniumImports} ${seleniumImports}
@ -93,10 +97,12 @@ let
${seleniumTakeScreenshot "\"/home/selenium/screenshot.png\""} ${seleniumTakeScreenshot "\"/home/selenium/screenshot.png\""}
${seleniumQuit}''; ${seleniumQuit}'';
seleniumScriptGetSrc = pkgs.writers.writePython3Bin "selenium-script-get-src" seleniumScriptGetSrc =
pkgs.writers.writePython3Bin "selenium-script-get-src"
{ {
libraries = with pkgs.python3Packages; [ selenium ]; libraries = with pkgs.python3Packages; [ selenium ];
} '' }
''
${seleniumImports} ${seleniumImports}
${seleniumSetup} ${seleniumSetup}
${seleniumPixelfedLogin} ${seleniumPixelfedLogin}
@ -115,7 +121,9 @@ pkgs.nixosTest {
name = "test-pixelfed-garage"; name = "test-pixelfed-garage";
nodes = { nodes = {
server = { config, ... }: { server =
{ config, ... }:
{
services = { services = {
xserver = { xserver = {
@ -129,14 +137,21 @@ pkgs.nixosTest {
user = "selenium"; user = "selenium";
}; };
}; };
virtualisation.resolution = { x = 1680; y = 1050; }; virtualisation.resolution = {
x = 1680;
y = 1050;
};
virtualisation = { virtualisation = {
memorySize = lib.mkVMOverride 8192; memorySize = lib.mkVMOverride 8192;
cores = 8; cores = 8;
}; };
imports = with self.nixosModules; [ pixelfed-vm ]; imports = with self.nixosModules; [
bleedingFediverse
fediversity
garage-vm
pixelfed-vm
];
# TODO: pair down # TODO: pair down
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
python3 python3
@ -152,6 +167,8 @@ pkgs.nixosTest {
POST_MEDIA = ./fediversity.png; POST_MEDIA = ./fediversity.png;
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id; AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id;
AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.pixelfed.secret; AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.pixelfed.secret;
## without this we get frivolous errors in the logs
MC_REGION = "garage";
}; };
# chrome does not like being run as root # chrome does not like being run as root
users.users.selenium = { users.users.selenium = {
@ -160,7 +177,9 @@ pkgs.nixosTest {
}; };
}; };
testScript = { nodes, ... }: '' testScript =
{ nodes, ... }:
''
import re import re
server.start() server.start()
@ -202,7 +221,7 @@ pkgs.nixosTest {
with subtest("Check that image comes from garage"): with subtest("Check that image comes from garage"):
src = server.succeed("su - selenium -c 'selenium-script-get-src ${email} ${password}'") src = server.succeed("su - selenium -c 'selenium-script-get-src ${email} ${password}'")
if not src.startswith("${nodes.server.fediversity.internal.garage.web.urlFor "pixelfed"}"): if not src.startswith("${nodes.server.fediversity.internal.garage.web.urlForBucket "pixelfed"}"):
raise Exception("image does not come from garage") raise Exception("image does not come from garage")
''; '';
} }

View file

@ -1,9 +1,16 @@
pkgs: test: pkgs: test:
let let
inherit (pkgs.lib) mapAttrsToList concatStringsSep genAttrs mkIf; inherit (pkgs.lib)
mapAttrsToList
concatStringsSep
genAttrs
mkIf
;
inherit (builtins) attrNames; inherit (builtins) attrNames;
interactiveConfig = ({ config, ... }: { interactiveConfig = (
{ config, ... }:
{
# so we can run `nix shell nixpkgs#foo` on the machines # so we can run `nix shell nixpkgs#foo` on the machines
nix.extraOptions = '' nix.extraOptions = ''
extra-experimental-features = nix-command flakes extra-experimental-features = nix-command flakes
@ -20,13 +27,16 @@ let
}; };
virtualisation = mkIf (config.networking.hostName == "jumphost") { virtualisation = mkIf (config.networking.hostName == "jumphost") {
forwardPorts = [{ forwardPorts = [
{
from = "host"; from = "host";
host.port = 2222; host.port = 2222;
guest.port = 22; guest.port = 22;
}]; }
];
}; };
}); }
);
sshConfig = pkgs.writeText "ssh-config" '' sshConfig = pkgs.writeText "ssh-config" ''
Host * Host *
@ -50,10 +60,11 @@ let
# create an association array from machine names to the path to their # create an association array from machine names to the path to their
# configuration in the nix store # configuration in the nix store
declare -A configPaths=(${ declare -A configPaths=(${
concatStringsSep " " concatStringsSep " " (
(mapAttrsToList mapAttrsToList (
(n: v: ''["${n}"]="${v.system.build.toplevel}"'') n: v: ''["${n}"]="${v.system.build.toplevel}"''
rebuildableTest.driverInteractive.nodes) ) rebuildableTest.driverInteractive.nodes
)
}) })
rebuild_one() { rebuild_one() {
@ -113,16 +124,14 @@ let
# we're at it) # we're at it)
rebuildableTest = rebuildableTest =
let let
preOverride = pkgs.nixosTest (test // { preOverride = pkgs.nixosTest (
test
// {
interactive = (test.interactive or { }) // { interactive = (test.interactive or { }) // {
# no need to // with test.interactive.nodes here, since we are iterating # no need to // with test.interactive.nodes here, since we are iterating
# over all of them, and adding back in the config via `imports` # over all of them, and adding back in the config via `imports`
nodes = genAttrs nodes =
( genAttrs (attrNames test.nodes or { } ++ attrNames test.interactive.nodes or { } ++ [ "jumphost" ])
attrNames test.nodes or { } ++
attrNames test.interactive.nodes or { } ++
[ "jumphost" ]
)
(n: { (n: {
imports = [ imports = [
(test.interactive.${n} or { }) (test.interactive.${n} or { })
@ -131,14 +140,20 @@ let
}); });
}; };
# override with test.passthru in case someone wants to overwrite us. # override with test.passthru in case someone wants to overwrite us.
passthru = { inherit rebuildScript sshConfig; } // (test.passthru or { }); passthru = {
}); inherit rebuildScript sshConfig;
} // (test.passthru or { });
}
);
in in
preOverride // { preOverride
// {
driverInteractive = preOverride.driverInteractive.overrideAttrs (old: { driverInteractive = preOverride.driverInteractive.overrideAttrs (old: {
# this comes from runCommand, not mkDerivation, so this is the only # this comes from runCommand, not mkDerivation, so this is the only
# hook we have to override # hook we have to override
buildCommand = old.buildCommand + '' buildCommand =
old.buildCommand
+ ''
ln -s ${sshConfig} $out/ssh-config ln -s ${sshConfig} $out/ssh-config
ln -s ${rebuildScript}/bin/rebuild $out/bin/rebuild ln -s ${rebuildScript}/bin/rebuild $out/bin/rebuild
''; '';
@ -146,4 +161,3 @@ let
}; };
in in
rebuildableTest rebuildableTest

44
vm/garage-vm.nix Normal file
View file

@ -0,0 +1,44 @@
{
lib,
config,
modulesPath,
...
}:
let
inherit (lib) mkVMOverride mapAttrs' filterAttrs;
cfg = config.services.garage;
fedicfg = config.fediversity.internal.garage;
in
{
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
services.nginx.virtualHosts =
let
value = {
forceSSL = mkVMOverride false;
enableACME = mkVMOverride false;
};
in
mapAttrs' (bucket: _: {
name = fedicfg.web.domainForBucket bucket;
inherit value;
}) (filterAttrs (_: { website, ... }: website) cfg.ensureBuckets);
virtualisation.diskSize = 2048;
virtualisation.forwardPorts = [
{
from = "host";
host.port = fedicfg.rpc.port;
guest.port = fedicfg.rpc.port;
}
{
from = "host";
host.port = fedicfg.web.internalPort;
guest.port = fedicfg.web.internalPort;
}
];
}

View file

@ -1,5 +1,6 @@
# customize nixos-rebuild build-vm to be a bit more convenient # customize nixos-rebuild build-vm to be a bit more convenient
{ pkgs, ... }: { { pkgs, ... }:
{
# let us log in # let us log in
users.mutableUsers = false; users.mutableUsers = false;
users.users.root.hashedPassword = ""; users.users.root.hashedPassword = "";
@ -34,7 +35,10 @@
# no graphics. see nixos-shell # no graphics. see nixos-shell
virtualisation = { virtualisation = {
graphics = false; graphics = false;
qemu.consoles = [ "tty0" "hvc0" ]; qemu.consoles = [
"tty0"
"hvc0"
];
qemu.options = [ qemu.options = [
"-serial null" "-serial null"
"-device virtio-serial" "-device virtio-serial"
@ -44,12 +48,19 @@
]; ];
}; };
# we can't forward port 80 or 443, so let's run nginx on a different port # we can't forward port 80 or 443, so let's run nginx on a different port
networking.firewall.allowedTCPPorts = [ 8443 8080 ]; networking.firewall.allowedTCPPorts = [
8443
8080
];
services.nginx.defaultSSLListenPort = 8443; services.nginx.defaultSSLListenPort = 8443;
services.nginx.defaultHTTPListenPort = 8080; services.nginx.defaultHTTPListenPort = 8080;
virtualisation.forwardPorts = [ virtualisation.forwardPorts = [
{
from = "host";
host.port = 22222;
guest.port = 22;
}
{ {
from = "host"; from = "host";
host.port = 8080; host.port = 8080;

View file

@ -1,8 +1,12 @@
{ modulesPath, lib, config, ... }: { {
modulesPath,
lib,
config,
...
}:
{
imports = [ imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
(modulesPath + "/virtualisation/qemu-vm.nix")
];
config = lib.mkMerge [ config = lib.mkMerge [
{ {
@ -10,19 +14,17 @@
enable = true; enable = true;
domain = "localhost"; domain = "localhost";
mastodon.enable = true; mastodon.enable = true;
temp.cores = config.virtualisation.cores;
}; };
services.mastodon = { services.mastodon = {
extraConfig = { extraConfig = {
EMAIL_DOMAIN_ALLOWLIST = "example.com"; EMAIL_DOMAIN_ALLOWLIST = "example.com";
}; };
# from the documentation: recommended is the amount of your CPU cores
# minus one. but it also must be a positive integer
streamingProcesses = lib.max 1 (config.virtualisation.cores - 1);
}; };
security.acme = { security.acme = lib.mkVMOverride {
defaults = { defaults = {
# invalid server; the systemd service will fail, and we won't get # invalid server; the systemd service will fail, and we won't get
# properly signed certificates. but let's not spam the letsencrypt # properly signed certificates. but let's not spam the letsencrypt

View file

@ -1,8 +1,8 @@
{ pkgs, modulesPath, ... }: { { modulesPath, ... }:
imports = [ {
(modulesPath + "/virtualisation/qemu-vm.nix")
]; imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
services.peertube = { services.peertube = {
enableWebHttps = false; enableWebHttps = false;
@ -10,10 +10,6 @@
listen.hostname = "0.0.0.0"; listen.hostname = "0.0.0.0";
instance.name = "PeerTube Test VM"; instance.name = "PeerTube Test VM";
}; };
# TODO: use agenix
secrets.secretsFile = pkgs.writeText "secret" ''
574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
'';
}; };
virtualisation.forwardPorts = [ virtualisation.forwardPorts = [

View file

@ -1,8 +1,16 @@
{ pkgs, modulesPath, ... }: { {
lib,
modulesPath,
...
}:
imports = [ let
(modulesPath + "/virtualisation/qemu-vm.nix") inherit (lib) mkVMOverride;
];
in
{
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
fediversity = { fediversity = {
enable = true; enable = true;
@ -10,22 +18,16 @@
pixelfed.enable = true; pixelfed.enable = true;
}; };
networking.firewall.allowedTCPPorts = [ 80 ];
services.pixelfed = { services.pixelfed = {
# TODO: secrets management!
secretFile = pkgs.writeText "secrets.env" ''
APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
'';
settings = { settings = {
OPEN_REGISTRATION = true;
FORCE_HTTPS_URLS = false; FORCE_HTTPS_URLS = false;
}; };
# I feel like this should have an `enable` option and be configured via `services.nginx` rather than mirroring those options in services.pixelfed.nginx
# TODO: If that indeed makes sense, upstream it.
nginx = { nginx = {
# locations."/public/".proxyPass = "${config.fediversity.internal.garage.web.urlFor "pixelfed"}/public/"; forceSSL = mkVMOverride false;
enableACME = mkVMOverride false;
}; };
}; };
virtualisation.memorySize = 2048; virtualisation.memorySize = 2048;
virtualisation.forwardPorts = [ virtualisation.forwardPorts = [
{ {