at master 7.9 kB view raw
1# This test runs the radicle-node and radicle-httpd services on a seed host, 2# and verifies that an alice peer can host a repository on the seed, 3# and that a bob peer can send alice a patch via the seed. 4 5{ pkgs, ... }: 6 7let 8 # The Node ID depends on nodes.seed.services.radicle.privateKeyFile 9 seed-nid = "z6Mkg52RcwDrPKRzzHaYgBkHH3Gi5p4694fvPstVE9HTyMB6"; 10 seed-ssh-keys = import ./ssh-keys.nix pkgs; 11 seed-tls-certs = import common/acme/server/snakeoil-certs.nix; 12 13 commonHostConfig = 14 { 15 nodes, 16 config, 17 pkgs, 18 ... 19 }: 20 { 21 environment.systemPackages = [ 22 config.services.radicle.package 23 pkgs.curl 24 pkgs.gitMinimal 25 pkgs.jq 26 ]; 27 environment.etc."gitconfig".text = '' 28 [init] 29 defaultBranch = main 30 [user] 31 email = root@${config.networking.hostName} 32 name = ${config.networking.hostName} 33 ''; 34 networking = { 35 extraHosts = '' 36 ${nodes.seed.networking.primaryIPAddress} ${nodes.seed.services.radicle.httpd.nginx.serverName} 37 ''; 38 }; 39 security.pki.certificateFiles = [ 40 seed-tls-certs.ca.cert 41 ]; 42 }; 43 44 radicleConfig = 45 { nodes, ... }: 46 alias: 47 pkgs.writeText "config.json" ( 48 builtins.toJSON { 49 preferredSeeds = [ 50 "${seed-nid}@seed:${toString nodes.seed.services.radicle.node.listenPort}" 51 ]; 52 node = { 53 inherit alias; 54 relay = "never"; 55 seedingPolicy = { 56 default = "block"; 57 }; 58 }; 59 } 60 ); 61in 62 63{ 64 name = "radicle"; 65 66 meta = with pkgs.lib.maintainers; { 67 maintainers = [ 68 julm 69 lorenzleutgeb 70 ]; 71 }; 72 73 nodes = { 74 seed = 75 { pkgs, config, ... }: 76 { 77 imports = [ commonHostConfig ]; 78 79 services.radicle = { 80 enable = true; 81 privateKeyFile = seed-ssh-keys.snakeOilEd25519PrivateKey; 82 publicKey = seed-ssh-keys.snakeOilEd25519PublicKey; 83 node = { 84 openFirewall = true; 85 }; 86 httpd = { 87 enable = true; 88 nginx = { 89 serverName = seed-tls-certs.domain; 90 addSSL = true; 91 sslCertificate = seed-tls-certs.${seed-tls-certs.domain}.cert; 92 sslCertificateKey = seed-tls-certs.${seed-tls-certs.domain}.key; 93 }; 94 }; 95 settings = { 96 preferredSeeds = [ ]; 97 node = { 98 relay = "always"; 99 seedingPolicy = { 100 default = "allow"; 101 scope = "all"; 102 }; 103 }; 104 }; 105 }; 106 107 services.nginx = { 108 enable = true; 109 }; 110 111 networking.firewall.allowedTCPPorts = [ 443 ]; 112 }; 113 114 alice = { 115 imports = [ commonHostConfig ]; 116 }; 117 118 bob = { 119 imports = [ commonHostConfig ]; 120 }; 121 }; 122 123 testScript = 124 { nodes, ... }@args: 125 '' 126 start_all() 127 128 with subtest("seed can run radicle-node"): 129 # The threshold and/or hardening may have to be changed with new features/checks 130 print(seed.succeed("systemd-analyze security radicle-node.service --threshold=10 --no-pager")) 131 seed.wait_for_unit("radicle-node.service") 132 seed.wait_for_open_port(${toString nodes.seed.services.radicle.node.listenPort}) 133 134 with subtest("seed can run radicle-httpd"): 135 # The threshold and/or hardening may have to be changed with new features/checks 136 print(seed.succeed("systemd-analyze security radicle-httpd.service --threshold=10 --no-pager")) 137 seed.wait_for_unit("radicle-httpd.service") 138 seed.wait_for_open_port(${toString nodes.seed.services.radicle.httpd.listenPort}) 139 seed.wait_for_open_port(443) 140 assert alice.succeed("curl -sS 'https://${nodes.seed.services.radicle.httpd.nginx.serverName}/api/v1' | jq -r .nid") == "${seed-nid}\n" 141 assert bob.succeed("curl -sS 'https://${nodes.seed.services.radicle.httpd.nginx.serverName}/api/v1' | jq -r .nid") == "${seed-nid}\n" 142 143 with subtest("alice can create a Node ID"): 144 alice.succeed("rad auth --alias alice --stdin </dev/null") 145 alice.copy_from_host("${radicleConfig args "alice"}", "/root/.radicle/config.json") 146 with subtest("alice can run a node"): 147 alice.succeed("rad node start") 148 with subtest("alice can create a Git repository"): 149 alice.succeed( 150 "mkdir /tmp/repo", 151 "git -C /tmp/repo init", 152 "echo hello world > /tmp/repo/testfile", 153 "git -C /tmp/repo add .", 154 "git -C /tmp/repo commit -m init" 155 ) 156 with subtest("alice can create a Repository ID"): 157 alice.succeed( 158 "cd /tmp/repo && rad init --name repo --description descr --default-branch main --public" 159 ) 160 alice_repo_rid=alice.succeed("cd /tmp/repo && rad inspect --rid").rstrip("\n") 161 with subtest("alice can send a repository to the seed"): 162 alice.succeed(f"rad sync --seed ${seed-nid} {alice_repo_rid}") 163 164 with subtest(f"seed can receive the repository {alice_repo_rid}"): 165 seed.wait_until_succeeds("test 1 = \"$(rad-system stats | jq .local.repos)\"") 166 167 with subtest("bob can create a Node ID"): 168 bob.succeed("rad auth --alias bob --stdin </dev/null") 169 bob.copy_from_host("${radicleConfig args "bob"}", "/root/.radicle/config.json") 170 bob.succeed("rad node start") 171 with subtest("bob can clone alice's repository from the seed"): 172 bob.succeed(f"rad clone {alice_repo_rid} /tmp/repo") 173 assert bob.succeed("cat /tmp/repo/testfile") == "hello world\n" 174 175 with subtest("bob can clone alice's repository from the seed through the HTTP gateway"): 176 bob.succeed(f"git clone https://${nodes.seed.services.radicle.httpd.nginx.serverName}/{alice_repo_rid[4:]}.git /tmp/repo-http") 177 assert bob.succeed("cat /tmp/repo-http/testfile") == "hello world\n" 178 179 with subtest("alice can push the main branch to the rad remote"): 180 alice.succeed( 181 "echo hello bob > /tmp/repo/testfile", 182 "git -C /tmp/repo add .", 183 "git -C /tmp/repo commit -m 'hello to bob'", 184 "git -C /tmp/repo push rad main" 185 ) 186 with subtest("bob can sync bob's repository from the seed"): 187 bob.succeed( 188 "cd /tmp/repo && rad sync --seed ${seed-nid}", 189 "cd /tmp/repo && git pull" 190 ) 191 assert bob.succeed("cat /tmp/repo/testfile") == "hello bob\n" 192 193 with subtest("bob can push a patch"): 194 bob.succeed( 195 "echo hello alice > /tmp/repo/testfile", 196 "git -C /tmp/repo checkout -b for-alice", 197 "git -C /tmp/repo add .", 198 "git -C /tmp/repo commit -m 'hello to alice'", 199 "git -C /tmp/repo push -o patch.message='hello for alice' rad HEAD:refs/patches" 200 ) 201 202 bob_repo_patch1_pid=bob.succeed("cd /tmp/repo && git branch --remotes | sed -ne 's:^ *rad/patches/::'p").rstrip("\n") 203 with subtest("alice can receive the patch"): 204 alice.wait_until_succeeds("test 1 = \"$(rad stats | jq .local.patches)\"") 205 alice.succeed( 206 f"cd /tmp/repo && rad patch show {bob_repo_patch1_pid} | grep -E '{bob_repo_patch1_pid[:7]} @ .+ by bob'", 207 f"cd /tmp/repo && rad patch checkout {bob_repo_patch1_pid}" 208 ) 209 assert alice.succeed("cat /tmp/repo/testfile") == "hello alice\n" 210 with subtest("alice can comment the patch"): 211 alice.succeed( 212 f"cd /tmp/repo && rad patch comment {bob_repo_patch1_pid} -m thank-you" 213 ) 214 with subtest("alice can merge the patch"): 215 alice.succeed( 216 "git -C /tmp/repo checkout main", 217 f"git -C /tmp/repo merge patch/{bob_repo_patch1_pid[:7]}", 218 "git -C /tmp/repo push rad main", 219 "cd /tmp/repo && rad patch list | grep -qxF 'Nothing to show.'" 220 ) 221 ''; 222}