Flake for my NixOS devices

Tangled Knot Self Host

bwc9876.dev ac712f1c 6faf3b20

verified
+372 -20
+208
flake.lock
··· 1 1 { 2 2 "nodes": { 3 + "actor-typeahead-src": { 4 + "flake": false, 5 + "locked": { 6 + "lastModified": 1762835797, 7 + "narHash": "sha256-heizoWUKDdar6ymfZTnj3ytcEv/L4d4fzSmtr0HlXsQ=", 8 + "ref": "refs/heads/main", 9 + "rev": "677fe7f743050a4e7f09d4a6f87bbf1325a06f6b", 10 + "revCount": 6, 11 + "type": "git", 12 + "url": "https://tangled.org/@jakelazaroff.com/actor-typeahead" 13 + }, 14 + "original": { 15 + "type": "git", 16 + "url": "https://tangled.org/@jakelazaroff.com/actor-typeahead" 17 + } 18 + }, 3 19 "bingus": { 4 20 "inputs": { 5 21 "flakelight": "flakelight", ··· 164 180 "type": "github" 165 181 } 166 182 }, 183 + "flake-compat_2": { 184 + "flake": false, 185 + "locked": { 186 + "lastModified": 1751685974, 187 + "narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=", 188 + "rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1", 189 + "type": "tarball", 190 + "url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1" 191 + }, 192 + "original": { 193 + "type": "tarball", 194 + "url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz" 195 + } 196 + }, 167 197 "flake-parts": { 168 198 "inputs": { 169 199 "nixpkgs-lib": [ ··· 182 212 "original": { 183 213 "owner": "hercules-ci", 184 214 "repo": "flake-parts", 215 + "type": "github" 216 + } 217 + }, 218 + "flake-utils": { 219 + "inputs": { 220 + "systems": "systems_2" 221 + }, 222 + "locked": { 223 + "lastModified": 1731533236, 224 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 225 + "owner": "numtide", 226 + "repo": "flake-utils", 227 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 228 + "type": "github" 229 + }, 230 + "original": { 231 + "owner": "numtide", 232 + "repo": "flake-utils", 185 233 "type": "github" 186 234 } 187 235 }, ··· 376 424 "type": "github" 377 425 } 378 426 }, 427 + "gomod2nix": { 428 + "inputs": { 429 + "flake-utils": "flake-utils", 430 + "nixpkgs": [ 431 + "tangled", 432 + "nixpkgs" 433 + ] 434 + }, 435 + "locked": { 436 + "lastModified": 1763982521, 437 + "narHash": "sha256-ur4QIAHwgFc0vXiaxn5No/FuZicxBr2p0gmT54xZkUQ=", 438 + "owner": "nix-community", 439 + "repo": "gomod2nix", 440 + "rev": "02e63a239d6eabd595db56852535992c898eba72", 441 + "type": "github" 442 + }, 443 + "original": { 444 + "owner": "nix-community", 445 + "repo": "gomod2nix", 446 + "type": "github" 447 + } 448 + }, 379 449 "hm": { 380 450 "inputs": { 381 451 "nixpkgs": [ ··· 396 466 "type": "github" 397 467 } 398 468 }, 469 + "htmx-src": { 470 + "flake": false, 471 + "locked": { 472 + "narHash": "sha256-nm6avZuEBg67SSyyZUhjpXVNstHHgUxrtBHqJgowU08=", 473 + "type": "file", 474 + "url": "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js" 475 + }, 476 + "original": { 477 + "type": "file", 478 + "url": "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js" 479 + } 480 + }, 481 + "htmx-ws-src": { 482 + "flake": false, 483 + "locked": { 484 + "narHash": "sha256-2fg6KyEJoO24q0fQqbz9RMaYNPQrMwpZh29tkSqdqGY=", 485 + "type": "file", 486 + "url": "https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2" 487 + }, 488 + "original": { 489 + "type": "file", 490 + "url": "https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2" 491 + } 492 + }, 493 + "ibm-plex-mono-src": { 494 + "flake": false, 495 + "locked": { 496 + "lastModified": 1731402384, 497 + "narHash": "sha256-OwUmrPfEehLDz0fl2ChYLK8FQM2p0G1+EMrGsYEq+6g=", 498 + "type": "tarball", 499 + "url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip" 500 + }, 501 + "original": { 502 + "type": "tarball", 503 + "url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip" 504 + } 505 + }, 399 506 "imperm": { 400 507 "inputs": { 401 508 "home-manager": [ ··· 419 526 "type": "github" 420 527 } 421 528 }, 529 + "indigo": { 530 + "flake": false, 531 + "locked": { 532 + "lastModified": 1753693716, 533 + "narHash": "sha256-DMIKnCJRODQXEHUxA+7mLzRALmnZhkkbHlFT2rCQYrE=", 534 + "owner": "oppiliappan", 535 + "repo": "indigo", 536 + "rev": "5f170569da9360f57add450a278d73538092d8ca", 537 + "type": "github" 538 + }, 539 + "original": { 540 + "owner": "oppiliappan", 541 + "repo": "indigo", 542 + "type": "github" 543 + } 544 + }, 545 + "inter-fonts-src": { 546 + "flake": false, 547 + "locked": { 548 + "lastModified": 1731687360, 549 + "narHash": "sha256-5vdKKvHAeZi6igrfpbOdhZlDX2/5+UvzlnCQV6DdqoQ=", 550 + "type": "tarball", 551 + "url": "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip" 552 + }, 553 + "original": { 554 + "type": "tarball", 555 + "url": "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip" 556 + } 557 + }, 422 558 "lanzaboote": { 423 559 "inputs": { 424 560 "crane": "crane_2", ··· 440 576 "owner": "nix-community", 441 577 "repo": "lanzaboote", 442 578 "type": "github" 579 + } 580 + }, 581 + "lucide-src": { 582 + "flake": false, 583 + "locked": { 584 + "lastModified": 1754044466, 585 + "narHash": "sha256-+exBR2OToB1iv7ZQI2S4B0lXA/QRvC9n6U99UxGpJGs=", 586 + "type": "tarball", 587 + "url": "https://github.com/lucide-icons/lucide/releases/download/0.536.0/lucide-icons-0.536.0.zip" 588 + }, 589 + "original": { 590 + "type": "tarball", 591 + "url": "https://github.com/lucide-icons/lucide/releases/download/0.536.0/lucide-icons-0.536.0.zip" 443 592 } 444 593 }, 445 594 "mrpack-install": { ··· 771 920 "nixvim": "nixvim", 772 921 "nu_plugin_dbus": "nu_plugin_dbus", 773 922 "spoon": "spoon", 923 + "tangled": "tangled", 774 924 "wayland-mpris-idle-inhibit": "wayland-mpris-idle-inhibit" 775 925 } 776 926 }, ··· 862 1012 "url": "https://codeberg.org/spoonbaker/mono" 863 1013 } 864 1014 }, 1015 + "sqlite-lib-src": { 1016 + "flake": false, 1017 + "locked": { 1018 + "lastModified": 1706631843, 1019 + "narHash": "sha256-bJoMjirsBjm2Qk9KPiy3yV3+8b/POlYe76/FQbciHro=", 1020 + "type": "tarball", 1021 + "url": "https://sqlite.org/2024/sqlite-amalgamation-3450100.zip" 1022 + }, 1023 + "original": { 1024 + "type": "tarball", 1025 + "url": "https://sqlite.org/2024/sqlite-amalgamation-3450100.zip" 1026 + } 1027 + }, 865 1028 "systems": { 866 1029 "locked": { 867 1030 "lastModified": 1681028828, ··· 875 1038 "owner": "nix-systems", 876 1039 "repo": "default", 877 1040 "type": "github" 1041 + } 1042 + }, 1043 + "systems_2": { 1044 + "locked": { 1045 + "lastModified": 1681028828, 1046 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 1047 + "owner": "nix-systems", 1048 + "repo": "default", 1049 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 1050 + "type": "github" 1051 + }, 1052 + "original": { 1053 + "owner": "nix-systems", 1054 + "repo": "default", 1055 + "type": "github" 1056 + } 1057 + }, 1058 + "tangled": { 1059 + "inputs": { 1060 + "actor-typeahead-src": "actor-typeahead-src", 1061 + "flake-compat": "flake-compat_2", 1062 + "gomod2nix": "gomod2nix", 1063 + "htmx-src": "htmx-src", 1064 + "htmx-ws-src": "htmx-ws-src", 1065 + "ibm-plex-mono-src": "ibm-plex-mono-src", 1066 + "indigo": "indigo", 1067 + "inter-fonts-src": "inter-fonts-src", 1068 + "lucide-src": "lucide-src", 1069 + "nixpkgs": [ 1070 + "nixpkgs" 1071 + ], 1072 + "sqlite-lib-src": "sqlite-lib-src" 1073 + }, 1074 + "locked": { 1075 + "lastModified": 1769508314, 1076 + "narHash": "sha256-NO6zA56l1qqBs44oXdMuIjyGIcL0EGwZ95bGnd5ZT0s=", 1077 + "ref": "refs/heads/master", 1078 + "rev": "dc2cc2b318297aaca96f29a7db9c6b2eaeba092b", 1079 + "revCount": 1896, 1080 + "type": "git", 1081 + "url": "https://tangled.org/tangled.org/core" 1082 + }, 1083 + "original": { 1084 + "type": "git", 1085 + "url": "https://tangled.org/tangled.org/core" 878 1086 } 879 1087 }, 880 1088 "treefmt-nix": {
+3
flake.nix
··· 35 35 niri.inputs.nixpkgs.follows = "nixpkgs"; 36 36 musnix.url = "github:musnix/musnix"; 37 37 musnix.inputs.nixpkgs.follows = "nixpkgs"; 38 + tangled.url = "git+https://tangled.org/tangled.org/core"; 39 + tangled.inputs.nixpkgs.follows = "nixpkgs"; 38 40 39 41 spoon.url = "git+https://codeberg.org/spoonbaker/mono"; 40 42 spoon.inputs = { ··· 65 67 gh-grader-preview, 66 68 niri, 67 69 musnix, 70 + tangled, 68 71 }: 69 72 flakelight ./. { 70 73 inherit inputs;
+14 -18
homeModules/jj.nix
··· 1 - { ... }: 2 - { 1 + {...}: { 3 2 pkgs, 4 3 config, 5 4 lib, 6 5 ... 7 - }: 8 - { 6 + }: { 9 7 options.cow.jj = { 10 8 enable = lib.mkEnableOption "jj + customizations"; 11 9 }; 12 10 13 - config = 14 - let 15 - conf = config.cow.jj; 16 - in 17 - (lib.mkIf conf.enable { 18 - programs.jujutsu = { 19 - enable = true; 20 - settings = { 21 - ui.pager = "less -FR"; 22 - }; 11 + config = let 12 + conf = config.cow.jj; 13 + in (lib.mkIf conf.enable { 14 + programs.jujutsu = { 15 + enable = true; 16 + settings = { 17 + ui.pager = "less -FR"; 23 18 }; 19 + }; 24 20 25 - home.packages = with pkgs; [ 26 - mergiraf 27 - ]; 28 - }); 21 + home.packages = with pkgs; [ 22 + mergiraf 23 + ]; 24 + }); 29 25 }
-1
homeModules/nushell.nix
··· 74 74 "cd" = "z"; 75 75 "py" = "python"; 76 76 "🥺" = "sudo"; 77 - "j" = "just"; 78 77 }; 79 78 }; 80 79 };
+46 -1
nixosConfigurations/black-mesa.nix
··· 92 92 ]; 93 93 }; 94 94 } 95 + ( 96 + { 97 + config, 98 + pkgs, 99 + ... 100 + }: { 101 + # Self hosted stuff 102 + 103 + cow = { 104 + tangled = { 105 + hostname = "knot.bwc9876.dev"; 106 + knot.enable = true; 107 + }; 108 + imperm.keep = ["/var/lib/acme"]; 109 + }; 110 + 111 + services.nginx = { 112 + enable = true; 113 + virtualHosts."knot.bwc9876.dev" = { 114 + forceSSL = true; 115 + acmeRoot = null; # Doing DNS challenges 116 + useACMEHost = "bwc9876.dev"; 117 + }; 118 + }; 119 + 120 + security.acme = { 121 + acceptTerms = true; 122 + defaults = { 123 + email = "ben@bwc9876.dev"; 124 + }; 125 + certs."bwc9876.dev" = { 126 + domain = "*.bwc9876.dev"; 127 + extraDomainNames = [ 128 + "knot.bwc9876.dev" 129 + ]; 130 + reloadServices = [ 131 + "nginx" 132 + ]; 133 + group = config.services.nginx.group; 134 + dnsProvider = "porkbun"; 135 + environmentFile = "/nix/persist/secure/porkbun.env"; 136 + }; 137 + }; 138 + } 139 + ) 95 140 { 96 - # for WOL 141 + # WOL 97 142 systemd.network.links."79-eth-wol" = { 98 143 matchConfig = { 99 144 Type = "ether";
+63
nixosModules/tangled.nix
··· 1 + {inputs, ...}: { 2 + config, 3 + pkgs, 4 + lib, 5 + ... 6 + }: { 7 + imports = [inputs.tangled.nixosModules.knot]; 8 + 9 + options.cow.tangled = { 10 + hostname = lib.mkOption { 11 + type = lib.types.str; 12 + description = "virtual host for knot and spindle"; 13 + default = "knot.bwc9876.dev"; 14 + }; 15 + knot = { 16 + enable = lib.mkEnableOption "tangled knot service"; 17 + }; 18 + }; 19 + 20 + config = let 21 + conf = config.cow.tangled; 22 + knotStateDir = "/var/lib/tangled-knot"; 23 + gitUser = "gurt"; 24 + in { 25 + cow.imperm.keep = lib.optional conf.knot.enable knotStateDir; 26 + 27 + services.tangled = { 28 + knot = lib.mkIf conf.knot.enable { 29 + enable = true; 30 + openFirewall = false; 31 + inherit gitUser; 32 + stateDir = knotStateDir; 33 + repo.scanPath = "${config.services.tangled.knot.stateDir}/repos"; 34 + motdFile = ../res/bleh.txt; 35 + server = { 36 + # Pub Port: 5555, Internal Port: 5444 37 + hostname = lib.mkDefault conf.hostname; 38 + owner = lib.mkIf config.cow.bean.enable (lib.mkDefault config.cow.bean.atproto.did); 39 + }; 40 + }; 41 + }; 42 + 43 + services.nginx.virtualHosts.${conf.hostname} = lib.mkIf conf.knot.enable { 44 + locations = { 45 + "/" = { 46 + proxyPass = "http://localhost:5555"; 47 + recommendedProxySettings = true; 48 + }; 49 + "/events" = { 50 + proxyPass = "http://localhost:5555"; 51 + proxyWebsockets = true; 52 + recommendedProxySettings = true; 53 + }; 54 + }; 55 + }; 56 + 57 + services.openssh = lib.mkIf conf.knot.enable { 58 + enable = true; 59 + settings.AllowUsers = [gitUser]; 60 + settings.AllowGroups = [gitUser]; 61 + }; 62 + }; 63 + }
+6
nixosModules/user-bean.nix
··· 5 5 ... 6 6 }: let 7 7 pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKsVzdJra+x5aEuwTjL1FBOiMh9bftvs8QwsM1xyEbdd"; 8 + did = "did:plc:x7tlupbnqot7nu6udnffnv4h"; 8 9 in { 9 10 options.cow.bean = { 10 11 enable = lib.mkEnableOption "Bean user"; ··· 13 14 type = lib.types.nullOr lib.types.str; 14 15 description = "Public Key to Add for Bean"; 15 16 default = pubkey; 17 + }; 18 + atproto.did = lib.mkOption { 19 + type = lib.types.str; 20 + description = "DID for the bean user"; 21 + default = did; 16 22 }; 17 23 }; 18 24
+32
res/bleh.txt
··· 1 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢠⡀⢠⡄⠀⡤⠀⣤⡀⠄⠀⠀⢠⡀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣤⠤⠤⠤⢤⣤⠠⠠⡄⠠⠄⣤⡤⢤⣤⡤⠤⠤⣤⠀ 2 + ⠀⠲⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣤⣿⡆⢹⡄⢹⣓⡒⠀⠀⠘⢏⡳⢞⢲⡖⡶⠆⣾⣸⠀⠀⠀⠀⠀⠀⠀⠀⠰⣿⣿⣿⡂⢸⣿⣿⣶⡌⢋⡑⠀⣠⣼⣿⣄⡤⡤⠀⠀ 3 + ⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠋⣉⣛⠀⠁⠐⣘⢢⠀⠀⠀⠛⡍⠉⠡⠐⠀⠛⠛⠘⠀⠀⠀⢠⣬⠀⠆⠀⣿⣿⣿⣿⣿⣿⡟⢿⣿⣿⠆⢈⣛⣀⡘⠀⣰⡶⠀⠀⠀ 4 + ⠀⠱⣆⡀⠀⢁⡄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣿⣤⠘⣁⠨⣗⠐⠶⠤⠀⡤⠤⠀⠀⠀⣘⣂⡀⠀⠀⠀⣌⠁⠆⠀⢠⣿⡿⣿⣯⣾⣿⠿⢛⣩⢴⣾⣯⡥⠴⣾⡿⡄⠀⠀⠀⠀ 5 + ⠀⠀⠉⠉⠀⠈⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⠗⠀⠉⠀⠊⠶⠀⠀⠀⠁⢀⠀⠀⠀⢹⡿⠃⠀⠀⠀⠙⠆⠀⠀⠐⣿⣿⢿⡟⠉⢀⡶⠟⢁⣈⣶⢆⡉⢰⣿⠐⠀⠀⠀⢀⠀ 6 + ⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣄⡀⠀⢀⠀⠀⠁⠀⢁⠀⠐⣶⣠⣄⢀⣩⣦⣦⣤⣈⣁⣀⣤⣦⣼⣆⣼⣄⣤⣶⣿⠉⣈⢡⡶⣯⣵⣾⣿⣿⣿⣮⣟⣻⡏⠁⠀⠀⠀⠋⠐ 7 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢾⣿⣿⣶⣶⣶⣽⣤⣮⣼⣦⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡾⣫⣿⣾⣿⣿⣿⣿⣿⣻⣿⣶⠟⠁⠀⠀⡐⠁⠀⠀ 8 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⡿⣡⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠌⠀⠀⠀⠊⢄⢛⣓⠓ 9 + ⠀⠀⠠⠁⡀⠂⠀⠀⠀⠀⠀⠀⠀⢴⣶⣿⣿⣿⣿⣿⣿⣿⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠿⠃⠀⠀⢀⠐⠌⡂⠦⣌⢏ 10 + ⠀⠠⠁⠂⠄⡐⠀⠄⠀⠀⠀⡒⠶⢿⣿⣿⠟⠛⠛⠛⠛⠿⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⡄⠀⢀⠠⢀⠊⠤⡙⢦⡙⣮ 11 + ⠀⣂⠑⠈⠐⠀⠀⠀⠀⢰⠤⣣⣾⣿⣿⣿⣶⣦⣤⣤⣀⣀⣀⠀⠙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠛⠛⠉⠉⠉⠉⠉⡛⠻⠿⠿⠀⠀⠀⢁⠂⡘⠤⣓⡬⣷⣿ 12 + ⠀⢀⠀⠀⠉⠀⠀⠙⠁⢀⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠠⣈⠥⣳⡜⣷⣿⣿ 13 + ⠀⡈⠔⠂⠀⠀⠀⡔⠚⠟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢰⢣⢞⣱⣾⣿⣿⣿ 14 + ⠀⠈⠀⠀⠀⠀⠀⢀⣀⣠⡽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠻⢿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡝⣎⢯⣻⣿⣿⣿⣿ 15 + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠛⠛⠻⠛⡟⣿⢻⠤⡤⠄⠈⠈⠉⠉⠁⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣏⢣⡈⢼⡡⣿⣿⣿⣿ 16 + ⠀⠀⠡⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠈⣁⢛⡞⠲⠃⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣭⣝⣋⣛⣺⢷⠿⣿⣿⣿ 17 + ⠀⡂⠀⠠⠈⠀⣀⣠⣤⣤⡤⠴⠶⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⡀⠘⠄⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⡿⠿⠟⠟⠂⠤⠉⠯⢍⣙⣻⢿⠿⣻⣿ 18 + ⠀⣤⡴⠖⠋⠉⠉⢉⣁⣠⣤⡴⠶⠾⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⢋⡛⠛⠳⠶⢶⣬⣄⣊⣘⠉⠟⡿⢿ 19 + ⠀⣛⣠⣶⡶⠞⠛⠛⠉⠉⣀⡤⠴⢒⡒⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣿⣿⣶⡿⠦⠁⠀⢐⢚⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣣⣴⣶⣤⣌⣛⠓⠶⢤⣄⣀⡈⠉⠛⠛⠿⢶⣾ 20 + ⠀⠛⠉⠀⠀⢀⣤⣴⣞⠫⠝⠒⠉⣡⢤⢸⣿⣿⣿⣿⣿⣿⣿⣻⣿⡿⠿⠟⠉⠀⠀⠀⠀⠠⣚⣿⠻⣿⣿⣿⣿⣿⣿⠛⢻⡽⢯⣿⠛⠁⢿⡙⠛⠾⢷⣶⡌⡙⠛⠴⡆⠤⠀⠀⠀ 21 + ⠀⠀⠀⣠⠶⠋⠉⠀⢀⢠⠠⠖⠋⠁⠠⣾⣿⣿⣿⣿⣿⣿⡇⢉⣀⣠⣤⣠⣾⣷⣶⣦⣷⣾⣿⣿⡧⠈⣉⡉⠛⠋⠁⢶⠮⣹⠏⠀⠀⠀⠀⠈⠉⠒⢤⣈⠉⠛⠳⣤⣤⣁⠲⢅⡀ 22 + ⠀⡴⠛⠉⢀⠠⡐⠰⠉⠀⡀⠄⡀⠤⢰⣿⣿⣿⣿⣿⣿⡿⢡⣾⡿⢟⣿⣿⣿⣿⣿⣿⣿⣿⣿⠘⣡⣾⣿⣿⣦⠈⠏⡏⣸⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠂⠄⠀⠈⠛⢇⡀⠩ 23 + ⠀⠀⣠⠰⠌⠂⠁⡀⠄⠡⠐⢠⠀⠁⢸⣿⣿⣿⣿⡿⠏⣠⠞⣧⣻⣿⣽⣻⣿⣿⣿⣿⣿⡿⣟⣼⣟⣛⣿⣯⣿⣿⠂⣼⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠂ 24 + ⠀⠡⠄⡁⠂⠔⡀⠐⣈⠡⠘⢄⠂⠀⢘⣿⣿⣿⣿⠋⣀⢧⡛⣬⢳⡽⣏⡿⣶⣿⣻⣿⢿⣟⣭⣿⢿⣯⢻⢵⢯⢿⡆⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠂⠄⠀ 25 + ⠀⠁⠒⠀⠉⠐⠀⠁⠐⡈⠱⠈⠆⠀⢈⣿⣿⣿⡇⢰⠉⠖⠹⢈⢳⡘⣁⠻⣉⠻⢿⡿⢿⡟⡿⣹⡏⡾⣉⡏⠾⣏⡙⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 26 + ⠀⠡⠒⡀⠎⣁⠂⠆⢢⠁⢆⠒⡈⠅⠘⣻⣿⣿⠁⣏⠷⣘⢧⢺⣌⢳⡙⣦⢋⡷⢺⡗⣾⣟⡷⣯⣽⣳⣭⡛⣶⠯⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡀⠄⠀⠄⠐⠀⠀⠀ 27 + ⠀⢡⠘⠤⡑⠠⠌⡘⠤⡉⠦⡑⣌⠸⡀⠘⣿⣿⡆⣭⢻⡜⢎⡵⣌⢳⡙⣆⠫⡜⣽⣻⣵⢯⡿⣽⡷⢯⣞⠽⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⡐⠈⠀⢀⠀⠀⠀ 28 + ⠀⢢⠘⡰⢈⠱⡈⡔⠡⢎⡱⢡⠆⡣⢽⡄⢙⣿⡇⠌⡳⣜⢣⡜⡜⢦⡹⣌⢳⡙⡶⣿⣞⣯⢿⡱⣭⢛⠈⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠐⠈⢀⠀⠀⠀⠀ 29 + ⠀⢠⠃⡔⡈⢆⠱⣈⢑⡊⡔⢣⢜⡰⢣⣞⡄⢹⡧⠀⣳⢬⡓⡼⡙⢎⡱⢎⡣⣝⡳⣿⡽⢞⡫⡕⠎⢈⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠌⡀⢀⠈⠀⠀ 30 + ⠀⠀⠈⠰⠡⠊⠔⠂⠧⠜⠜⠲⠎⠭⠣⠎⠡⠴⠿⠥⠡⠬⠳⠱⠙⠎⠱⠃⠹⠘⠱⠁⠌⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠂⠐⠀⠠⠀⠀ 31 + 32 +