forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: let 7 cfg = config.services.tangled-knot; 8in 9 with lib; { 10 options = { 11 services.tangled-knot = { 12 enable = mkOption { 13 type = types.bool; 14 default = false; 15 description = "Enable a tangled knot"; 16 }; 17 18 package = mkOption { 19 type = types.package; 20 description = "Package to use for the knot"; 21 }; 22 23 appviewEndpoint = mkOption { 24 type = types.str; 25 default = "https://tangled.sh"; 26 description = "Appview endpoint"; 27 }; 28 29 gitUser = mkOption { 30 type = types.str; 31 default = "git"; 32 description = "User that hosts git repos and performs git operations"; 33 }; 34 35 openFirewall = mkOption { 36 type = types.bool; 37 default = true; 38 description = "Open port 22 in the firewall for ssh"; 39 }; 40 41 stateDir = mkOption { 42 type = types.path; 43 default = "/home/${cfg.gitUser}"; 44 description = "Tangled knot data directory"; 45 }; 46 47 repo = { 48 scanPath = mkOption { 49 type = types.path; 50 default = cfg.stateDir; 51 description = "Path where repositories are scanned from"; 52 }; 53 54 mainBranch = mkOption { 55 type = types.str; 56 default = "main"; 57 description = "Default branch name for repositories"; 58 }; 59 }; 60 61 server = { 62 listenAddr = mkOption { 63 type = types.str; 64 default = "0.0.0.0:5555"; 65 description = "Address to listen on"; 66 }; 67 68 internalListenAddr = mkOption { 69 type = types.str; 70 default = "127.0.0.1:5444"; 71 description = "Internal address for inter-service communication"; 72 }; 73 74 secretFile = mkOption { 75 type = lib.types.path; 76 example = "KNOT_SERVER_SECRET=<hash>"; 77 description = "File containing secret key provided by appview (required)"; 78 }; 79 80 dbPath = mkOption { 81 type = types.path; 82 default = "${cfg.stateDir}/knotserver.db"; 83 description = "Path to the database file"; 84 }; 85 86 hostname = mkOption { 87 type = types.str; 88 example = "knot.tangled.sh"; 89 description = "Hostname for the server (required)"; 90 }; 91 92 dev = mkOption { 93 type = types.bool; 94 default = false; 95 description = "Enable development mode (disables signature verification)"; 96 }; 97 }; 98 }; 99 }; 100 101 config = mkIf cfg.enable { 102 environment.systemPackages = [ 103 pkgs.git 104 cfg.package 105 ]; 106 107 system.activationScripts.gitConfig = '' 108 mkdir -p "${cfg.repo.scanPath}" 109 chown -R ${cfg.gitUser}:${cfg.gitUser} "${cfg.repo.scanPath}" 110 111 mkdir -p "${cfg.stateDir}/.config/git" 112 cat > "${cfg.stateDir}/.config/git/config" << EOF 113 [user] 114 name = Git User 115 email = git@example.com 116 [receive] 117 advertisePushOptions = true 118 EOF 119 chown -R ${cfg.gitUser}:${cfg.gitUser} "${cfg.stateDir}" 120 ''; 121 122 users.users.${cfg.gitUser} = { 123 isSystemUser = true; 124 useDefaultShell = true; 125 home = cfg.stateDir; 126 createHome = true; 127 group = cfg.gitUser; 128 }; 129 130 users.groups.${cfg.gitUser} = {}; 131 132 services.openssh = { 133 enable = true; 134 extraConfig = '' 135 Match User ${cfg.gitUser} 136 AuthorizedKeysCommand /etc/ssh/keyfetch_wrapper 137 AuthorizedKeysCommandUser nobody 138 ''; 139 }; 140 141 environment.etc."ssh/keyfetch_wrapper" = { 142 mode = "0555"; 143 text = '' 144 #!${pkgs.stdenv.shell} 145 ${cfg.package}/bin/knot keys \ 146 -output authorized-keys \ 147 -internal-api "http://${cfg.server.internalListenAddr}" \ 148 -git-dir "${cfg.repo.scanPath}" \ 149 -log-path /tmp/knotguard.log 150 ''; 151 }; 152 153 systemd.services.knot = { 154 description = "knot service"; 155 after = ["network.target" "sshd.service"]; 156 wantedBy = ["multi-user.target"]; 157 serviceConfig = { 158 User = cfg.gitUser; 159 WorkingDirectory = cfg.stateDir; 160 Environment = [ 161 "KNOT_REPO_SCAN_PATH=${cfg.repo.scanPath}" 162 "KNOT_REPO_MAIN_BRANCH=${cfg.repo.mainBranch}" 163 "APPVIEW_ENDPOINT=${cfg.appviewEndpoint}" 164 "KNOT_SERVER_INTERNAL_LISTEN_ADDR=${cfg.server.internalListenAddr}" 165 "KNOT_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}" 166 "KNOT_SERVER_DB_PATH=${cfg.server.dbPath}" 167 "KNOT_SERVER_HOSTNAME=${cfg.server.hostname}" 168 ]; 169 EnvironmentFile = cfg.server.secretFile; 170 ExecStart = "${cfg.package}/bin/knot server"; 171 Restart = "always"; 172 }; 173 }; 174 175 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [22]; 176 }; 177 }