Monorepo for Tangled
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 293 lines 8.9 kB view raw
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.org"; 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 readme = mkOption { 55 type = types.listOf types.str; 56 default = [ 57 "README.md" 58 "readme.md" 59 "README" 60 "readme" 61 "README.markdown" 62 "readme.markdown" 63 "README.txt" 64 "readme.txt" 65 "README.rst" 66 "readme.rst" 67 "README.org" 68 "readme.org" 69 "README.asciidoc" 70 "readme.asciidoc" 71 ]; 72 description = "List of README filenames to look for (in priority order)"; 73 }; 74 75 mainBranch = mkOption { 76 type = types.str; 77 default = "main"; 78 description = "Default branch name for repositories"; 79 }; 80 }; 81 82 git = { 83 userName = mkOption { 84 type = types.str; 85 default = "Tangled"; 86 description = "Git user name used as committer"; 87 }; 88 89 userEmail = mkOption { 90 type = types.str; 91 default = "noreply@tangled.org"; 92 description = "Git user email used as committer"; 93 }; 94 }; 95 96 motd = mkOption { 97 type = types.nullOr types.str; 98 default = null; 99 description = '' 100 Message of the day 101 102 The contents are shown as-is; eg. you will want to add a newline if 103 setting a non-empty message since the knot won't do this for you. 104 ''; 105 }; 106 107 motdFile = mkOption { 108 type = types.nullOr types.path; 109 default = null; 110 description = '' 111 File containing message of the day 112 113 The contents are shown as-is; eg. you will want to add a newline if 114 setting a non-empty message since the knot won't do this for you. 115 ''; 116 }; 117 118 knotmirrors = mkOption { 119 type = types.listOf types.str; 120 default = [ 121 "https://mirror.tangled.network" 122 ]; 123 description = "List of knotmirror hosts to request crawl"; 124 }; 125 126 server = { 127 listenAddr = mkOption { 128 type = types.str; 129 default = "0.0.0.0:5555"; 130 description = "Address to listen on"; 131 }; 132 133 internalListenAddr = mkOption { 134 type = types.str; 135 default = "127.0.0.1:5444"; 136 description = "Internal address for inter-service communication"; 137 }; 138 139 owner = mkOption { 140 type = types.str; 141 example = "did:plc:qfpnj4og54vl56wngdriaxug"; 142 description = "DID of owner (required)"; 143 }; 144 145 dbPath = mkOption { 146 type = types.path; 147 default = "${cfg.stateDir}/knotserver.db"; 148 description = "Path to the database file"; 149 }; 150 151 hostname = mkOption { 152 type = types.str; 153 example = "my.knot.com"; 154 description = "Hostname for the server (required)"; 155 }; 156 157 plcUrl = mkOption { 158 type = types.str; 159 default = "https://plc.directory"; 160 description = "atproto PLC directory"; 161 }; 162 163 jetstreamEndpoint = mkOption { 164 type = types.str; 165 default = "wss://jetstream1.us-west.bsky.network/subscribe"; 166 description = "Jetstream endpoint to subscribe to"; 167 }; 168 169 logDids = mkOption { 170 type = types.bool; 171 default = true; 172 description = "Enable logging of DIDs"; 173 }; 174 175 dev = mkOption { 176 type = types.bool; 177 default = false; 178 description = "Enable development mode (disables signature verification)"; 179 }; 180 }; 181 }; 182 }; 183 184 config = mkIf cfg.enable { 185 environment.systemPackages = [ 186 pkgs.git 187 cfg.package 188 ]; 189 190 users.users.${cfg.gitUser} = { 191 isSystemUser = true; 192 useDefaultShell = true; 193 home = cfg.stateDir; 194 createHome = true; 195 group = cfg.gitUser; 196 }; 197 198 users.groups.${cfg.gitUser} = {}; 199 200 services.openssh = { 201 enable = true; 202 extraConfig = '' 203 Match User ${cfg.gitUser} 204 AuthorizedKeysCommand /etc/ssh/keyfetch_wrapper 205 AuthorizedKeysCommandUser nobody 206 ChallengeResponseAuthentication no 207 PasswordAuthentication no 208 ''; 209 }; 210 211 environment.etc."ssh/keyfetch_wrapper" = { 212 mode = "0555"; 213 text = '' 214 #!${pkgs.stdenv.shell} 215 ${cfg.package}/bin/knot keys \ 216 -output authorized-keys \ 217 -internal-api "http://${cfg.server.internalListenAddr}" \ 218 -git-dir "${cfg.repo.scanPath}" \ 219 -log-path /tmp/knotguard.log 220 ''; 221 }; 222 223 systemd.services.knot = { 224 description = "knot service"; 225 after = ["network.target" "sshd.service"]; 226 wantedBy = ["multi-user.target"]; 227 enableStrictShellChecks = true; 228 229 preStart = let 230 setMotd = 231 if cfg.motdFile != null && cfg.motd != null 232 then throw "motdFile and motd cannot be both set" 233 else '' 234 ${optionalString (cfg.motdFile != null) "cat ${cfg.motdFile} > ${cfg.stateDir}/motd"} 235 ${optionalString (cfg.motd != null) ''printf "${cfg.motd}" > ${cfg.stateDir}/motd''} 236 ''; 237 in '' 238 mkdir -p "${cfg.repo.scanPath}" 239 chown -R ${cfg.gitUser}:${cfg.gitUser} "${cfg.repo.scanPath}" 240 241 mkdir -p "${cfg.stateDir}/.config/git" 242 cat > "${cfg.stateDir}/.config/git/config" << EOF 243 [user] 244 name = ${cfg.git.userName} 245 email = ${cfg.git.userEmail} 246 [receive] 247 advertisePushOptions = true 248 [uploadpack] 249 allowFilter = true 250 allowReachableSHA1InWant = true 251 EOF 252 ${setMotd} 253 chown -R ${cfg.gitUser}:${cfg.gitUser} "${cfg.stateDir}" 254 ''; 255 256 serviceConfig = { 257 User = cfg.gitUser; 258 PermissionsStartOnly = true; 259 WorkingDirectory = cfg.stateDir; 260 Environment = [ 261 "KNOT_REPO_SCAN_PATH=${cfg.repo.scanPath}" 262 "KNOT_REPO_README=${concatStringsSep "," cfg.repo.readme}" 263 "KNOT_REPO_MAIN_BRANCH=${cfg.repo.mainBranch}" 264 "KNOT_GIT_USER_NAME=${cfg.git.userName}" 265 "KNOT_GIT_USER_EMAIL=${cfg.git.userEmail}" 266 "APPVIEW_ENDPOINT=${cfg.appviewEndpoint}" 267 "KNOT_SERVER_INTERNAL_LISTEN_ADDR=${cfg.server.internalListenAddr}" 268 "KNOT_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}" 269 "KNOT_SERVER_DB_PATH=${cfg.server.dbPath}" 270 "KNOT_SERVER_HOSTNAME=${cfg.server.hostname}" 271 "KNOT_SERVER_PLC_URL=${cfg.server.plcUrl}" 272 "KNOT_SERVER_JETSTREAM_ENDPOINT=${cfg.server.jetstreamEndpoint}" 273 "KNOT_SERVER_OWNER=${cfg.server.owner}" 274 "KNOT_MIRRORS=${concatStringsSep "," cfg.knotmirrors}" 275 "KNOT_SERVER_LOG_DIDS=${ 276 if cfg.server.logDids 277 then "true" 278 else "false" 279 }" 280 "KNOT_SERVER_DEV=${ 281 if cfg.server.dev 282 then "true" 283 else "false" 284 }" 285 ]; 286 ExecStart = "${cfg.package}/bin/knot server"; 287 Restart = "always"; 288 }; 289 }; 290 291 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [22]; 292 }; 293 }