This repository has no description
0

Configure Feed

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

configs.services.git: tangled knot and appview on git.lassul.us

+322
+2
configs/dns/lassul.us.zone
··· 71 71 *.pds IN CNAME neoprism 72 72 ;; DNS-01 for the *.pds wildcard cert (PDS handles), dynamic zone in knot.nix 73 73 _acme-challenge.pds IN NS ns1 74 + git IN CNAME neoprism 75 + knot IN CNAME neoprism 74 76 when IN CNAME neoprism 75 77 paperless IN CNAME neoprism 76 78
+138
configs/services/git/appview.nix
··· 1 + { 2 + self, 3 + config, 4 + lib, 5 + pkgs, 6 + ... 7 + }: 8 + let 9 + domain = "git.lassul.us"; 10 + port = 3010; 11 + redisPort = 6380; 12 + listenAddr = "127.0.0.1:${toString port}"; 13 + in 14 + { 15 + # Tangled appview: the web UI for repos on knot.lassul.us (knot.nix) and 16 + # other knots. Users log in with their atproto account via OAuth to their 17 + # PDS. Records come from Bluesky's jetstream and repo contents from the 18 + # public knotmirror (mirror.tangled.network), both upstream defaults. 19 + # Follows tangled's nix/modules/appview.nix, without its flake (the module 20 + # files are in a fetched source, importing them would be IFD). 21 + 22 + clan.core.vars.generators.tangled-appview = { 23 + files."appview.env" = { }; 24 + runtimeInputs = with pkgs; [ 25 + atproto-goat 26 + coreutils 27 + gawk 28 + gnugrep 29 + openssl 30 + ]; 31 + script = '' 32 + { 33 + echo "TANGLED_COOKIE_SECRET=$(openssl rand -hex 32)" 34 + echo "TANGLED_OAUTH_CLIENT_KID=$(date +%s)" 35 + echo "TANGLED_OAUTH_CLIENT_SECRET=$(goat key generate -t P-256 | grep -A1 'Secret Key' | tail -n1 | awk '{print $1}')" 36 + } > "$out/appview.env" 37 + ''; 38 + }; 39 + 40 + # 6379 is docuseal's 41 + services.redis.servers.appview = { 42 + enable = true; 43 + port = redisPort; 44 + }; 45 + 46 + systemd.services.appview = { 47 + description = "tangled appview service"; 48 + wantedBy = [ "multi-user.target" ]; 49 + after = [ 50 + "redis-appview.service" 51 + "network-online.target" 52 + ]; 53 + requires = [ "redis-appview.service" ]; 54 + wants = [ "network-online.target" ]; 55 + path = [ pkgs.diffutils ]; 56 + serviceConfig = { 57 + Type = "simple"; 58 + ExecStart = "${self.packages.${pkgs.stdenv.hostPlatform.system}.tangled.appview}/bin/appview"; 59 + Restart = "always"; 60 + RestartSec = "10s"; 61 + EnvironmentFile = config.clan.core.vars.generators.tangled-appview.files."appview.env".path; 62 + # upstream runs it as root 63 + DynamicUser = true; 64 + StateDirectory = "appview"; 65 + WorkingDirectory = "/var/lib/appview"; 66 + NoNewPrivileges = true; 67 + PrivateTmp = true; 68 + ProtectSystem = "strict"; 69 + ProtectHome = true; 70 + ReadWritePaths = [ "/var/lib/appview" ]; 71 + }; 72 + # upstream's defaults, except where commented 73 + environment = { 74 + TANGLED_DB_PATH = "/var/lib/appview/appview.db"; 75 + TANGLED_LISTEN_ADDR = listenAddr; 76 + TANGLED_METRICS_LISTEN_ADDR = "127.0.0.1:${toString (port + 1)}"; 77 + TANGLED_APPVIEW_HOST = domain; 78 + TANGLED_APPVIEW_NAME = domain; 79 + TANGLED_DEV = "false"; 80 + TANGLED_REDIS_ADDR = "127.0.0.1:${toString redisPort}"; 81 + TANGLED_REDIS_DB = "0"; 82 + TANGLED_JETSTREAM_ENDPOINT = "wss://jetstream1.us-east.bsky.network/subscribe"; 83 + TANGLED_KNOTSTREAM_RETRY_INTERVAL = "60s"; 84 + TANGLED_KNOTSTREAM_MAX_RETRY_INTERVAL = "120m"; 85 + TANGLED_KNOTSTREAM_CONNECTION_TIMEOUT = "5s"; 86 + TANGLED_KNOTSTREAM_WORKER_COUNT = "64"; 87 + TANGLED_KNOTSTREAM_QUEUE_SIZE = "100"; 88 + TANGLED_SPINDLESTREAM_RETRY_INTERVAL = "60s"; 89 + TANGLED_SPINDLESTREAM_MAX_RETRY_INTERVAL = "120m"; 90 + TANGLED_SPINDLESTREAM_CONNECTION_TIMEOUT = "5s"; 91 + TANGLED_SPINDLESTREAM_WORKER_COUNT = "64"; 92 + TANGLED_SPINDLESTREAM_QUEUE_SIZE = "100"; 93 + TANGLED_RESEND_SENT_FROM = "noreply@notifs.tangled.sh"; 94 + # sends logins, searches and pushes even without an API key 95 + TANGLED_POSTHOG_ENDPOINT = "http://127.0.0.1:9"; 96 + TANGLED_CAMO_HOST = "https://camo.tangled.sh"; 97 + TANGLED_AVATAR_HOST = "https://avatar.tangled.sh"; 98 + TANGLED_PLC_URL = "https://plc.directory"; 99 + TANGLED_PDS_HOST = "https://tngl.sh"; 100 + TANGLED_LABEL_DEFAULTS = 101 + lib.concatMapStringsSep "," 102 + (l: "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/${l}") 103 + [ 104 + "wontfix" 105 + "good-first-issue" 106 + "duplicate" 107 + "documentation" 108 + "assignee" 109 + ]; 110 + TANGLED_LABEL_GFI = "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/good-first-issue"; 111 + # probed at every login, upstream's is knot1.tangled.sh 112 + TANGLED_KNOT_DEFAULT = "knot.lassul.us"; 113 + # OG images of repos, issues and pulls, rendered by tangled 114 + TANGLED_OGRE_HOST = "http://127.0.0.1:9"; 115 + }; 116 + }; 117 + 118 + # redis only caches 119 + lass.backup.state.tangled-appview = { 120 + preBackup = '' 121 + ${pkgs.sqlite}/bin/sqlite3 -cmd ".timeout 60000" /var/lib/private/appview/appview.db ".backup /var/backup/tangled-appview.db" 122 + ''; 123 + # WAL-mode SQLite readers need to write the -shm file 124 + readWritePaths = [ "/var/lib/private/appview" ]; 125 + }; 126 + 127 + services.nginx.virtualHosts.${domain} = { 128 + enableACME = true; 129 + forceSSL = true; 130 + locations."/" = { 131 + proxyPass = "http://${listenAddr}"; 132 + recommendedProxySettings = true; 133 + proxyWebsockets = true; 134 + }; 135 + # creates tngl.sh accounts, which needs tangled's PDS admin secret 136 + locations."/signup".return = "404"; 137 + }; 138 + }
+2
configs/services/git/default.nix
··· 1 1 { 2 2 imports = [ 3 + ./appview.nix 4 + ./knot.nix 3 5 ./pds.nix 4 6 ]; 5 7 }
+180
configs/services/git/knot.nix
··· 1 + { 2 + self, 3 + config, 4 + lib, 5 + pkgs, 6 + ... 7 + }: 8 + let 9 + domain = "knot.lassul.us"; 10 + # DID of the owner's account on pds.lassul.us; the knot stays off while null 11 + ownerDid = null; 12 + user = "tangled-knot"; 13 + stateDir = "/var/lib/tangled-knot"; 14 + listenAddr = "127.0.0.1:5555"; 15 + sshPort = 2222; 16 + package = self.packages.${pkgs.stdenv.hostPlatform.system}.tangled.knot-rs; 17 + 18 + # `knot-server config-template` lists the keys; upstream's defaults, except 19 + # where commented 20 + rendered = (pkgs.formats.toml { }).generate "knot.toml" { 21 + server = { 22 + hostname = domain; 23 + admins = [ ownerDid ]; 24 + appview_endpoint = "https://git.lassul.us"; 25 + listen_addr = listenAddr; 26 + internal_listen_addr = "[::1]:5444"; 27 + ssh_listen_addr = "[::]:${toString sshPort}"; 28 + ssh_host_key_file = "${stateDir}/ssh_host_key"; 29 + }; 30 + atproto.plc_directory = "https://plc.directory"; 31 + acl.admission = "closed"; 32 + repo.scan_path = "${stateDir}/repos"; 33 + # sha256 repos can't exchange objects with sha1 ones (github, cgit) 34 + git.object_format = "sha1"; 35 + secrets = { 36 + sealed_key_file = "${stateDir}/sealed-keys"; 37 + master_key_env = "KNOT_MASTER_KEY"; 38 + }; 39 + tls = { 40 + http3 = true; 41 + acme_enabled = false; 42 + acme_cache_dir = "${stateDir}/acme"; 43 + acme_staging = false; 44 + mtls_enabled = false; 45 + }; 46 + xrpc = { 47 + trusted_proxy_header = "x-forwarded-for"; 48 + trusted_proxies = [ 49 + "127.0.0.1" 50 + "::1" 51 + ]; 52 + }; 53 + }; 54 + configFile = pkgs.runCommandLocal "knot-config.toml" { nativeBuildInputs = [ package ]; } '' 55 + knot-server validate --config-only ${rendered} 56 + ln -s ${rendered} $out 57 + ''; 58 + in 59 + { 60 + # Tangled knot 2 (knot-rs): git hosting for git.lassul.us, following 61 + # tangled's nix/modules/knot-rs.nix without its flake (the module files are 62 + # in a fetched source, importing them would be IFD): plain HTTP behind 63 + # nginx, unprivileged ports, keys in stateDir. The Go knot's unit and user 64 + # are both named knot, which the DNS server already uses. 65 + # Knot 2 serves ssh itself, on port 2222 since sshd keeps 22, so clone URLs 66 + # need ssh://git@knot.lassul.us:2222/... or a Port line in ~/.ssh/config. 67 + 68 + config = lib.mkIf (ownerDid != null) { 69 + clan.core.vars.generators.tangled-knot = { 70 + files."knot.env" = { }; 71 + runtimeInputs = [ pkgs.openssl ]; 72 + script = '' 73 + echo "KNOT_MASTER_KEY=$(openssl rand -base64 32)" > "$out/knot.env" 74 + ''; 75 + }; 76 + 77 + environment.systemPackages = [ package ]; 78 + environment.etc."knot/config.toml".source = configFile; 79 + 80 + users.users.${user} = { 81 + isSystemUser = true; 82 + home = stateDir; 83 + group = user; 84 + }; 85 + users.groups.${user} = { }; 86 + 87 + systemd.tmpfiles.settings."10-knot-rs" = { 88 + ${stateDir}.d = { 89 + mode = "0750"; 90 + inherit user; 91 + group = user; 92 + }; 93 + "${stateDir}/repos".d = { 94 + mode = "0750"; 95 + inherit user; 96 + group = user; 97 + }; 98 + }; 99 + 100 + systemd.services.knot-rs = { 101 + description = "knot git server"; 102 + after = [ "network-online.target" ]; 103 + wants = [ "network-online.target" ]; 104 + wantedBy = [ "multi-user.target" ]; 105 + restartTriggers = [ configFile ]; 106 + startLimitIntervalSec = 60; 107 + startLimitBurst = 5; 108 + serviceConfig = { 109 + User = user; 110 + Group = user; 111 + UMask = "0077"; 112 + WorkingDirectory = stateDir; 113 + EnvironmentFile = config.clan.core.vars.generators.tangled-knot.files."knot.env".path; 114 + ExecStart = "${lib.getExe package} /etc/knot/config.toml"; 115 + Restart = "on-failure"; 116 + RestartSec = 5; 117 + TimeoutStopSec = 120; 118 + LimitNOFILE = 65536; 119 + CapabilityBoundingSet = [ ]; 120 + ReadWritePaths = [ 121 + stateDir 122 + "${stateDir}/repos" 123 + ]; 124 + NoNewPrivileges = true; 125 + ProtectProc = "invisible"; 126 + ProtectSystem = "strict"; 127 + ProtectHome = true; 128 + PrivateTmp = true; 129 + PrivateDevices = true; 130 + PrivateUsers = true; 131 + ProtectHostname = true; 132 + ProtectClock = true; 133 + ProtectKernelTunables = true; 134 + ProtectKernelModules = true; 135 + ProtectKernelLogs = true; 136 + ProtectControlGroups = true; 137 + RestrictAddressFamilies = [ 138 + "AF_INET" 139 + "AF_INET6" 140 + "AF_NETLINK" 141 + "AF_UNIX" 142 + ]; 143 + RestrictNamespaces = true; 144 + LockPersonality = true; 145 + MemoryDenyWriteExecute = true; 146 + RestrictRealtime = true; 147 + RestrictSUIDSGID = true; 148 + RemoveIPC = true; 149 + PrivateMounts = true; 150 + SystemCallFilter = [ 151 + "@system-service" 152 + "~@privileged @resources" 153 + ]; 154 + SystemCallArchitectures = "native"; 155 + }; 156 + }; 157 + 158 + networking.firewall.allowedTCPPorts = [ sshPort ]; 159 + 160 + # repos (with the ACL data), sealed keys and ssh host key; the master key 161 + # is in vars 162 + lass.backup.state.tangled-knot.folders = [ stateDir ]; 163 + 164 + services.nginx.virtualHosts.${domain} = { 165 + enableACME = true; 166 + forceSSL = true; 167 + locations."/" = { 168 + proxyPass = "http://${listenAddr}"; 169 + recommendedProxySettings = true; 170 + # /events 171 + proxyWebsockets = true; 172 + extraConfig = '' 173 + proxy_read_timeout 1d; 174 + client_max_body_size 0; 175 + proxy_request_buffering off; 176 + ''; 177 + }; 178 + }; 179 + }; 180 + }