Format everything, RFC-style
This commit is contained in:
parent
49473c43c8
commit
7007da1775
|
@ -10,4 +10,4 @@ writeShellApplication {
|
|||
"$result"/bin/switch-to-configuration switch
|
||||
EOF
|
||||
'';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ let
|
|||
inherit (lib) mkOption mkEnableOption mkForce;
|
||||
inherit (lib.types) types;
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./garage.nix
|
||||
./mastodon.nix
|
||||
|
@ -33,7 +34,7 @@ in {
|
|||
|
||||
temp = mkOption {
|
||||
description = "options that are only used while developing; should be removed eventually";
|
||||
default = {};
|
||||
default = { };
|
||||
type = types.submodule {
|
||||
options = {
|
||||
cores = mkOption {
|
||||
|
@ -51,7 +52,7 @@ in {
|
|||
|
||||
internal = mkOption {
|
||||
description = "options that are only meant to be used internally; change at your own risk";
|
||||
default = {};
|
||||
default = { };
|
||||
type = types.submodule {
|
||||
options = {
|
||||
garage = {
|
||||
|
|
|
@ -8,27 +8,49 @@ let
|
|||
in
|
||||
|
||||
# TODO: expand to a multi-machine setup
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (builtins) toString;
|
||||
inherit (lib) types mkOption mkEnableOption optionalString concatStringsSep;
|
||||
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 }:
|
||||
ensureBucketScriptFn =
|
||||
bucket:
|
||||
{
|
||||
website,
|
||||
aliases,
|
||||
corsRules,
|
||||
}:
|
||||
let
|
||||
bucketArg = escapeShellArg bucket;
|
||||
corsRulesJSON = escapeShellArg (builtins.toJSON {
|
||||
CORSRules = [{
|
||||
AllowedHeaders = corsRules.allowedHeaders;
|
||||
AllowedMethods = corsRules.allowedMethods;
|
||||
AllowedOrigins = corsRules.allowedOrigins;
|
||||
}];
|
||||
});
|
||||
in ''
|
||||
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}
|
||||
|
||||
|
@ -37,9 +59,11 @@ let
|
|||
garage bucket website --allow ${bucketArg}
|
||||
''}
|
||||
|
||||
${concatStringsSep "\n" (map (alias: ''
|
||||
garage bucket alias ${bucketArg} ${escapeShellArg alias}
|
||||
'') aliases)}
|
||||
${concatStringsSep "\n" (
|
||||
map (alias: ''
|
||||
garage bucket alias ${bucketArg} ${escapeShellArg alias}
|
||||
'') aliases
|
||||
)}
|
||||
|
||||
${optionalString corsRules.enable ''
|
||||
garage bucket allow --read --write --owner ${bucketArg} --key tmp
|
||||
|
@ -49,15 +73,29 @@ let
|
|||
''}
|
||||
'';
|
||||
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}: ''
|
||||
## 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}
|
||||
'';
|
||||
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,
|
||||
}:
|
||||
''
|
||||
## 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
|
||||
|
||||
|
@ -66,76 +104,85 @@ in
|
|||
options = {
|
||||
services.garage = {
|
||||
ensureBuckets = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
website = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
# I think setting corsRules should allow another website to show images from your bucket
|
||||
corsRules = {
|
||||
enable = mkEnableOption "CORS Rules";
|
||||
allowedHeaders = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
type = types.attrsOf (
|
||||
types.submodule {
|
||||
options = {
|
||||
website = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
allowedMethods = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
# I think setting corsRules should allow another website to show images from your bucket
|
||||
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 = [ ];
|
||||
};
|
||||
};
|
||||
allowedOrigins = mkOption {
|
||||
aliases = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
aliases = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
ensureKeys = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
# TODO: these should be managed as secrets, not in the nix store
|
||||
options = {
|
||||
id = mkOption {
|
||||
type = types.str;
|
||||
type = types.attrsOf (
|
||||
types.submodule {
|
||||
# TODO: these should be managed as secrets, not in the nix store
|
||||
options = {
|
||||
id = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
secret = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
# TODO: assert at least one of these is true
|
||||
# NOTE: this currently needs to be done at the top level module
|
||||
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 = [ ];
|
||||
};
|
||||
};
|
||||
secret = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
# TODO: assert at least one of these is true
|
||||
# NOTE: this currently needs to be done at the top level module
|
||||
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 = {};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.fediversity.enable {
|
||||
environment.systemPackages = [ pkgs.minio-client pkgs.awscli ];
|
||||
environment.systemPackages = [
|
||||
pkgs.minio-client
|
||||
pkgs.awscli
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
fedicfg.rpc.port
|
||||
|
@ -178,9 +225,11 @@ in
|
|||
'';
|
||||
};
|
||||
};
|
||||
in mapAttrs'
|
||||
(bucket: _: {name = fedicfg.web.domainForBucket bucket; inherit value;})
|
||||
(filterAttrs (_: {website, ...}: website) cfg.ensureBuckets);
|
||||
in
|
||||
mapAttrs' (bucket: _: {
|
||||
name = fedicfg.web.domainForBucket bucket;
|
||||
inherit value;
|
||||
}) (filterAttrs (_: { website, ... }: website) cfg.ensureBuckets);
|
||||
|
||||
systemd.services.ensure-garage = {
|
||||
after = [ "garage.service" ];
|
||||
|
@ -188,7 +237,11 @@ in
|
|||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
};
|
||||
path = [ cfg.package pkgs.perl pkgs.awscli ];
|
||||
path = [
|
||||
cfg.package
|
||||
pkgs.perl
|
||||
pkgs.awscli
|
||||
];
|
||||
script = ''
|
||||
set -xeuo pipefail
|
||||
|
||||
|
|
|
@ -5,10 +5,15 @@ let
|
|||
};
|
||||
in
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
|
||||
#### garage setup
|
||||
#### garage setup
|
||||
services.garage = {
|
||||
ensureBuckets = {
|
||||
mastodon = {
|
||||
|
@ -58,7 +63,10 @@ lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
|
|||
#### mastodon setup
|
||||
|
||||
# open up access to the mastodon web interface. 80 is necessary if only for ACME
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
|
||||
services.mastodon = {
|
||||
enable = true;
|
||||
|
|
|
@ -5,10 +5,18 @@ let
|
|||
};
|
||||
in
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
|
||||
services.garage = {
|
||||
ensureBuckets = {
|
||||
|
@ -22,7 +30,7 @@ lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
|
|||
allowedOrigins = [ "*" ];
|
||||
};
|
||||
};
|
||||
# TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
|
||||
# TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
|
||||
peertube-playlists = {
|
||||
website = true;
|
||||
corsRules = {
|
||||
|
|
|
@ -5,7 +5,12 @@ let
|
|||
};
|
||||
in
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
|
||||
services.garage = {
|
||||
|
@ -80,5 +85,8 @@ lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
|
|||
after = [ "ensure-garage.service" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
}
|
||||
|
|
206
flake.nix
206
flake.nix
|
@ -9,113 +9,123 @@
|
|||
disko.url = "github:nix-community/disko";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, nixpkgs-latest, pixelfed, disko }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
lib = nixpkgs.lib;
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
pkgsLatest = nixpkgs-latest.legacyPackages.${system};
|
||||
bleedingFediverseOverlay = (self: super: {
|
||||
pixelfed = pkgsLatest.pixelfed.overrideAttrs (old: {
|
||||
src = pixelfed;
|
||||
patches = (old.patches or [ ]) ++ [ ./fediversity/pixelfed-group-permissions.patch ];
|
||||
});
|
||||
## TODO: give mastodon, peertube the same treatment
|
||||
});
|
||||
in {
|
||||
nixosModules = {
|
||||
## Bleeding-edge fediverse packages
|
||||
bleedingFediverse = {
|
||||
nixpkgs.overlays = [ bleedingFediverseOverlay ];
|
||||
};
|
||||
## Fediversity modules
|
||||
fediversity = import ./fediversity;
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
nixpkgs-latest,
|
||||
pixelfed,
|
||||
disko,
|
||||
}:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
lib = nixpkgs.lib;
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
pkgsLatest = nixpkgs-latest.legacyPackages.${system};
|
||||
bleedingFediverseOverlay = (
|
||||
self: super: {
|
||||
pixelfed = pkgsLatest.pixelfed.overrideAttrs (old: {
|
||||
src = pixelfed;
|
||||
patches = (old.patches or [ ]) ++ [ ./fediversity/pixelfed-group-permissions.patch ];
|
||||
});
|
||||
## TODO: give mastodon, peertube the same treatment
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
nixosModules = {
|
||||
## Bleeding-edge fediverse packages
|
||||
bleedingFediverse = {
|
||||
nixpkgs.overlays = [ bleedingFediverseOverlay ];
|
||||
};
|
||||
## Fediversity modules
|
||||
fediversity = import ./fediversity;
|
||||
|
||||
## VM-specific modules
|
||||
interactive-vm = import ./vm/interactive-vm.nix;
|
||||
garage-vm = import ./vm/garage-vm.nix;
|
||||
mastodon-vm = import ./vm/mastodon-vm.nix;
|
||||
peertube-vm = import ./vm/peertube-vm.nix;
|
||||
pixelfed-vm = import ./vm/pixelfed-vm.nix;
|
||||
## VM-specific modules
|
||||
interactive-vm = import ./vm/interactive-vm.nix;
|
||||
garage-vm = import ./vm/garage-vm.nix;
|
||||
mastodon-vm = import ./vm/mastodon-vm.nix;
|
||||
peertube-vm = import ./vm/peertube-vm.nix;
|
||||
pixelfed-vm = import ./vm/pixelfed-vm.nix;
|
||||
|
||||
disk-layout = import ./disk-layout.nix;
|
||||
};
|
||||
|
||||
nixosConfigurations = {
|
||||
mastodon = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
mastodon-vm
|
||||
];
|
||||
disk-layout = import ./disk-layout.nix;
|
||||
};
|
||||
|
||||
peertube = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
peertube-vm
|
||||
];
|
||||
nixosConfigurations = {
|
||||
mastodon = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
mastodon-vm
|
||||
];
|
||||
};
|
||||
|
||||
peertube = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
peertube-vm
|
||||
];
|
||||
};
|
||||
|
||||
pixelfed = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
pixelfed-vm
|
||||
];
|
||||
};
|
||||
|
||||
all = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
peertube-vm
|
||||
pixelfed-vm
|
||||
mastodon-vm
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
pixelfed = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
pixelfed-vm
|
||||
];
|
||||
## 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} = {
|
||||
mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; };
|
||||
pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; };
|
||||
};
|
||||
|
||||
all = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = with self.nixosModules; [
|
||||
disko.nixosModules.default
|
||||
disk-layout
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
interactive-vm
|
||||
garage-vm
|
||||
peertube-vm
|
||||
pixelfed-vm
|
||||
mastodon-vm
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
inputs = with pkgs; [
|
||||
nil
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
## 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} = {
|
||||
mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; };
|
||||
pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; };
|
||||
};
|
||||
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
inputs = with pkgs; [
|
||||
nil
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,15 +4,22 @@
|
|||
WARNING: Running this installer will format the target disk!
|
||||
*/
|
||||
|
||||
{ nixpkgs,
|
||||
hostKeys ? {}
|
||||
{
|
||||
nixpkgs,
|
||||
hostKeys ? { },
|
||||
}:
|
||||
machine:
|
||||
|
||||
let
|
||||
inherit (builtins) concatStringsSep attrValues mapAttrs;
|
||||
|
||||
installer = { config, pkgs, lib, ... }:
|
||||
installer =
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
bootstrap = pkgs.writeShellApplication {
|
||||
name = "bootstrap";
|
||||
|
@ -20,39 +27,35 @@ let
|
|||
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
|
||||
)
|
||||
${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;
|
||||
{
|
||||
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.
|
||||
};
|
||||
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
|
||||
(nixpkgs.lib.nixosSystem { modules = [ installer ]; }).config.system.build.isoImage
|
||||
|
|
|
@ -2,143 +2,149 @@
|
|||
let
|
||||
lib = pkgs.lib;
|
||||
rebuildableTest = import ./rebuildableTest.nix pkgs;
|
||||
seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
} ''
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
seleniumScript =
|
||||
pkgs.writers.writePython3Bin "selenium-script"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
}
|
||||
''
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
print(1)
|
||||
print(1)
|
||||
|
||||
options = Options()
|
||||
options.add_argument("--headless")
|
||||
# devtools don't show up in headless screenshots
|
||||
# options.add_argument("-devtools")
|
||||
service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501
|
||||
options = Options()
|
||||
options.add_argument("--headless")
|
||||
# devtools don't show up in headless screenshots
|
||||
# options.add_argument("-devtools")
|
||||
service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501
|
||||
|
||||
driver = webdriver.Firefox(options=options, service=service)
|
||||
driver.get("http://mastodon.localhost:55001/public/local")
|
||||
driver = webdriver.Firefox(options=options, service=service)
|
||||
driver.get("http://mastodon.localhost:55001/public/local")
|
||||
|
||||
# wait until the statuses load
|
||||
WebDriverWait(driver, 90).until(
|
||||
lambda x: x.find_element(By.CLASS_NAME, "status"))
|
||||
# wait until the statuses load
|
||||
WebDriverWait(driver, 90).until(
|
||||
lambda x: x.find_element(By.CLASS_NAME, "status"))
|
||||
|
||||
driver.save_screenshot("/mastodon-screenshot.png")
|
||||
driver.save_screenshot("/mastodon-screenshot.png")
|
||||
|
||||
driver.close()
|
||||
'';
|
||||
driver.close()
|
||||
'';
|
||||
in
|
||||
pkgs.nixosTest {
|
||||
name = "test-mastodon-garage";
|
||||
|
||||
nodes = {
|
||||
server = { config, ... }: {
|
||||
virtualisation.memorySize = lib.mkVMOverride 4096;
|
||||
imports = with self.nixosModules; [
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
garage-vm
|
||||
mastodon-vm
|
||||
];
|
||||
# TODO: pair down
|
||||
environment.systemPackages = with pkgs; [
|
||||
python3
|
||||
firefox-unwrapped
|
||||
geckodriver
|
||||
toot
|
||||
xh
|
||||
seleniumScript
|
||||
helix
|
||||
imagemagick
|
||||
];
|
||||
environment.variables = {
|
||||
POST_MEDIA = ./green.png;
|
||||
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id;
|
||||
AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret;
|
||||
server =
|
||||
{ config, ... }:
|
||||
{
|
||||
virtualisation.memorySize = lib.mkVMOverride 4096;
|
||||
imports = with self.nixosModules; [
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
garage-vm
|
||||
mastodon-vm
|
||||
];
|
||||
# TODO: pair down
|
||||
environment.systemPackages = with pkgs; [
|
||||
python3
|
||||
firefox-unwrapped
|
||||
geckodriver
|
||||
toot
|
||||
xh
|
||||
seleniumScript
|
||||
helix
|
||||
imagemagick
|
||||
];
|
||||
environment.variables = {
|
||||
POST_MEDIA = ./green.png;
|
||||
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id;
|
||||
AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
import re
|
||||
import time
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import re
|
||||
import time
|
||||
|
||||
server.start()
|
||||
server.start()
|
||||
|
||||
with subtest("Mastodon starts"):
|
||||
server.wait_for_unit("mastodon-web.service")
|
||||
with subtest("Mastodon starts"):
|
||||
server.wait_for_unit("mastodon-web.service")
|
||||
|
||||
# make sure mastodon is fully up and running before we interact with it
|
||||
# TODO: is there a way to test for this?
|
||||
time.sleep(180)
|
||||
# make sure mastodon is fully up and running before we interact with it
|
||||
# TODO: is there a way to test for this?
|
||||
time.sleep(180)
|
||||
|
||||
with subtest("Account creation"):
|
||||
account_creation_output = server.succeed("mastodon-tootctl accounts create test --email test@test.com --confirmed --approve")
|
||||
password_match = re.match('.*New password: ([^\n]*).*', account_creation_output, re.S)
|
||||
if password_match is None:
|
||||
raise Exception(f"account creation did not generate a password.\n{account_creation_output}")
|
||||
password = password_match.group(1)
|
||||
with subtest("Account creation"):
|
||||
account_creation_output = server.succeed("mastodon-tootctl accounts create test --email test@test.com --confirmed --approve")
|
||||
password_match = re.match('.*New password: ([^\n]*).*', account_creation_output, re.S)
|
||||
if password_match is None:
|
||||
raise Exception(f"account creation did not generate a password.\n{account_creation_output}")
|
||||
password = password_match.group(1)
|
||||
|
||||
with subtest("TTY Login"):
|
||||
server.wait_until_tty_matches("1", "login: ")
|
||||
server.send_chars("root\n");
|
||||
with subtest("TTY Login"):
|
||||
server.wait_until_tty_matches("1", "login: ")
|
||||
server.send_chars("root\n");
|
||||
|
||||
with subtest("Log in with toot"):
|
||||
# toot doesn't provide a way to just specify our login details as arguments, so we have to pretend we're typing them in at the prompt
|
||||
server.send_chars("toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com\n")
|
||||
server.wait_until_tty_matches("1", "Password: ")
|
||||
server.send_chars(password + "\n")
|
||||
server.wait_until_tty_matches("1", "Successfully logged in.")
|
||||
with subtest("Log in with toot"):
|
||||
# toot doesn't provide a way to just specify our login details as arguments, so we have to pretend we're typing them in at the prompt
|
||||
server.send_chars("toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com\n")
|
||||
server.wait_until_tty_matches("1", "Password: ")
|
||||
server.send_chars(password + "\n")
|
||||
server.wait_until_tty_matches("1", "Successfully logged in.")
|
||||
|
||||
with subtest("post text"):
|
||||
server.succeed("echo 'hello mastodon' | toot post")
|
||||
with subtest("post text"):
|
||||
server.succeed("echo 'hello mastodon' | toot post")
|
||||
|
||||
with subtest("post image"):
|
||||
server.succeed("toot post --media $POST_MEDIA")
|
||||
with subtest("post image"):
|
||||
server.succeed("toot post --media $POST_MEDIA")
|
||||
|
||||
with subtest("access garage"):
|
||||
server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
|
||||
server.succeed("mc ls garage/mastodon")
|
||||
with subtest("access garage"):
|
||||
server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
|
||||
server.succeed("mc ls garage/mastodon")
|
||||
|
||||
with subtest("access image in garage"):
|
||||
image = server.succeed("mc find garage --regex original")
|
||||
image = image.rstrip()
|
||||
if image == "":
|
||||
raise Exception("image posted to mastodon did not get stored in garage")
|
||||
server.succeed(f"mc cat {image} >/garage-image.webp")
|
||||
garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp")
|
||||
image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA")
|
||||
if garage_image_hash != image_hash:
|
||||
raise Exception("image stored in garage did not match image uploaded")
|
||||
with subtest("access image in garage"):
|
||||
image = server.succeed("mc find garage --regex original")
|
||||
image = image.rstrip()
|
||||
if image == "":
|
||||
raise Exception("image posted to mastodon did not get stored in garage")
|
||||
server.succeed(f"mc cat {image} >/garage-image.webp")
|
||||
garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp")
|
||||
image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA")
|
||||
if garage_image_hash != image_hash:
|
||||
raise Exception("image stored in garage did not match image uploaded")
|
||||
|
||||
with subtest("Content security policy allows garage images"):
|
||||
headers = server.succeed("xh -h http://mastodon.localhost:55001/public/local")
|
||||
csp_match = None
|
||||
# I can't figure out re.MULTILINE
|
||||
for header in headers.split("\n"):
|
||||
csp_match = re.match('^Content-Security-Policy: (.*)$', header)
|
||||
if csp_match is not None:
|
||||
break
|
||||
if csp_match is None:
|
||||
raise Exception("mastodon did not send a content security policy header")
|
||||
csp = csp_match.group(1)
|
||||
# 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. Be careful with port 80 though.
|
||||
garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost.*", csp)
|
||||
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.")
|
||||
with subtest("Content security policy allows garage images"):
|
||||
headers = server.succeed("xh -h http://mastodon.localhost:55001/public/local")
|
||||
csp_match = None
|
||||
# I can't figure out re.MULTILINE
|
||||
for header in headers.split("\n"):
|
||||
csp_match = re.match('^Content-Security-Policy: (.*)$', header)
|
||||
if csp_match is not None:
|
||||
break
|
||||
if csp_match is None:
|
||||
raise Exception("mastodon did not send a content security policy header")
|
||||
csp = csp_match.group(1)
|
||||
# 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. Be careful with port 80 though.
|
||||
garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost.*", csp)
|
||||
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.")
|
||||
|
||||
# this could in theory give a false positive if mastodon changes it's colorscheme to include pure green.
|
||||
with subtest("image displays"):
|
||||
server.succeed("selenium-script")
|
||||
server.copy_from_vm("/mastodon-screenshot.png", "")
|
||||
displayed_colors = server.succeed("convert /mastodon-screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
|
||||
# check that the green image displayed somewhere
|
||||
green_check = re.match(".*#00FF00.*", displayed_colors, re.S)
|
||||
if green_check is None:
|
||||
raise Exception("cannot detect the uploaded image on mastodon page.")
|
||||
'';
|
||||
# this could in theory give a false positive if mastodon changes it's colorscheme to include pure green.
|
||||
with subtest("image displays"):
|
||||
server.succeed("selenium-script")
|
||||
server.copy_from_vm("/mastodon-screenshot.png", "")
|
||||
displayed_colors = server.succeed("convert /mastodon-screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
|
||||
# check that the green image displayed somewhere
|
||||
green_check = re.match(".*#00FF00.*", displayed_colors, re.S)
|
||||
if green_check is None:
|
||||
raise Exception("cannot detect the uploaded image on mastodon page.")
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -50,166 +50,176 @@ let
|
|||
driver.quit()
|
||||
'';
|
||||
|
||||
seleniumScriptPostPicture = pkgs.writers.writePython3Bin "selenium-script-post-picture"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
} ''
|
||||
import os
|
||||
import time
|
||||
${seleniumImports}
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
seleniumScriptPostPicture =
|
||||
pkgs.writers.writePython3Bin "selenium-script-post-picture"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
}
|
||||
''
|
||||
import os
|
||||
import time
|
||||
${seleniumImports}
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
|
||||
${seleniumSetup}
|
||||
${seleniumPixelfedLogin}
|
||||
time.sleep(3)
|
||||
${seleniumSetup}
|
||||
${seleniumPixelfedLogin}
|
||||
time.sleep(3)
|
||||
|
||||
media_path = os.environ['POST_MEDIA']
|
||||
media_path = os.environ['POST_MEDIA']
|
||||
|
||||
# Find the new post form, fill it in with our pictureand a caption.
|
||||
print("Click on “Create New Post”...", file=sys.stderr)
|
||||
driver.find_element(By.LINK_TEXT, "Create New Post").click()
|
||||
print("Add file to input element...", file=sys.stderr)
|
||||
driver.find_element(By.XPATH, "//input[@type='file']").send_keys(media_path)
|
||||
print("Add a caption", file=sys.stderr)
|
||||
driver.find_element(By.CSS_SELECTOR, ".media-body textarea").send_keys(
|
||||
"Fediversity test of image upload to pixelfed with garage storage."
|
||||
)
|
||||
time.sleep(3)
|
||||
print("Click on “Post” button...", file=sys.stderr)
|
||||
driver.find_element(By.LINK_TEXT, "Post").click()
|
||||
# Find the new post form, fill it in with our pictureand a caption.
|
||||
print("Click on “Create New Post”...", file=sys.stderr)
|
||||
driver.find_element(By.LINK_TEXT, "Create New Post").click()
|
||||
print("Add file to input element...", file=sys.stderr)
|
||||
driver.find_element(By.XPATH, "//input[@type='file']").send_keys(media_path)
|
||||
print("Add a caption", file=sys.stderr)
|
||||
driver.find_element(By.CSS_SELECTOR, ".media-body textarea").send_keys(
|
||||
"Fediversity test of image upload to pixelfed with garage storage."
|
||||
)
|
||||
time.sleep(3)
|
||||
print("Click on “Post” button...", file=sys.stderr)
|
||||
driver.find_element(By.LINK_TEXT, "Post").click()
|
||||
|
||||
# Wait until the post loads, and in particular its picture, then take a
|
||||
# screenshot of the whole page.
|
||||
print("Wait for post and image to be loaded...", file=sys.stderr)
|
||||
img = driver.find_element(
|
||||
By.XPATH,
|
||||
"//div[@class='timeline-status-component-content']//img"
|
||||
)
|
||||
WebDriverWait(driver, timeout=10).until(
|
||||
lambda d: d.execute_script("return arguments[0].complete", img)
|
||||
)
|
||||
time.sleep(3)
|
||||
# Wait until the post loads, and in particular its picture, then take a
|
||||
# screenshot of the whole page.
|
||||
print("Wait for post and image to be loaded...", file=sys.stderr)
|
||||
img = driver.find_element(
|
||||
By.XPATH,
|
||||
"//div[@class='timeline-status-component-content']//img"
|
||||
)
|
||||
WebDriverWait(driver, timeout=10).until(
|
||||
lambda d: d.execute_script("return arguments[0].complete", img)
|
||||
)
|
||||
time.sleep(3)
|
||||
|
||||
${seleniumTakeScreenshot "\"/home/selenium/screenshot.png\""}
|
||||
${seleniumQuit}'';
|
||||
${seleniumTakeScreenshot "\"/home/selenium/screenshot.png\""}
|
||||
${seleniumQuit}'';
|
||||
|
||||
seleniumScriptGetSrc = pkgs.writers.writePython3Bin "selenium-script-get-src"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
} ''
|
||||
${seleniumImports}
|
||||
${seleniumSetup}
|
||||
${seleniumPixelfedLogin}
|
||||
seleniumScriptGetSrc =
|
||||
pkgs.writers.writePython3Bin "selenium-script-get-src"
|
||||
{
|
||||
libraries = with pkgs.python3Packages; [ selenium ];
|
||||
}
|
||||
''
|
||||
${seleniumImports}
|
||||
${seleniumSetup}
|
||||
${seleniumPixelfedLogin}
|
||||
|
||||
img = driver.find_element(
|
||||
By.XPATH,
|
||||
"//div[@class='timeline-status-component-content']//img"
|
||||
)
|
||||
# REVIEW: Need to wait for it to be loaded?
|
||||
print(img.get_attribute('src'))
|
||||
img = driver.find_element(
|
||||
By.XPATH,
|
||||
"//div[@class='timeline-status-component-content']//img"
|
||||
)
|
||||
# REVIEW: Need to wait for it to be loaded?
|
||||
print(img.get_attribute('src'))
|
||||
|
||||
${seleniumQuit}'';
|
||||
${seleniumQuit}'';
|
||||
|
||||
in
|
||||
pkgs.nixosTest {
|
||||
name = "test-pixelfed-garage";
|
||||
|
||||
nodes = {
|
||||
server = { config, ... }: {
|
||||
server =
|
||||
{ config, ... }:
|
||||
{
|
||||
|
||||
services = {
|
||||
xserver = {
|
||||
enable = true;
|
||||
displayManager.lightdm.enable = true;
|
||||
desktopManager.lxqt.enable = true;
|
||||
services = {
|
||||
xserver = {
|
||||
enable = true;
|
||||
displayManager.lightdm.enable = true;
|
||||
desktopManager.lxqt.enable = true;
|
||||
};
|
||||
|
||||
displayManager.autoLogin = {
|
||||
enable = true;
|
||||
user = "selenium";
|
||||
};
|
||||
};
|
||||
virtualisation.resolution = {
|
||||
x = 1680;
|
||||
y = 1050;
|
||||
};
|
||||
|
||||
displayManager.autoLogin = {
|
||||
enable = true;
|
||||
user = "selenium";
|
||||
virtualisation = {
|
||||
memorySize = lib.mkVMOverride 8192;
|
||||
cores = 8;
|
||||
};
|
||||
imports = with self.nixosModules; [
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
garage-vm
|
||||
pixelfed-vm
|
||||
];
|
||||
# TODO: pair down
|
||||
environment.systemPackages = with pkgs; [
|
||||
python3
|
||||
chromium
|
||||
chromedriver
|
||||
xh
|
||||
seleniumScriptPostPicture
|
||||
seleniumScriptGetSrc
|
||||
helix
|
||||
imagemagick
|
||||
];
|
||||
environment.variables = {
|
||||
POST_MEDIA = ./fediversity.png;
|
||||
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id;
|
||||
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
|
||||
users.users.selenium = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
virtualisation.resolution = { x = 1680; y = 1050; };
|
||||
|
||||
|
||||
virtualisation = {
|
||||
memorySize = lib.mkVMOverride 8192;
|
||||
cores = 8;
|
||||
};
|
||||
imports = with self.nixosModules; [
|
||||
bleedingFediverse
|
||||
fediversity
|
||||
garage-vm
|
||||
pixelfed-vm
|
||||
];
|
||||
# TODO: pair down
|
||||
environment.systemPackages = with pkgs; [
|
||||
python3
|
||||
chromium
|
||||
chromedriver
|
||||
xh
|
||||
seleniumScriptPostPicture
|
||||
seleniumScriptGetSrc
|
||||
helix
|
||||
imagemagick
|
||||
];
|
||||
environment.variables = {
|
||||
POST_MEDIA = ./fediversity.png;
|
||||
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id;
|
||||
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
|
||||
users.users.selenium = {
|
||||
isNormalUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
import re
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import re
|
||||
|
||||
server.start()
|
||||
server.start()
|
||||
|
||||
with subtest("Pixelfed starts"):
|
||||
server.wait_for_unit("phpfpm-pixelfed.service")
|
||||
with subtest("Pixelfed starts"):
|
||||
server.wait_for_unit("phpfpm-pixelfed.service")
|
||||
|
||||
with subtest("Account creation"):
|
||||
server.succeed("pixelfed-manage user:create --name=test --username=test --email=${email} --password=${password} --confirm_email=1")
|
||||
with subtest("Account creation"):
|
||||
server.succeed("pixelfed-manage user:create --name=test --username=test --email=${email} --password=${password} --confirm_email=1")
|
||||
|
||||
# NOTE: This could in theory give a false positive if pixelfed changes it's
|
||||
# colorscheme to include pure green. (see same problem in pixelfed-garage.nix).
|
||||
# TODO: For instance: post a red image and check that the green pixel IS NOT
|
||||
# there, then post a green image and check that the green pixel IS there.
|
||||
# NOTE: This could in theory give a false positive if pixelfed changes it's
|
||||
# colorscheme to include pure green. (see same problem in pixelfed-garage.nix).
|
||||
# TODO: For instance: post a red image and check that the green pixel IS NOT
|
||||
# there, then post a green image and check that the green pixel IS there.
|
||||
|
||||
with subtest("Image displays"):
|
||||
server.succeed("su - selenium -c 'selenium-script-post-picture ${email} ${password}'")
|
||||
server.copy_from_vm("/home/selenium/screenshot.png", "")
|
||||
displayed_colors = server.succeed("magick /home/selenium/screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
|
||||
# check that the green image displayed somewhere
|
||||
image_check = re.match(".*#FF0500.*", displayed_colors, re.S)
|
||||
if image_check is None:
|
||||
raise Exception("cannot detect the uploaded image on pixelfed page.")
|
||||
with subtest("Image displays"):
|
||||
server.succeed("su - selenium -c 'selenium-script-post-picture ${email} ${password}'")
|
||||
server.copy_from_vm("/home/selenium/screenshot.png", "")
|
||||
displayed_colors = server.succeed("magick /home/selenium/screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
|
||||
# check that the green image displayed somewhere
|
||||
image_check = re.match(".*#FF0500.*", displayed_colors, re.S)
|
||||
if image_check is None:
|
||||
raise Exception("cannot detect the uploaded image on pixelfed page.")
|
||||
|
||||
with subtest("access garage"):
|
||||
server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
|
||||
server.succeed("mc ls garage/pixelfed")
|
||||
with subtest("access garage"):
|
||||
server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
|
||||
server.succeed("mc ls garage/pixelfed")
|
||||
|
||||
with subtest("access image in garage"):
|
||||
image = server.succeed("mc find garage --regex '\\.png' --ignore '*_thumb.png'")
|
||||
image = image.rstrip()
|
||||
if image == "":
|
||||
raise Exception("image posted to Pixelfed did not get stored in garage")
|
||||
server.succeed(f"mc cat {image} >/garage-image.png")
|
||||
garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.png")
|
||||
image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA")
|
||||
if garage_image_hash != image_hash:
|
||||
raise Exception("image stored in garage did not match image uploaded")
|
||||
with subtest("access image in garage"):
|
||||
image = server.succeed("mc find garage --regex '\\.png' --ignore '*_thumb.png'")
|
||||
image = image.rstrip()
|
||||
if image == "":
|
||||
raise Exception("image posted to Pixelfed did not get stored in garage")
|
||||
server.succeed(f"mc cat {image} >/garage-image.png")
|
||||
garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.png")
|
||||
image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA")
|
||||
if garage_image_hash != image_hash:
|
||||
raise Exception("image stored in garage did not match image uploaded")
|
||||
|
||||
with subtest("Check that image comes from garage"):
|
||||
src = server.succeed("su - selenium -c 'selenium-script-get-src ${email} ${password}'")
|
||||
if not src.startswith("${nodes.server.fediversity.internal.garage.web.urlForBucket "pixelfed"}"):
|
||||
raise Exception("image does not come from garage")
|
||||
'';
|
||||
with subtest("Check that image comes from garage"):
|
||||
src = server.succeed("su - selenium -c 'selenium-script-get-src ${email} ${password}'")
|
||||
if not src.startswith("${nodes.server.fediversity.internal.garage.web.urlForBucket "pixelfed"}"):
|
||||
raise Exception("image does not come from garage")
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -1,32 +1,42 @@
|
|||
pkgs: test:
|
||||
let
|
||||
inherit (pkgs.lib) mapAttrsToList concatStringsSep genAttrs mkIf;
|
||||
inherit (pkgs.lib)
|
||||
mapAttrsToList
|
||||
concatStringsSep
|
||||
genAttrs
|
||||
mkIf
|
||||
;
|
||||
inherit (builtins) attrNames;
|
||||
|
||||
interactiveConfig = ({ config, ... }: {
|
||||
# so we can run `nix shell nixpkgs#foo` on the machines
|
||||
nix.extraOptions = ''
|
||||
extra-experimental-features = nix-command flakes
|
||||
'';
|
||||
interactiveConfig = (
|
||||
{ config, ... }:
|
||||
{
|
||||
# so we can run `nix shell nixpkgs#foo` on the machines
|
||||
nix.extraOptions = ''
|
||||
extra-experimental-features = nix-command flakes
|
||||
'';
|
||||
|
||||
# so we can ssh in and rebuild them
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PermitRootLogin = "yes";
|
||||
PermitEmptyPasswords = "yes";
|
||||
UsePAM = false;
|
||||
# so we can ssh in and rebuild them
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PermitRootLogin = "yes";
|
||||
PermitEmptyPasswords = "yes";
|
||||
UsePAM = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation = mkIf (config.networking.hostName == "jumphost") {
|
||||
forwardPorts = [{
|
||||
from = "host";
|
||||
host.port = 2222;
|
||||
guest.port = 22;
|
||||
}];
|
||||
};
|
||||
});
|
||||
virtualisation = mkIf (config.networking.hostName == "jumphost") {
|
||||
forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = 2222;
|
||||
guest.port = 22;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
sshConfig = pkgs.writeText "ssh-config" ''
|
||||
Host *
|
||||
|
@ -50,10 +60,11 @@ let
|
|||
# create an association array from machine names to the path to their
|
||||
# configuration in the nix store
|
||||
declare -A configPaths=(${
|
||||
concatStringsSep " "
|
||||
(mapAttrsToList
|
||||
(n: v: ''["${n}"]="${v.system.build.toplevel}"'')
|
||||
rebuildableTest.driverInteractive.nodes)
|
||||
concatStringsSep " " (
|
||||
mapAttrsToList (
|
||||
n: v: ''["${n}"]="${v.system.build.toplevel}"''
|
||||
) rebuildableTest.driverInteractive.nodes
|
||||
)
|
||||
})
|
||||
|
||||
rebuild_one() {
|
||||
|
@ -113,37 +124,40 @@ let
|
|||
# we're at it)
|
||||
rebuildableTest =
|
||||
let
|
||||
preOverride = pkgs.nixosTest (test // {
|
||||
interactive = (test.interactive or { }) // {
|
||||
# no need to // with test.interactive.nodes here, since we are iterating
|
||||
# over all of them, and adding back in the config via `imports`
|
||||
nodes = genAttrs
|
||||
(
|
||||
attrNames test.nodes or { } ++
|
||||
attrNames test.interactive.nodes or { } ++
|
||||
[ "jumphost" ]
|
||||
)
|
||||
(n: {
|
||||
imports = [
|
||||
(test.interactive.${n} or { })
|
||||
interactiveConfig
|
||||
];
|
||||
});
|
||||
};
|
||||
# override with test.passthru in case someone wants to overwrite us.
|
||||
passthru = { inherit rebuildScript sshConfig; } // (test.passthru or { });
|
||||
});
|
||||
preOverride = pkgs.nixosTest (
|
||||
test
|
||||
// {
|
||||
interactive = (test.interactive or { }) // {
|
||||
# no need to // with test.interactive.nodes here, since we are iterating
|
||||
# over all of them, and adding back in the config via `imports`
|
||||
nodes =
|
||||
genAttrs (attrNames test.nodes or { } ++ attrNames test.interactive.nodes or { } ++ [ "jumphost" ])
|
||||
(n: {
|
||||
imports = [
|
||||
(test.interactive.${n} or { })
|
||||
interactiveConfig
|
||||
];
|
||||
});
|
||||
};
|
||||
# override with test.passthru in case someone wants to overwrite us.
|
||||
passthru = {
|
||||
inherit rebuildScript sshConfig;
|
||||
} // (test.passthru or { });
|
||||
}
|
||||
);
|
||||
in
|
||||
preOverride // {
|
||||
preOverride
|
||||
// {
|
||||
driverInteractive = preOverride.driverInteractive.overrideAttrs (old: {
|
||||
# this comes from runCommand, not mkDerivation, so this is the only
|
||||
# hook we have to override
|
||||
buildCommand = old.buildCommand + ''
|
||||
ln -s ${sshConfig} $out/ssh-config
|
||||
ln -s ${rebuildScript}/bin/rebuild $out/bin/rebuild
|
||||
'';
|
||||
buildCommand =
|
||||
old.buildCommand
|
||||
+ ''
|
||||
ln -s ${sshConfig} $out/ssh-config
|
||||
ln -s ${rebuildScript}/bin/rebuild $out/bin/rebuild
|
||||
'';
|
||||
});
|
||||
};
|
||||
in
|
||||
rebuildableTest
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
{ lib, config, modulesPath, ... }:
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkVMOverride mapAttrs' filterAttrs;
|
||||
|
@ -7,7 +12,8 @@ let
|
|||
|
||||
fedicfg = config.fediversity.internal.garage;
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
|
||||
|
||||
services.nginx.virtualHosts =
|
||||
|
@ -16,9 +22,11 @@ in {
|
|||
forceSSL = mkVMOverride false;
|
||||
enableACME = mkVMOverride false;
|
||||
};
|
||||
in mapAttrs'
|
||||
(bucket: _: {name = fedicfg.web.domainForBucket bucket; inherit value;})
|
||||
(filterAttrs (_: {website, ...}: website) cfg.ensureBuckets);
|
||||
in
|
||||
mapAttrs' (bucket: _: {
|
||||
name = fedicfg.web.domainForBucket bucket;
|
||||
inherit value;
|
||||
}) (filterAttrs (_: { website, ... }: website) cfg.ensureBuckets);
|
||||
|
||||
virtualisation.diskSize = 2048;
|
||||
virtualisation.forwardPorts = [
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# customize nixos-rebuild build-vm to be a bit more convenient
|
||||
{ pkgs, ... }: {
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
# let us log in
|
||||
users.mutableUsers = false;
|
||||
users.users.root.hashedPassword = "";
|
||||
|
@ -34,7 +35,10 @@
|
|||
# no graphics. see nixos-shell
|
||||
virtualisation = {
|
||||
graphics = false;
|
||||
qemu.consoles = [ "tty0" "hvc0" ];
|
||||
qemu.consoles = [
|
||||
"tty0"
|
||||
"hvc0"
|
||||
];
|
||||
qemu.options = [
|
||||
"-serial null"
|
||||
"-device virtio-serial"
|
||||
|
@ -45,7 +49,10 @@
|
|||
};
|
||||
|
||||
# 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.defaultHTTPListenPort = 8080;
|
||||
virtualisation.forwardPorts = [
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
{ modulesPath, lib, config, ... }: {
|
||||
{
|
||||
modulesPath,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{ pkgs, modulesPath, ... }: {
|
||||
{ pkgs, modulesPath, ... }:
|
||||
{
|
||||
|
||||
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
|
||||
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
{ pkgs, lib, modulesPath, ... }:
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) mkVMOverride;
|
||||
|
||||
in {
|
||||
in
|
||||
{
|
||||
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
|
||||
|
||||
fediversity = {
|
||||
|
|
Reference in a new issue