diff --git a/.gitignore b/.gitignore index 168fae6fe..c51843f8a 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ id_ed25519 .playwright-mcp/ .browser-artifacts + +/.localinfra/ +localinfra/pc/process-compose.override.yaml diff --git a/flake.lock b/flake.lock index f8f574a5c..a59329f1b 100644 --- a/flake.lock +++ b/flake.lock @@ -162,6 +162,22 @@ "url": "https://tangled.org/ptr.pet/hydrant" } }, + "hydrant-dev": { + "flake": false, + "locked": { + "lastModified": 1791825854, + "narHash": "sha256-OiuTTWElFVPMKV8SgU3AvCdcXivcwD93DDa9JQLf/Lk=", + "ref": "refs/heads/main", + "rev": "b6130856d4e8ce474e135403726d33a7f4b2f110", + "revCount": 803, + "type": "git", + "url": "https://tangled.org/ptr.pet/hydrant" + }, + "original": { + "type": "git", + "url": "https://tangled.org/ptr.pet/hydrant" + } + }, "ibm-plex-mono-src": { "flake": false, "locked": { @@ -278,6 +294,22 @@ "type": "github" } }, + "nixpkgs-pds": { + "locked": { + "lastModified": 1791734076, + "narHash": "sha256-i5qk1kLiit98rRbh1AXmWBNT9QXC/y4l5Wk/u5EdpDQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "7fd4004d0511991fff3a0524856a9be09e28c5f0", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "actor-typeahead-src": "actor-typeahead-src", @@ -289,6 +321,7 @@ "htmx-src": "htmx-src", "htmx-ws-src": "htmx-ws-src", "hydrant": "hydrant", + "hydrant-dev": "hydrant-dev", "ibm-plex-mono-src": "ibm-plex-mono-src", "indigo": "indigo", "inter-fonts-src": "inter-fonts-src", @@ -297,6 +330,7 @@ "mermaid-src": "mermaid-src", "microvm": "microvm", "nixpkgs": "nixpkgs", + "nixpkgs-pds": "nixpkgs-pds", "sqlite-lib-src": "sqlite-lib-src", "tailwindcss-animated-src": "tailwindcss-animated-src", "timeless-fonts-src": "timeless-fonts-src" diff --git a/flake.nix b/flake.nix index 9e2aa3fdc..efb98573c 100644 --- a/flake.nix +++ b/flake.nix @@ -75,6 +75,13 @@ url = "git+https://tangled.org/isabelroses.com/fetch-tangled"; inputs.nixpkgs.follows = "nixpkgs"; }; + # the stable pds refuses oauth when it shares a site with the web, as dev hostnames do + nixpkgs-pds.url = "github:nixos/nixpkgs/nixos-unstable"; + # hydrant has to match go.mod, hydrant-dev follows main + hydrant-dev = { + url = "git+https://tangled.org/ptr.pet/hydrant"; + flake = false; + }; hydrant = { url = "git+https://tangled.org/ptr.pet/hydrant"; flake = false; @@ -102,6 +109,8 @@ mathjax-src, tailwindcss-animated-src, hydrant, + hydrant-dev, + nixpkgs-pds, ... }: let supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"]; @@ -179,6 +188,10 @@ then self.rustPlatformStatic else self.rustPlatform; }; + hydrant-bin = self.callPackage ./nix/pkgs/hydrant-bin.nix { + hydrant = hydrant-dev; + inherit (self) rustPlatform; + }; spindle = self.callPackage ./nix/pkgs/spindle.nix {}; shuttle = self.callPackage ./nix/pkgs/shuttle.nix { src = self.mkRustSrc "shuttle"; @@ -220,6 +233,59 @@ inherit system; modules = [self.nixosModules.spindle-nixos]; }; + + devTools = system: let + pkgs = nixpkgsFor.${system}; + packages' = self.packages.${system}; + in + [ + pkgs.go + pkgs.air + pkgs.gopls + pkgs.httpie + pkgs.litecli + pkgs.websocat + pkgs.tailwindcss + pkgs.nixos-shell + pkgs.redis + pkgs.worker-build + pkgs.cargo-generate + pkgs.qemu + pkgs.cdrkit + pkgs.buf + pkgs.protobuf + pkgs.protoc-gen-prost + pkgs.protoc-gen-prost-crate + pkgs.protoc-gen-prost-serde + pkgs.protoc-gen-go + pkgs.protoc-gen-go-grpc + (fenix.packages.${system}.combine [ + fenix.packages.${system}.stable.cargo + fenix.packages.${system}.stable.rustc + fenix.packages.${system}.stable.rust-src + fenix.packages.${system}.stable.clippy + fenix.packages.${system}.stable.rustfmt + fenix.packages.${system}.stable.rust-analyzer + fenix.packages.${system}.targets.wasm32-unknown-unknown.stable.rust-std + ]) + pkgs.coreutils # for those of us who are on systems that use busybox (alpine) + packages'.lexgen + packages'.treefmt-wrapper + packages'.tap + pkgs.e2fsprogs + pkgs.util-linux + pkgs.pnpm + pkgs.nodejs + ] + ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ + pkgs.parted + pkgs.slirp4netns + pkgs.iproute2 + # cargo wants a plain cc and the static shell's compiler is prefixed + pkgs.stdenv.cc + # jemalloc builds with make, which the static shell lacks + pkgs.gnumake + ]; in { overlays.default = final: prev: { inherit @@ -274,6 +340,7 @@ goat spindle hydrant-ffi + hydrant-bin knot knot-unwrapped knot-rs @@ -443,51 +510,7 @@ }); in { default = staticShell { - nativeBuildInputs = - [ - pkgs.go - pkgs.air - pkgs.gopls - pkgs.httpie - pkgs.litecli - pkgs.websocat - pkgs.tailwindcss - pkgs.nixos-shell - pkgs.redis - pkgs.worker-build - pkgs.cargo-generate - pkgs.qemu - pkgs.cdrkit - pkgs.buf - pkgs.protobuf - pkgs.protoc-gen-prost - pkgs.protoc-gen-prost-crate - pkgs.protoc-gen-prost-serde - pkgs.protoc-gen-go - pkgs.protoc-gen-go-grpc - (fenix.packages.${system}.combine [ - fenix.packages.${system}.stable.cargo - fenix.packages.${system}.stable.rustc - fenix.packages.${system}.stable.rust-src - fenix.packages.${system}.stable.clippy - fenix.packages.${system}.stable.rustfmt - fenix.packages.${system}.stable.rust-analyzer - fenix.packages.${system}.targets.wasm32-unknown-unknown.stable.rust-std - ]) - pkgs.coreutils # for those of us who are on systems that use busybox (alpine) - packages'.lexgen - packages'.treefmt-wrapper - packages'.tap - pkgs.e2fsprogs - pkgs.util-linux - pkgs.pnpm - pkgs.nodejs - ] - ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ - pkgs.parted - pkgs.slirp4netns - pkgs.iproute2 - ]; + nativeBuildInputs = devTools system; shellHook = '' export CC=${pkgs.stdenv.cc}/bin/cc export SQLX_OFFLINE=true @@ -649,6 +672,41 @@ ${gomod2nix.legacyPackages.${system}.gomod2nix}/bin/gomod2nix generate --outdir ./nix ''); }; + localinfra = { + type = "app"; + program = + (pkgs.writeShellApplication { + name = "localinfra"; + runtimeInputs = with pkgs; + [process-compose postgresql_14 caddy dnsmasq dnsutils curl jq git nodejs_22 yarn openssl packages'.hydrant-bin nixpkgs-pds.legacyPackages.${system}.bluesky-pds] + ++ lib.optionals stdenv.hostPlatform.isLinux [socat slirp4netns util-linux iproute2 iptables procps gawk qemu_kvm e2fsprogs patchelf] + ++ lib.concatMap (name: let + file = ./localinfra/pc/services + "/${name}/packages"; + in + lib.optionals (builtins.pathExists file) + (map (attr: pkgs.${attr}) (lib.filter (attr: attr != "") (lib.splitString "\n" (builtins.readFile file))))) + (lib.attrNames (builtins.readDir ./localinfra/pc/services)) + ++ devTools system; + text = '' + rootDir=$(jj --ignore-working-copy root 2>/dev/null || git rev-parse --show-toplevel) || (echo "error: can't find repo root?"; exit 1) + # only macos ships the sqlite the libsqlite3 tag needs + export CGO_CFLAGS="-I${packages'.sqlite-lib}/include''${CGO_CFLAGS:+ $CGO_CFLAGS}" + export CGO_LDFLAGS="-L${packages'.sqlite-lib}/lib''${CGO_LDFLAGS:+ $CGO_LDFLAGS}" + ${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isLinux '' + export OPENSSL_INCLUDE_DIR=${pkgs.openssl.dev}/include + export OPENSSL_LIB_DIR=${pkgs.openssl.out}/lib + # rusqlite would link the system sqlite + export SQLITE3_LIB_DIR=${packages'.sqlite-lib}/lib + export SQLITE3_STATIC=1 + # npm's workerd needs a nix loader, bin/patch-workerd reads these + export TNGL_DYNAMIC_LINKER=${pkgs.stdenv.cc.bintools.dynamicLinker} + export TNGL_NIX_LIBS=${pkgs.lib.makeLibraryPath [pkgs.stdenv.cc.cc.lib pkgs.stdenv.cc.libc]} + ''} + exec "$rootDir/localinfra/pc/bin/up" "$@" + ''; + }) + + /bin/localinfra; + }; lexgen = { type = "app"; program = diff --git a/localinfra/pc/Caddyfile b/localinfra/pc/Caddyfile new file mode 100644 index 000000000..9847d4479 --- /dev/null +++ b/localinfra/pc/Caddyfile @@ -0,0 +1,59 @@ +{ + admin off + auto_https disable_redirects + skip_install_trust + https_port {$TNGL_HTTPS_PORT} + storage file_system {$TNGL_STATE}/data/caddy + + # the root comes from $TNGL_CERTS so a rebuild keeps the ca people already trust + pki { + ca local { + name "Tangled Dev" + root_cn "Tangled Dev Root" + root { + format pem_file + cert {$TNGL_CERTS}/root.crt + key {$TNGL_CERTS}/root.key + } + } + } +} + +plc.tngl.boltless.dev { + tls internal + reverse_proxy 127.0.0.1:{$TNGL_PLC_PORT} +} + +*.pds.tngl.boltless.dev, pds.tngl.boltless.dev { + tls internal + reverse_proxy 127.0.0.1:{$TNGL_PDS_PORT} +} + +hydrant.tngl.boltless.dev { + tls internal + reverse_proxy 127.0.0.1:{$TNGL_HYDRANT_PORT} +} + +# the knot answers cors preflight itself, so this block skips the import +knot2.tngl.boltless.dev, knot2-alt.tngl.boltless.dev { + tls internal + reverse_proxy 127.0.0.1:{$TNGL_KNOT_PORT} +} + +# a doubled Access-Control-Allow-Origin gets the response rejected, so only import where the upstream has none +(cors) { + @cors_preflight method OPTIONS + handle @cors_preflight { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, OPTIONS" + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" + header Access-Control-Max-Age "86400" + respond 204 + } + handle { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, OPTIONS" + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" + reverse_proxy {args[0]} + } +} diff --git a/localinfra/pc/README.md b/localinfra/pc/README.md new file mode 100644 index 000000000..c8f16829e --- /dev/null +++ b/localinfra/pc/README.md @@ -0,0 +1,113 @@ +# pc + +process-compose runs the localinfra stack as plain processes on your machine. each service +is wrapped in `offline`, so it only reaches loopback unless its yaml says otherwise. + +```sh +nix run .#localinfra # start the stack +bin/up # without nix, running doctor and setup first +bin/up --online # skip offline sandboxing +bin/up --profile spindle # add an optional profile +bin/pc process list # check running services +bin/pc process restart knot2 # restart one service +bin/pc down # stop the stack +``` + +`nix run .#localinfra` brings the monorepo toolchain, so it needs no `nix develop` and +runs fine from inside one. extra arguments go to `bin/up`. without nix, `bin/up` runs +`bin/doctor` for missing programs, then `bin/setup` to fetch and build. + +`bin/up --exec selftest` checks that the offline sandbox works. `TNGL_ONLINE=1` disables +it. + +## optional services + +some services only run under a profile: + +- `spindle`: spindle with the dummy engine, so no kvm +- `ncps`: ncps and its migration, the nix cache spindle uses +- `microvm`: linux only. spindle with the real microvm engine, implying `spindle` and + `ncps`. wants `/dev/kvm`, `/dev/vhost-vsock` and `/dev/net/tun` writable +- `observability`: prometheus, loki, tempo, otlp gateway and grafana +- `pdsls`: pds browser, needs bun and a cpu with avx2 + +pass `--profile ` to `bin/up`, or set `TNGL_PROFILES=a,b`. + +## swapping what a service runs + +`localinfra/pc/.env` overrides commands, ports and urls. `FOO_BIN` swaps a service's +program and keeps our flags, `FOO_CMD` replaces the whole command as a shell string that +runs after its env is set. names and defaults are in the `commands.env` files. + +services run what is already built, so build and restart to pick up edits. knot2, bobbin, +gitmirror and tranquil-gate build into this checkout's `target/`: + +```sh +cargo build -p knot-server && bin/pc process restart knot2 +``` + +to run your own checkout, put the build in the command: + +```sh +PDS_CMD='cd ~/proj/atproto/services/pds && exec node --enable-source-maps --import=@atproto/pds/telemetry index.ts' +PLC_CMD='cd ~/proj/did-method-plc/packages/server && DATABASE_URL=postgres://tnglr:tnglr@127.0.0.1:$TNGL_PG_PORT/plc PORT=$TNGL_PLC_PORT exec node dist/bin.js' +``` + +your plc checkout has to be built already for `PLC_CMD` (`node build.js`), and the atproto +one for `PDS_CMD` (`pnpm build`). + +on macos cargo builds here need `nix develop`, because `.cargo/config.toml` names a nix +linker. + +`process-compose.override.yaml` merges over the stack yaml: `is_disabled: "true"` drops a +service, a `watch` block restarts one when files change. + +## fixtures + +`init-data` runs `localinfra/scripts/init-data.sh` for the accounts and repos compose has. +later runs skip it while `.localinfra/data/init-data.stamp` matches, delete the stamp to +reseed. after a failed import delete `.localinfra/data/knot2`, since a rerun skips repos +the knot already has. + +## machine setup + +- trust `localinfra/certs/root.crt` in your browser. +- host dns: `*.tngl.boltless.dev` must resolve on the host, the pds checks handles over + dns. on macos: + + ```sh + sudo mkdir -p /etc/resolver + printf 'nameserver 127.0.0.1\nport 5354\n' | sudo tee /etc/resolver/tngl.boltless.dev + ``` +- linux ports: `sysctl net.ipv4.ip_unprivileged_port_start=443` lets the forwarder bind + 443 without root, and `doctor` says so when it can't. +- loopback alias: if using `TNGL_IP=11.0.0.253` (for older hydrant or the migrator github + stub flow), macos needs `sudo ifconfig lo0 alias 11.0.0.253/32` (lost on reboot) and + `--online`. +- caddy binds 443. if colima or podman holds it, set `TNGL_HTTPS_PORT` in + `localinfra/pc/.env`. + +## layout + +``` +process-compose.yaml dns, postgres, pg-init, plc, caddy, pds, init-data, knot2, hydrant +Caddyfile shared config, each service adds its vhosts +ports.env host ports, and TNGL_FORWARD +commands.env *_BIN defaults of the core programs +airgap.sb macos sandbox profile behind offline +bin/ + up parses its flags, runs setup, doctor, then process-compose + setup the only part that may use the internet + doctor checks the needed programs and the host + env.sh sets TNGL_* and PATH, sources every service's env files + services lists the active services + profile-on exit 0 when a profile is on + offline wraps a command so its only way out is loopback + linux-ns, ns-inner linux user and network namespace + pc process-compose against the stack's socket + tcp-up exit 0 once something accepts connections on a host and port + selftest checks the offline wrapper, run as `up --exec selftest` + patch-workerd points npm's workerd at the nix loader on nixos + svc/ launch scripts for the core services +services/ one directory per service, see services/README.md +``` diff --git a/localinfra/pc/airgap.sb b/localinfra/pc/airgap.sb new file mode 100644 index 000000000..2cb9e4232 --- /dev/null +++ b/localinfra/pc/airgap.sb @@ -0,0 +1,5 @@ +(version 1) +(allow default) +(deny network-outbound (with message "tngl-offline")) +(allow network-outbound (remote ip "localhost:*")) +(allow network-outbound (remote unix-socket)) diff --git a/localinfra/pc/bin/doctor b/localinfra/pc/bin/doctor new file mode 100755 index 000000000..f4af7b3fd --- /dev/null +++ b/localinfra/pc/bin/doctor @@ -0,0 +1,67 @@ +#!/bin/sh +here=$(cd "$(dirname "$0")/.." && pwd) +. "$here/bin/env.sh" + +missing="" +need() { + for c in "$@"; do + command -v "$c" >/dev/null 2>&1 || missing="$missing $c" + done +} + +need process-compose pg_isready psql dig curl jq git go openssl +[ -n "${PLC_CMD:-}" ] || need git node yarn +[ -n "${POSTGRES_CMD:-}" ] || need postgres initdb +[ -n "${CADDY_CMD:-}" ] || need "$CADDY_BIN" +[ -n "${DNS_CMD:-}" ] || need "$DNS_BIN" +[ -n "${PDS_CMD:-}" ] || need "$PDS_BIN" +[ -n "${HYDRANT_CMD:-}" ] || need "$HYDRANT_BIN" +if [ -z "${KNOT2_CMD:-}" ]; then + if [ "$KNOT2_BIN" = "$KNOT2_BUILT" ]; then need cargo; else need "$KNOT2_BIN"; fi +fi +for svc in $(services); do + [ -f "$here/services/$svc/doctor" ] && . "$here/services/$svc/doctor" +done +case "$(uname -s)" in +Darwin) + [ -x /usr/bin/sandbox-exec ] || missing="$missing sandbox-exec" + case "$TNGL_IP" in + 127.*) ;; + *) + [ "${TNGL_ONLINE:-0}" = 1 ] || { + echo "the macos sandbox only allows loopback, so offline services can't reach $TNGL_IP. use a hydrant with HYDRANT_ALLOW_PRIVATE_NETWORKS, or --online" >&2 + exit 1 + } + ifconfig lo0 | grep -q "inet $TNGL_IP " || { + echo "names resolve to $TNGL_IP and loopback has to answer for it: sudo ifconfig lo0 alias $TNGL_IP/32" >&2 + exit 1 + } + ;; + esac + ;; +Linux) + need unshare setpriv nsenter socat ip iptables ip6tables sysctl awk + command -v slirp4netns >/dev/null 2>&1 || echo "note: no slirp4netns, so nothing in the stack can reach the internet" >&2 + if ! grep -q "^$(id -un):" /etc/subgid 2>/dev/null || ! command -v newgidmap >/dev/null 2>&1; then + echo "need a /etc/subgid entry for $(id -un) and newgidmap, the offline marker is a mapped group" >&2 + exit 1 + fi + unshare --user true 2>/dev/null || { + echo "unprivileged user namespaces are off (ubuntu 24.04+ restricts them with apparmor)" >&2 + exit 1 + } + limit=$(sysctl -n net.ipv4.ip_unprivileged_port_start 2>/dev/null || echo 1024) + [ "$(id -u)" = 0 ] || for m in $(echo "${TNGL_FORWARD:-}" | tr , ' '); do + [ "${m%%:*}" -ge "$limit" ] || { + echo "TNGL_FORWARD binds ${m%%:*} on the host, which is below net.ipv4.ip_unprivileged_port_start=$limit. lower it (sudo sysctl -w net.ipv4.ip_unprivileged_port_start=${m%%:*}) or forward a higher port, like TNGL_FORWARD=8443:443" >&2 + exit 1 + } + done + ;; +esac + +if [ -n "$missing" ]; then + echo "missing:$missing" >&2 + echo "with nix, run this through: nix run .#localinfra (it brings go and cargo along)" >&2 + exit 1 +fi diff --git a/localinfra/pc/bin/env.sh b/localinfra/pc/bin/env.sh new file mode 100644 index 000000000..27bfef69a --- /dev/null +++ b/localinfra/pc/bin/env.sh @@ -0,0 +1,23 @@ +set -a +[ -f "$here/.env" ] && . "$here/.env" +set +a + +TNGL_PC=$here +TNGL_ROOT=$(cd "$here/../.." && pwd) +TNGL_STATE=${TNGL_STATE:-$TNGL_ROOT/.localinfra} +TNGL_CERTS=${TNGL_CERTS:-$TNGL_ROOT/localinfra/certs} +TNGL_OFFLINE_GID=${TNGL_OFFLINE_GID:-5000} + +set -a +. "$here/ports.env" +. "$here/commands.env" +for svc in $("$here/bin/services"); do + for f in ports.env commands.env; do + [ -f "$here/services/$svc/$f" ] && . "$here/services/$svc/$f" + done +done +set +a + +export TNGL_PC TNGL_ROOT TNGL_STATE TNGL_CERTS TNGL_OFFLINE_GID +PATH=$TNGL_PC/bin:$PATH +export PATH diff --git a/localinfra/pc/bin/linux-ns b/localinfra/pc/bin/linux-ns new file mode 100755 index 000000000..7a61fd751 --- /dev/null +++ b/localinfra/pc/bin/linux-ns @@ -0,0 +1,51 @@ +#!/bin/sh +set -eu +here=$(cd "$(dirname "$0")/.." && pwd) +. "$here/bin/env.sh" + +run=$TNGL_STATE/run +mkdir -p "$run/empty" +pidf=$run/ns.pid +rm -f "$pidf" + +# slirp4netns serves dns at 10.0.2.3, so that is the upstream +command -v slirp4netns >/dev/null 2>&1 && export TNGL_DNS_UPSTREAM=10.0.2.3 + +uid=$(id -u) +gid=$(id -g) +sub=$(awk -F: -v u="$(id -un)" '$1 == u { print $2; exit }' /etc/subgid) + +# postgres refuses root, so the uid maps to itself, and the offline gid goes to a subgid so files stay yours +unshare --user --keep-caps --map-users="$uid:$uid:1" \ + --map-groups="$gid:$gid:1" --map-groups="$TNGL_OFFLINE_GID:$sub:1" \ + --net --mount sh "$TNGL_PC/bin/ns-inner" "$pidf" "$@" & +ns=$! + +jobs_to_kill="$ns" +cleanup() { + # shellcheck disable=SC2086 + kill $jobs_to_kill 2>/dev/null || true +} +trap cleanup EXIT INT TERM + +while [ ! -s "$pidf" ]; do + kill -0 "$ns" 2>/dev/null || exit 1 + sleep 0.1 +done + +if command -v slirp4netns >/dev/null 2>&1; then + slirp4netns --configure --mtu=65520 --disable-host-loopback "$ns" tap0 >"$run/slirp.log" 2>&1 & + jobs_to_kill="$jobs_to_kill $!" +fi + +for m in $(echo "${TNGL_FORWARD:-}" | tr , ' '); do + host=${m%%:*} + inner=${m##*:} + socat "TCP-LISTEN:$host,bind=127.0.0.1,fork,reuseaddr" \ + EXEC:"nsenter --user --preserve-credentials --net -t $ns socat STDIO TCP\\:127.0.0.1\\:$inner" & + jobs_to_kill="$jobs_to_kill $!" +done + +status=0 +wait "$ns" || status=$? +exit "$status" diff --git a/localinfra/pc/bin/ns-inner b/localinfra/pc/bin/ns-inner new file mode 100755 index 000000000..61618bf3f --- /dev/null +++ b/localinfra/pc/bin/ns-inner @@ -0,0 +1,31 @@ +#!/bin/sh +# setgid has to survive the drop or the children can't take the offline gid +set -eu +echo $$ > "$1" +shift + +ip link set lo up +# names resolve to TNGL_IP, so lo has to hold it or local connects fail +case "$TNGL_IP" in +127.*) ;; +*) ip addr add "$TNGL_IP/32" dev lo ;; +esac +sysctl -qw net.ipv4.ip_unprivileged_port_start=0 +# stop outgoing connects from grabbing a service port before its service binds +sysctl -qw net.ipv4.ip_local_reserved_ports="$(env | sed -n 's/^TNGL_[A-Z0-9_]*PORT=\([0-9][0-9]*\)$/\1/p' | sort -un | paste -sd, -)" + +# nscd would answer from the host cache and skip the dnsmasq, so /run/nscd gets an empty dir too +printf 'nameserver 127.0.0.1\n' > "$TNGL_STATE/run/resolv.conf" +mount --bind "$TNGL_STATE/run/resolv.conf" /etc/resolv.conf +[ -d /run/nscd ] && mount --bind "$TNGL_STATE/run/empty" /run/nscd + +# with no caps the marked gid can't lift these rules, so it stays boxed in +case "$TNGL_IP" in +127.*) ;; +*) iptables -A OUTPUT -m owner --gid-owner "$TNGL_OFFLINE_GID" -d "$TNGL_IP" -j ACCEPT ;; +esac +iptables -A OUTPUT -m owner --gid-owner "$TNGL_OFFLINE_GID" ! -d 127.0.0.0/8 -j REJECT +ip6tables -A OUTPUT -m owner --gid-owner "$TNGL_OFFLINE_GID" ! -d ::1/128 -j REJECT + +export TNGL_DNS_PORT=53 +exec setpriv --inh-caps=-all,+setgid --ambient-caps=-all,+setgid --bounding-set=-all,+setgid "$@" diff --git a/localinfra/pc/bin/offline b/localinfra/pc/bin/offline new file mode 100755 index 000000000..cc613dfe7 --- /dev/null +++ b/localinfra/pc/bin/offline @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu +[ "${TNGL_ONLINE:-0}" = 1 ] && exec "$@" +case "$(uname -s)" in +Darwin) exec /usr/bin/sandbox-exec -f "$TNGL_PC/airgap.sb" "$@" ;; +Linux) exec setpriv --regid="$TNGL_OFFLINE_GID" --clear-groups --inh-caps=-all --ambient-caps=-all "$@" ;; +esac diff --git a/localinfra/pc/bin/patch-workerd b/localinfra/pc/bin/patch-workerd new file mode 100755 index 000000000..04af491b9 --- /dev/null +++ b/localinfra/pc/bin/patch-workerd @@ -0,0 +1,9 @@ +#!/bin/sh +# the workerd npm downloads asks for a generic linux loader that nixos doesn't ship +# only the nix app sets TNGL_DYNAMIC_LINKER, so everywhere else this is a no-op +set -eu +[ -n "${TNGL_DYNAMIC_LINKER:-}" ] || exit 0 +for bin in "$1"/node_modules/.pnpm/@cloudflare+workerd-linux-*/node_modules/@cloudflare/workerd-linux-*/bin/workerd; do + [ -f "$bin" ] || continue + patchelf --set-interpreter "$TNGL_DYNAMIC_LINKER" --set-rpath "$TNGL_NIX_LIBS" "$bin" +done diff --git a/localinfra/pc/bin/pc b/localinfra/pc/bin/pc new file mode 100755 index 000000000..00983509f --- /dev/null +++ b/localinfra/pc/bin/pc @@ -0,0 +1,4 @@ +#!/bin/sh +here=$(cd "$(dirname "$0")/.." && pwd) +. "$here/bin/env.sh" +exec process-compose -U -u "$TNGL_STATE/run/pc.sock" "$@" diff --git a/localinfra/pc/bin/profile-on b/localinfra/pc/bin/profile-on new file mode 100755 index 000000000..0256ada50 --- /dev/null +++ b/localinfra/pc/bin/profile-on @@ -0,0 +1,6 @@ +#!/bin/sh +# services parses TNGL_PROFILES too, so change the two together +for p in $(echo "${TNGL_PROFILES:-}" | tr , ' '); do + [ "$p" = "$1" ] && exit 0 +done +exit 1 diff --git a/localinfra/pc/bin/selftest b/localinfra/pc/bin/selftest new file mode 100755 index 000000000..50725e748 --- /dev/null +++ b/localinfra/pc/bin/selftest @@ -0,0 +1,31 @@ +#!/bin/sh +# run as `up --exec selftest` +here=$(cd "$(dirname "$0")/.." && pwd) +. "$here/bin/env.sh" + +fail=0 +check() { + want=$1 name=$2 + shift 2 + if "$@" >/dev/null 2>&1; then got=ok; else got=blocked; fi + if [ "$got" = "$want" ]; then echo "pass $name"; else echo "FAIL $name (wanted $want, got $got)"; fail=1; fi +} + +port=${TNGL_SELFTEST_PORT:-52999} +python3 -m http.server "$port" --bind 127.0.0.1 >/dev/null 2>&1 & +srv=$! +sleep 1 + +check ok "offline reaches loopback" offline curl -sS -m 3 "http://127.0.0.1:$port/" +check blocked "offline can't reach the internet" offline curl -sS -m 3 https://1.1.1.1 +check blocked "offline can't send udp out" offline python3 -c \ + "import socket; s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.connect(('1.1.1.1', 53)); s.send(b'x')" +if [ "$(uname -s)" = Linux ]; then + check blocked "offline can't unmark itself" offline setpriv --regid="$(id -g)" --clear-groups true +fi +if [ "${TNGL_SELFTEST_ONLINE:-0}" = 1 ]; then + check ok "an unwrapped process reaches the internet" curl -sS -m 5 https://1.1.1.1 +fi + +kill "$srv" 2>/dev/null +exit "$fail" diff --git a/localinfra/pc/bin/services b/localinfra/pc/bin/services new file mode 100755 index 000000000..8e0adc3f0 --- /dev/null +++ b/localinfra/pc/bin/services @@ -0,0 +1,22 @@ +#!/bin/sh +here=$(cd "$(dirname "$0")/.." && pwd) + +wanted() { + [ -f "$1/profile" ] || return 0 + for p in $(echo "${TNGL_PROFILES:-}" | tr , ' '); do + grep -qx "$p" "$1/profile" && return 0 + done + return 1 +} + +if [ -n "${TNGL_SERVICES:-}" ]; then + for s in $(echo "$TNGL_SERVICES" | tr , ' '); do + [ -d "$here/services/$s" ] || { echo "no service $s in $here/services" >&2; exit 1; } + echo "$s" + done +else + for d in "$here"/services/*/; do + s=$(basename "$d") + [ -d "$d" ] && wanted "$d" && case "$s" in _*) ;; *) echo "$s" ;; esac + done +fi diff --git a/localinfra/pc/bin/setup b/localinfra/pc/bin/setup new file mode 100755 index 000000000..578b70219 --- /dev/null +++ b/localinfra/pc/bin/setup @@ -0,0 +1,40 @@ +#!/bin/sh +# internet access happens here, because the stack itself runs offline +set -eu +here=$(cd "$(dirname "$0")/.." && pwd) +. "$here/bin/env.sh" + +mkdir -p "$TNGL_STATE"/data "$TNGL_STATE"/run "$TNGL_STATE"/src "$TNGL_STATE"/bin "$TNGL_CERTS" + +# make-certs.sh makes this ca too, but it needs go and makes keys nothing here uses +if [ ! -s "$TNGL_CERTS/root.crt" ]; then + (umask 077 && openssl req -x509 -newkey rsa:2048 \ + -keyout "$TNGL_CERTS/root.key" -out "$TNGL_CERTS/root.crt" \ + -days 3650 -nodes -subj "/CN=Tangled Dev CA" \ + -addext "basicConstraints=critical,CA:TRUE,pathlen:1" \ + -addext "keyUsage=critical,keyCertSign,cRLSign" \ + -addext "nameConstraints=critical,permitted;DNS:tngl.boltless.dev") + echo "made a dev ca in $TNGL_CERTS, trust root.crt to use the stack from a browser" >&2 +fi + +if [ -z "${PLC_CMD:-}" ]; then + plc=$TNGL_STATE/src/did-method-plc + if [ ! -d "$plc/.git" ]; then + mkdir -p "$plc" + git -C "$plc" init -q + git -C "$plc" fetch -q --depth 1 https://github.com/did-method-plc/did-method-plc f2ab7516bac5bc0f3f86842fa94e996bd1b3815b + git -C "$plc" checkout -q FETCH_HEAD + fi + [ -d "$plc/node_modules" ] || (cd "$plc" && yarn install --frozen-lockfile --silent) + [ -f "$plc/packages/server/dist/bin.js" ] || (cd "$plc/packages/server" && node build.js >/dev/null) +fi + +(cd "$TNGL_ROOT" && go build -o "$TNGL_STATE/bin/seed" ./localinfra/cmd/seed) + +if [ -z "${KNOT2_CMD:-}" ] && [ "$KNOT2_BIN" = "$KNOT2_BUILT" ]; then + (cd "$TNGL_ROOT" && cargo build -p knot-server) +fi + +for svc in $(services); do + if [ -f "$here/services/$svc/setup" ]; then sh "$here/services/$svc/setup"; fi +done diff --git a/localinfra/pc/bin/svc/caddy b/localinfra/pc/bin/svc/caddy new file mode 100755 index 000000000..f5c636611 --- /dev/null +++ b/localinfra/pc/bin/svc/caddy @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu +conf=$TNGL_STATE/run/Caddyfile +cp "$TNGL_PC/Caddyfile" "$conf" +for svc in $(services); do + if [ -f "$TNGL_PC/services/$svc/Caddyfile" ]; then { echo; cat "$TNGL_PC/services/$svc/Caddyfile"; } >> "$conf"; fi +done +# the default runs under sh -c, which only sees exported vars +export conf +default='exec "$CADDY_BIN" run --config "$conf" --adapter caddyfile' +exec sh -c "${CADDY_CMD:-$default}" diff --git a/localinfra/pc/bin/svc/dns b/localinfra/pc/bin/svc/dns new file mode 100755 index 000000000..d7abe9d78 --- /dev/null +++ b/localinfra/pc/bin/svc/dns @@ -0,0 +1,6 @@ +#!/bin/sh +# octostub.dev too, deliberi looks up committer email domains over dns +default='exec "$DNS_BIN" --keep-in-foreground --conf-file=/dev/null --pid-file= --no-hosts --no-resolv \ + --bind-interfaces --listen-address=127.0.0.1 --port="$TNGL_DNS_PORT" \ + --address=/tngl.boltless.dev/$TNGL_IP --address=/octostub.dev/$TNGL_IP ${TNGL_DNS_UPSTREAM:+--server=$TNGL_DNS_UPSTREAM}' +exec sh -c "${DNS_CMD:-$default}" diff --git a/localinfra/pc/bin/svc/hydrant b/localinfra/pc/bin/svc/hydrant new file mode 100755 index 000000000..487dca24a --- /dev/null +++ b/localinfra/pc/bin/svc/hydrant @@ -0,0 +1,21 @@ +#!/bin/sh +set -eu +mkdir -p "$TNGL_STATE/data/hydrant" +export HYDRANT_API_BIND=127.0.0.1:$TNGL_HYDRANT_PORT +export HYDRANT_DATABASE_PATH=$TNGL_STATE/data/hydrant/hydrant.db +export HYDRANT_RELAY_HOSTS=pds::wss://pds.tngl.boltless.dev,pds::wss://knot2.tngl.boltless.dev +export HYDRANT_CRAWLER_URLS=list_repos::https://pds.tngl.boltless.dev,list_repos::https://knot2.tngl.boltless.dev +if services | grep -qx tranquil; then + HYDRANT_RELAY_HOSTS=$HYDRANT_RELAY_HOSTS,pds::wss://tranquil.tngl.boltless.dev + HYDRANT_CRAWLER_URLS=$HYDRANT_CRAWLER_URLS,list_repos::https://tranquil.tngl.boltless.dev +fi +export HYDRANT_PLC_URL=https://plc.tngl.boltless.dev +export HYDRANT_FILTER_COLLECTIONS='org.tangled.*' +export HYDRANT_FILTER_SIGNALS=org.tangled.actor.profile,org.tangled.repo.manifest,org.tangled.repo.declaration,org.tangled.git.refs +export HYDRANT_BACKFILL_STRATEGY=sparse-filter +export HYDRANT_VERIFY_SIGNATURES=none +# these hosts resolve to loopback, and hydrant refuses that unless this is set +export HYDRANT_ALLOW_PRIVATE_NETWORKS=true +export SSL_CERT_FILE=$TNGL_CERTS/root.crt +default='exec "$HYDRANT_BIN"' +exec sh -c "${HYDRANT_CMD:-$default}" diff --git a/localinfra/pc/bin/svc/init-data b/localinfra/pc/bin/svc/init-data new file mode 100755 index 000000000..f437589ae --- /dev/null +++ b/localinfra/pc/bin/svc/init-data @@ -0,0 +1,64 @@ +#!/bin/sh +# imports need the internet, so this runs its own knot2 and kills it once they are done +set -eu + +# the stamp lives in data/, so wiping data/ reseeds too +stamp=$TNGL_STATE/data/init-data.stamp +want=$({ + cat "$0" "$TNGL_ROOT/localinfra/scripts/init-data.sh" "$TNGL_ROOT/localinfra/scripts/lib.sh" "$TNGL_ROOT/localinfra/pds.env" + services + find "$TNGL_ROOT/localinfra/fixtures" "$TNGL_ROOT/localinfra/cmd/seed" -type f | sort | xargs cksum +} | cksum) +if [ -s "$TNGL_STATE/shared/accounts.tsv" ] && [ "$(cat "$stamp" 2>/dev/null || true)" = "$want" ]; then + echo "[init-data] already seeded" >&2 + exit 0 +fi + +set -a +. "$TNGL_ROOT/localinfra/pds.env" +set +a +export PDS_URL=http://127.0.0.1:$TNGL_PDS_PORT +export PLC_URL=http://127.0.0.1:$TNGL_PLC_PORT +export KNOT_URL=http://127.0.0.1:$TNGL_KNOT_PORT +export KNOT_HOSTNAME=knot2.tngl.boltless.dev +export OWNER_USER=alice +export SCRIPTS=$TNGL_ROOT/localinfra/scripts +export FIXTURES=$TNGL_ROOT/localinfra/fixtures +export SHARED_DIR=$TNGL_STATE/shared +export PATH=$TNGL_STATE/bin:$PATH + +if services | grep -qx tranquil; then + export TRANQUIL_URL=http://127.0.0.1:$TNGL_TRANQUIL_PORT + export TRANQUIL_HOSTNAME=tranquil.tngl.boltless.dev +fi + +if services | grep -qx ozone; then + set -a + . "$TNGL_ROOT/localinfra/ozone.env" + set +a +fi + +"$TNGL_PC/bin/svc/knot2" & +knot=$! +trap 'kill $knot 2>/dev/null || true' EXIT +knot_alive() { + kill -0 "$knot" 2>/dev/null || { echo "[init-data] the temporary knot exited" >&2; exit 1; } +} +until tcp-up 127.0.0.1 "$TNGL_KNOT_PORT"; do knot_alive; sleep 1; done + +sh "$SCRIPTS/init-data.sh" + +# a knot that dies mid fetch can leave its state stuck on fetching, so knot_alive is what gets this loop out +while find "$TNGL_STATE/data/knot2/repositories" -name tangled.import.json -exec grep -lE '"state":"(queued|fetching)"' {} + | grep -q .; do + knot_alive + echo "[init-data] waiting for the knot to finish its imports" >&2 + sleep 2 +done +if find "$TNGL_STATE/data/knot2/repositories" -name tangled.import.json -exec grep -lE '"state":"(failed|partial)"' {} + | grep -q .; then + echo "[init-data] an import failed, see the knot2 lines above. delete $TNGL_STATE/data/knot2 to retry" >&2 + exit 1 +fi + +kill "$knot" +wait "$knot" || true +echo "$want" > "$stamp" diff --git a/localinfra/pc/bin/svc/knot2 b/localinfra/pc/bin/svc/knot2 new file mode 100755 index 000000000..6c99cd2c0 --- /dev/null +++ b/localinfra/pc/bin/svc/knot2 @@ -0,0 +1,36 @@ +#!/bin/sh +set -eu +data=$TNGL_STATE/data/knot2 +# the knot makes keys on demand but expects the scan dir to already exist +mkdir -p "$data/repositories" +export KNOT_HOSTNAME=knot2.tngl.boltless.dev +export KNOT_ADMINS=did:plc:igzuueb63dytyy3tpaweofuu +export KNOT_LISTEN_ADDR=127.0.0.1:$TNGL_KNOT_PORT +export KNOT_INTERNAL_LISTEN_ADDR=127.0.0.1:$TNGL_KNOT_INTERNAL_PORT +export KNOT_SSH_LISTEN_ADDR=127.0.0.1:$TNGL_KNOT_SSH_PORT +export KNOT_SCAN_PATH=$data/repositories +export KNOT_SSH_HOST_KEY_FILE=$data/keys/host_key +export KNOT_SEALED_KEY_FILE=$data/keys/sealed.bin +export KNOT_MASTER_KEY_ENV=KNOT_MASTER_KEY +export KNOT_MASTER_KEY=9MfYcpOFaJYNzYJziRP3rrkM7DJdxQH/Hjey3LuwPF4= +export KNOT_LEGACY_ADMIN_SECRET_ENV=KNOT_LEGACY_ADMIN_SECRET +export KNOT_LEGACY_ADMIN_SECRET=super-strong-devonly-key +export KNOT_PLC_DIRECTORY=$TNGL_PLC_URL +export KNOT_APPVIEW_ENDPOINT=https://web.tngl.boltless.dev +export KNOT_EXTRA_CA_FILE=$TNGL_CERTS/root.crt +# plc and the other knots are on loopback, so the private address guard has to be off +export KNOT_HTTP_BLOCK_PRIVATE_ADDRESSES=false +# caddy is the only caller here, so the client ip comes from x-forwarded-for +export KNOT_XRPC_TRUSTED_PROXY_HEADER=x-forwarded-for +export KNOT_XRPC_TRUSTED_PROXIES=127.0.0.0/8 +export KNOT_XRPC_PREAUTH_BURST=500 +export KNOT_XRPC_PREAUTH_REFILL_MS=5 +export KNOT_XRPC_PER_PEER_INFLIGHT=64 +export KNOT_XRPC_EVENTS_MAX_PER_PEER=64 +export KNOT_LISTEN_REQUEST_TIMEOUT_MS=600000 +export KNOT_TLS_HTTP3=false +export KNOT_CONTRIBUTION_POLICY=anyone +export RUST_LOG=${RUST_LOG:-info} + +default='exec "$KNOT2_BIN"' +exec sh -c "${KNOT2_CMD:-$default}" diff --git a/localinfra/pc/bin/svc/pds b/localinfra/pc/bin/svc/pds new file mode 100755 index 000000000..3d354b4ac --- /dev/null +++ b/localinfra/pc/bin/svc/pds @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu +set -a +. "$TNGL_ROOT/localinfra/pds.env" +set +a +export PDS_PORT=$TNGL_PDS_PORT +export PDS_DATA_DIRECTORY=$TNGL_STATE/data/pds +export PDS_BLOBSTORE_DISK_LOCATION=$TNGL_STATE/data/pds/blocks +export PDS_DID_PLC_URL=$TNGL_PLC_URL +# plc and handles are on loopback, which the ssrf guard refuses +export PDS_DISABLE_SSRF_PROTECTION=true +export NODE_EXTRA_CA_CERTS=$TNGL_CERTS/root.crt +mkdir -p "$PDS_DATA_DIRECTORY" + +default='exec "$PDS_BIN"' +exec sh -c "${PDS_CMD:-$default}" diff --git a/localinfra/pc/bin/svc/pg-init b/localinfra/pc/bin/svc/pg-init new file mode 100755 index 000000000..b9fbb4e77 --- /dev/null +++ b/localinfra/pc/bin/svc/pg-init @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu +psql="psql -h 127.0.0.1 -p $TNGL_PG_PORT -U tnglr -d postgres -tAq" +for db in $(sed -n 's/^CREATE DATABASE \(.*\);$/\1/p' "$TNGL_ROOT/localinfra/postgres-init.sql"); do + $psql -c "SELECT 1 FROM pg_database WHERE datname = '$db'" | grep -q 1 || $psql -c "CREATE DATABASE $db" +done diff --git a/localinfra/pc/bin/svc/plc b/localinfra/pc/bin/svc/plc new file mode 100755 index 000000000..f848225b5 --- /dev/null +++ b/localinfra/pc/bin/svc/plc @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu +default='cd "$TNGL_STATE/src/did-method-plc/packages/server" && DATABASE_URL="postgres://tnglr:tnglr@127.0.0.1:$TNGL_PG_PORT/plc" PORT=$TNGL_PLC_PORT exec node dist/bin.js' +exec sh -c "${PLC_CMD:-$default}" diff --git a/localinfra/pc/bin/svc/postgres b/localinfra/pc/bin/svc/postgres new file mode 100755 index 000000000..4c7688fc9 --- /dev/null +++ b/localinfra/pc/bin/svc/postgres @@ -0,0 +1,8 @@ +#!/bin/sh +# unix sockets are off, a long state dir path would overflow the socket path limit +set -eu +default='set -e +data=$TNGL_STATE/data/postgres +[ -f "$data/PG_VERSION" ] || { mkdir -p "$data" && initdb -D "$data" -U tnglr --auth=trust -E UTF8 --no-locale >/dev/null; } +exec postgres -D "$data" -h 127.0.0.1 -p "$TNGL_PG_PORT" -c unix_socket_directories=' +exec sh -c "${POSTGRES_CMD:-$default}" diff --git a/localinfra/pc/bin/tcp-up b/localinfra/pc/bin/tcp-up new file mode 100755 index 000000000..ae3dc0a40 --- /dev/null +++ b/localinfra/pc/bin/tcp-up @@ -0,0 +1,4 @@ +#!/bin/sh +# curl exit 7 (refused) is the only down, timeouts and http errors still count as up +curl -s -m 2 -o /dev/null "http://$1:$2/" +[ $? -ne 7 ] diff --git a/localinfra/pc/bin/up b/localinfra/pc/bin/up new file mode 100755 index 000000000..cabe1e11e --- /dev/null +++ b/localinfra/pc/bin/up @@ -0,0 +1,36 @@ +#!/bin/sh +# up [--online] [--profile NAME]... [process-compose args] +# up --exec CMD... runs CMD in the same environment instead, for poking around +set -eu +here=$(cd "$(dirname "$0")/.." && pwd) + +# env.sh reads TNGL_PROFILES, so the flags have to be parsed before it is sourced +while [ $# -gt 0 ]; do + case "$1" in + --online) TNGL_ONLINE=1; export TNGL_ONLINE; shift ;; + --profile) TNGL_PROFILES="${TNGL_PROFILES:+$TNGL_PROFILES,}${2:?--profile needs a name}"; export TNGL_PROFILES; shift 2 ;; + *) break ;; + esac +done + +. "$here/bin/env.sh" +cd "$here" + +doctor +if [ "${1:-}" = --exec ]; then + shift +else + setup + files="-f process-compose.yaml" + for svc in $(services); do + [ -f "services/$svc/process.yaml" ] && files="$files -f services/$svc/process.yaml" + done + [ -f process-compose.override.yaml ] && files="$files -f process-compose.override.yaml" + # shellcheck disable=SC2086 + set -- process-compose up -U -u "$TNGL_STATE/run/pc.sock" $files "$@" +fi + +case "$(uname -s)" in +Linux) exec "$here/bin/linux-ns" "$@" ;; +*) exec "$@" ;; +esac diff --git a/localinfra/pc/commands.env b/localinfra/pc/commands.env new file mode 100644 index 000000000..5ae4be7c1 --- /dev/null +++ b/localinfra/pc/commands.env @@ -0,0 +1,7 @@ +# single quote the values in .env, or the $ references get expanded when it's sourced +DNS_BIN=${DNS_BIN:-dnsmasq} +CADDY_BIN=${CADDY_BIN:-caddy} +PDS_BIN=${PDS_BIN:-pds} +HYDRANT_BIN=${HYDRANT_BIN:-hydrant} +KNOT2_BUILT=${CARGO_TARGET_DIR:-$TNGL_ROOT/target}/debug/knot-server +KNOT2_BIN=${KNOT2_BIN:-$KNOT2_BUILT} diff --git a/localinfra/pc/ports.env b/localinfra/pc/ports.env new file mode 100644 index 000000000..62c07ead1 --- /dev/null +++ b/localinfra/pc/ports.env @@ -0,0 +1,16 @@ +TNGL_HTTPS_PORT=${TNGL_HTTPS_PORT:-443} +TNGL_DNS_PORT=${TNGL_DNS_PORT:-5354} +TNGL_PG_PORT=${TNGL_PG_PORT:-55432} +TNGL_PLC_PORT=${TNGL_PLC_PORT:-52582} +TNGL_PDS_PORT=${TNGL_PDS_PORT:-52583} +TNGL_HYDRANT_PORT=${TNGL_HYDRANT_PORT:-52584} +TNGL_KNOT_PORT=${TNGL_KNOT_PORT:-52585} +TNGL_KNOT_INTERNAL_PORT=${TNGL_KNOT_INTERNAL_PORT:-52586} +TNGL_KNOT_SSH_PORT=${TNGL_KNOT_SSH_PORT:-2223} +TNGL_PLC_URL=${TNGL_PLC_URL:-https://plc.tngl.boltless.dev} + +# services that refuse loopback need TNGL_IP=11.0.0.253, which macos only answers after `sudo ifconfig lo0 alias 11.0.0.253/32` +TNGL_IP=${TNGL_IP:-127.0.0.1} + +# only bin/linux-ns reads this +TNGL_FORWARD=${TNGL_FORWARD:-443:443,2223:2223} diff --git a/localinfra/pc/process-compose.yaml b/localinfra/pc/process-compose.yaml new file mode 100644 index 000000000..2cbecc6f8 --- /dev/null +++ b/localinfra/pc/process-compose.yaml @@ -0,0 +1,106 @@ +version: "0.5" +is_strict: true + +processes: + dns: + command: ${TNGL_PC}/bin/svc/dns + readiness_probe: + exec: + command: dig +short +time=1 +tries=1 @127.0.0.1 -p ${TNGL_DNS_PORT} plc.tngl.boltless.dev | grep -q ${TNGL_IP} + period_seconds: 1 + failure_threshold: 30 + + postgres: + command: offline ${TNGL_PC}/bin/svc/postgres + readiness_probe: + exec: + command: pg_isready -q -h 127.0.0.1 -p ${TNGL_PG_PORT} -U tnglr -d postgres + period_seconds: 1 + failure_threshold: 60 + + pg-init: + command: offline ${TNGL_PC}/bin/svc/pg-init + depends_on: + postgres: + condition: process_healthy + availability: + restart: "no" + + plc: + command: offline ${TNGL_PC}/bin/svc/plc + depends_on: + pg-init: + condition: process_completed_successfully + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /_health + port: ${TNGL_PLC_PORT} + period_seconds: 1 + failure_threshold: 60 + + caddy: + command: offline ${TNGL_PC}/bin/svc/caddy + readiness_probe: + exec: + command: tcp-up 127.0.0.1 ${TNGL_HTTPS_PORT} + period_seconds: 1 + failure_threshold: 30 + + pds: + command: offline ${TNGL_PC}/bin/svc/pds + depends_on: + dns: + condition: process_healthy + caddy: + condition: process_healthy + plc: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /xrpc/_health + port: ${TNGL_PDS_PORT} + period_seconds: 1 + failure_threshold: 180 + + init-data: + command: ${TNGL_PC}/bin/svc/init-data + depends_on: + dns: + condition: process_healthy + caddy: + condition: process_healthy + pds: + condition: process_healthy + availability: + restart: "no" + + knot2: + command: offline ${TNGL_PC}/bin/svc/knot2 + depends_on: + init-data: + condition: process_completed_successfully + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: / + port: ${TNGL_KNOT_PORT} + period_seconds: 1 + failure_threshold: 120 + + hydrant: + command: offline ${TNGL_PC}/bin/svc/hydrant + depends_on: + pds: + condition: process_healthy + knot2: + condition: process_healthy + readiness_probe: + exec: + command: tcp-up 127.0.0.1 ${TNGL_HYDRANT_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/README.md b/localinfra/pc/services/README.md new file mode 100644 index 000000000..22e5dee71 --- /dev/null +++ b/localinfra/pc/services/README.md @@ -0,0 +1,28 @@ +# adding a service + +one directory per service, `services//`. nothing outside it needs editing, the +scripts discover what is there. every file is optional except `run` and `process.yaml`. +`services/redis/` is a small example. + +- `run`: exports its env and execs `${FOO_CMD:-$default}`. runs what is already built, + builds go in `setup` +- `process.yaml`: process-compose entry with a readiness probe and `depends_on`, wrapped + in `offline` unless it needs the internet while it runs +- `ports.env`: loopback ports, `TNGL_FOO_PORT=${TNGL_FOO_PORT:-52590}`. pick one + nobody has with + `grep -rh _PORT localinfra/pc/ports.env localinfra/pc/services/*/ports.env` +- `commands.env`: defaults for `FOO_BIN` and `FOO_CMD` +- `Caddyfile`: vhosts, upstream `127.0.0.1:{$TNGL_FOO_PORT}` +- `setup`: runs before the stack starts, may fetch and build +- `doctor`: checks required binaries with `need prog` +- `profile`: profile names, one per line. the service only runs when one is on, without + the file it always runs +- `packages`: nixpkgs attributes `nix run .#localinfra` should bring + +## rules + +- loopback only: bind `127.0.0.1`. +- call sibling services through `*.tngl.boltless.dev` because caddy terminates tls for all + of them. +- trust `$TNGL_CERTS/root.crt` for outgoing https calls. +- one service per directory, matching the compose service name. diff --git a/localinfra/pc/services/avatar/Caddyfile b/localinfra/pc/services/avatar/Caddyfile new file mode 100644 index 000000000..419d99c3f --- /dev/null +++ b/localinfra/pc/services/avatar/Caddyfile @@ -0,0 +1,4 @@ +avatar.tngl.boltless.dev { + tls internal + reverse_proxy 127.0.0.1:{$TNGL_AVATAR_PORT} +} diff --git a/localinfra/pc/services/avatar/commands.env b/localinfra/pc/services/avatar/commands.env new file mode 100644 index 000000000..966c9e4dd --- /dev/null +++ b/localinfra/pc/services/avatar/commands.env @@ -0,0 +1,2 @@ +AVATAR_BUILT=$TNGL_ROOT/avatar/node_modules/.bin/wrangler +AVATAR_BIN=${AVATAR_BIN:-$AVATAR_BUILT} diff --git a/localinfra/pc/services/avatar/doctor b/localinfra/pc/services/avatar/doctor new file mode 100644 index 000000000..6c4bd2d68 --- /dev/null +++ b/localinfra/pc/services/avatar/doctor @@ -0,0 +1,3 @@ +if [ -z "${AVATAR_CMD:-}" ]; then + if [ "$AVATAR_BIN" = "$AVATAR_BUILT" ]; then need pnpm node; else need "$AVATAR_BIN"; fi +fi diff --git a/localinfra/pc/services/avatar/packages b/localinfra/pc/services/avatar/packages new file mode 100644 index 000000000..7ef5d3681 --- /dev/null +++ b/localinfra/pc/services/avatar/packages @@ -0,0 +1,2 @@ +pnpm +nodejs_22 diff --git a/localinfra/pc/services/avatar/ports.env b/localinfra/pc/services/avatar/ports.env new file mode 100644 index 000000000..2d65052b5 --- /dev/null +++ b/localinfra/pc/services/avatar/ports.env @@ -0,0 +1 @@ +TNGL_AVATAR_PORT=${TNGL_AVATAR_PORT:-52661} diff --git a/localinfra/pc/services/avatar/process.yaml b/localinfra/pc/services/avatar/process.yaml new file mode 100644 index 000000000..28ea185d5 --- /dev/null +++ b/localinfra/pc/services/avatar/process.yaml @@ -0,0 +1,19 @@ +version: "0.5" +processes: + avatar: + command: offline ${TNGL_PC}/services/avatar/run + depends_on: + dns: + condition: process_healthy + caddy: + condition: process_healthy + plc: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: / + port: ${TNGL_AVATAR_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/avatar/run b/localinfra/pc/services/avatar/run new file mode 100755 index 000000000..4d909a915 --- /dev/null +++ b/localinfra/pc/services/avatar/run @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu +export AVATAR_SHARED_SECRET=${AVATAR_SHARED_SECRET:-localinfra-avatar-secret} +export PLC_DIRECTORY_URL=${PLC_DIRECTORY_URL:-https://plc.tngl.boltless.dev} +export WRANGLER_SEND_METRICS=false +export SSL_CERT_FILE=$TNGL_CERTS/root.crt +export NODE_EXTRA_CA_CERTS=$TNGL_CERTS/root.crt + +# wrangler keeps its local state in the project dir, so dev has to cd there first +default='cd "$TNGL_ROOT/avatar" && exec "$AVATAR_BIN" dev --ip 127.0.0.1 --port "$TNGL_AVATAR_PORT" --var "AVATAR_SHARED_SECRET:$AVATAR_SHARED_SECRET" --var "PLC_DIRECTORY_URL:$PLC_DIRECTORY_URL"' +exec sh -c "${AVATAR_CMD:-$default}" diff --git a/localinfra/pc/services/avatar/setup b/localinfra/pc/services/avatar/setup new file mode 100755 index 000000000..95a965097 --- /dev/null +++ b/localinfra/pc/services/avatar/setup @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu +if [ -z "${AVATAR_CMD:-}" ] && [ "$AVATAR_BIN" = "$AVATAR_BUILT" ] && [ ! -d "$TNGL_ROOT/avatar/node_modules" ]; then + (cd "$TNGL_ROOT/avatar" && pnpm install --frozen-lockfile) +fi +patch-workerd "$TNGL_ROOT/avatar" diff --git a/localinfra/pc/services/bobbin/Caddyfile b/localinfra/pc/services/bobbin/Caddyfile new file mode 100644 index 000000000..2ae776841 --- /dev/null +++ b/localinfra/pc/services/bobbin/Caddyfile @@ -0,0 +1,8 @@ +bobbin.tngl.boltless.dev { + tls internal + import cors 127.0.0.1:{$TNGL_BOBBIN_PORT} +} + +:8090 { + import cors 127.0.0.1:{$TNGL_BOBBIN_PORT} +} diff --git a/localinfra/pc/services/bobbin/commands.env b/localinfra/pc/services/bobbin/commands.env new file mode 100644 index 000000000..08e59102f --- /dev/null +++ b/localinfra/pc/services/bobbin/commands.env @@ -0,0 +1,2 @@ +BOBBIN_BUILT=${CARGO_TARGET_DIR:-$TNGL_ROOT/target}/debug/bobbin +BOBBIN_BIN=${BOBBIN_BIN:-$BOBBIN_BUILT} diff --git a/localinfra/pc/services/bobbin/doctor b/localinfra/pc/services/bobbin/doctor new file mode 100644 index 000000000..97ef833e9 --- /dev/null +++ b/localinfra/pc/services/bobbin/doctor @@ -0,0 +1,3 @@ +if [ -z "${BOBBIN_CMD:-}" ]; then + if [ "$BOBBIN_BIN" = "$BOBBIN_BUILT" ]; then need cargo; else need "$BOBBIN_BIN"; fi +fi diff --git a/localinfra/pc/services/bobbin/ports.env b/localinfra/pc/services/bobbin/ports.env new file mode 100644 index 000000000..53ffdb847 --- /dev/null +++ b/localinfra/pc/services/bobbin/ports.env @@ -0,0 +1 @@ +TNGL_BOBBIN_PORT=${TNGL_BOBBIN_PORT:-52681} diff --git a/localinfra/pc/services/bobbin/process.yaml b/localinfra/pc/services/bobbin/process.yaml new file mode 100644 index 000000000..a2c625277 --- /dev/null +++ b/localinfra/pc/services/bobbin/process.yaml @@ -0,0 +1,19 @@ +version: "0.5" +processes: + bobbin: + command: offline ${TNGL_PC}/services/bobbin/run + depends_on: + hydrant: + condition: process_healthy + caddy: + condition: process_healthy + pocket: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /xrpc/sh.tangled.bobbin.getCoverage + port: ${TNGL_BOBBIN_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/bobbin/run b/localinfra/pc/services/bobbin/run new file mode 100755 index 000000000..88858cf65 --- /dev/null +++ b/localinfra/pc/services/bobbin/run @@ -0,0 +1,23 @@ +#!/bin/sh +set -eu + +mkdir -p "$TNGL_STATE/data/bobbin" + +export BOBBIN_BIND=127.0.0.1:$TNGL_BOBBIN_PORT +export BOBBIN_HYDRANT_URL=https://hydrant.tngl.boltless.dev +export BOBBIN_SLINGSHOT_URL=https://hydrant.tngl.boltless.dev +export BOBBIN_SERVICE_DID=did:web:bobbin.tngl.boltless.dev +export BOBBIN_MIRROR_URL=https://mirror.tngl.boltless.dev +export BOBBIN_MIRROR_BREAKER_THRESHOLD=20 +export BOBBIN_MIRROR_BREAKER_COOLDOWN_SECS=3 +export BOBBIN_POCKET_URL=https://pocket.tngl.boltless.dev +export BOBBIN_CODESEARCH_ZOEKT_URL=https://zoekt.tngl.boltless.dev +export BOBBIN_LOG=${BOBBIN_LOG:-info} +export BOBBIN_INGEST_IDLE_PROMOTE_MIN_EVENTS=1 +export BOBBIN_MODERATION_LABELER_DID=did:plc:zu6y5f5sbudyxhv5ytiirkjx +export BOBBIN_MODERATION_LABELER_URL=https://ozone.tngl.boltless.dev +export BOBBIN_OTLP_ENDPOINT=${BOBBIN_OTLP_ENDPOINT:-} +export SSL_CERT_FILE=$TNGL_CERTS/root.crt + +default='exec "$BOBBIN_BIN"' +exec sh -c "${BOBBIN_CMD:-$default}" diff --git a/localinfra/pc/services/bobbin/setup b/localinfra/pc/services/bobbin/setup new file mode 100755 index 000000000..316ad1d93 --- /dev/null +++ b/localinfra/pc/services/bobbin/setup @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu +if [ -z "${BOBBIN_CMD:-}" ] && [ "$BOBBIN_BIN" = "$BOBBIN_BUILT" ]; then + (cd "$TNGL_ROOT" && cargo build -p bobbin) +fi diff --git a/localinfra/pc/services/camo/Caddyfile b/localinfra/pc/services/camo/Caddyfile new file mode 100644 index 000000000..6c11a96e4 --- /dev/null +++ b/localinfra/pc/services/camo/Caddyfile @@ -0,0 +1,4 @@ +camo.tngl.boltless.dev { + tls internal + reverse_proxy 127.0.0.1:{$TNGL_CAMO_PORT} +} diff --git a/localinfra/pc/services/camo/commands.env b/localinfra/pc/services/camo/commands.env new file mode 100644 index 000000000..edefb0726 --- /dev/null +++ b/localinfra/pc/services/camo/commands.env @@ -0,0 +1,2 @@ +CAMO_BUILT=$TNGL_ROOT/camo/node_modules/.bin/wrangler +CAMO_BIN=${CAMO_BIN:-$CAMO_BUILT} diff --git a/localinfra/pc/services/camo/doctor b/localinfra/pc/services/camo/doctor new file mode 100644 index 000000000..ee48c3e68 --- /dev/null +++ b/localinfra/pc/services/camo/doctor @@ -0,0 +1,3 @@ +if [ -z "${CAMO_CMD:-}" ]; then + if [ "$CAMO_BIN" = "$CAMO_BUILT" ]; then need pnpm node; else need "$CAMO_BIN"; fi +fi diff --git a/localinfra/pc/services/camo/packages b/localinfra/pc/services/camo/packages new file mode 100644 index 000000000..7ef5d3681 --- /dev/null +++ b/localinfra/pc/services/camo/packages @@ -0,0 +1,2 @@ +pnpm +nodejs_22 diff --git a/localinfra/pc/services/camo/ports.env b/localinfra/pc/services/camo/ports.env new file mode 100644 index 000000000..0e7af8312 --- /dev/null +++ b/localinfra/pc/services/camo/ports.env @@ -0,0 +1 @@ +TNGL_CAMO_PORT=${TNGL_CAMO_PORT:-52660} diff --git a/localinfra/pc/services/camo/process.yaml b/localinfra/pc/services/camo/process.yaml new file mode 100644 index 000000000..b3b5f4c8d --- /dev/null +++ b/localinfra/pc/services/camo/process.yaml @@ -0,0 +1,17 @@ +version: "0.5" +processes: + camo: + command: offline ${TNGL_PC}/services/camo/run + depends_on: + dns: + condition: process_healthy + caddy: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: / + port: ${TNGL_CAMO_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/camo/run b/localinfra/pc/services/camo/run new file mode 100755 index 000000000..64b284939 --- /dev/null +++ b/localinfra/pc/services/camo/run @@ -0,0 +1,10 @@ +#!/bin/sh +set -eu +export CAMO_SHARED_SECRET=${CAMO_SHARED_SECRET:-localinfra-camo-secret} +export WRANGLER_SEND_METRICS=false +export SSL_CERT_FILE=$TNGL_CERTS/root.crt +export NODE_EXTRA_CA_CERTS=$TNGL_CERTS/root.crt + +# wrangler keeps its state in the project dir, so the cd has to come first +default='cd "$TNGL_ROOT/camo" && exec "$CAMO_BIN" dev --ip 127.0.0.1 --port "$TNGL_CAMO_PORT" --var "CAMO_SHARED_SECRET:$CAMO_SHARED_SECRET"' +exec sh -c "${CAMO_CMD:-$default}" diff --git a/localinfra/pc/services/camo/setup b/localinfra/pc/services/camo/setup new file mode 100755 index 000000000..ca0eaf001 --- /dev/null +++ b/localinfra/pc/services/camo/setup @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu +if [ -z "${CAMO_CMD:-}" ] && [ "$CAMO_BIN" = "$CAMO_BUILT" ] && [ ! -d "$TNGL_ROOT/camo/node_modules" ]; then + (cd "$TNGL_ROOT/camo" && pnpm install --frozen-lockfile) +fi +patch-workerd "$TNGL_ROOT/camo" diff --git a/localinfra/pc/services/deliberi/Caddyfile b/localinfra/pc/services/deliberi/Caddyfile new file mode 100644 index 000000000..d6a447ecb --- /dev/null +++ b/localinfra/pc/services/deliberi/Caddyfile @@ -0,0 +1,4 @@ +deliberi.tngl.boltless.dev { + tls internal + reverse_proxy 127.0.0.1:{$TNGL_DELIBERI_PORT} +} diff --git a/localinfra/pc/services/deliberi/commands.env b/localinfra/pc/services/deliberi/commands.env new file mode 100644 index 000000000..90ba2c9ed --- /dev/null +++ b/localinfra/pc/services/deliberi/commands.env @@ -0,0 +1,4 @@ +DELIBERI_BUILT=$TNGL_STATE/bin/deliberi +DELIBERI_BIN=${DELIBERI_BIN:-$DELIBERI_BUILT} +DELIBERI_BOOTSTRAP_BUILT=$TNGL_STATE/bin/deliberi-bootstrap +DELIBERI_BOOTSTRAP_BIN=${DELIBERI_BOOTSTRAP_BIN:-$DELIBERI_BOOTSTRAP_BUILT} diff --git a/localinfra/pc/services/deliberi/doctor b/localinfra/pc/services/deliberi/doctor new file mode 100644 index 000000000..c12bbbbda --- /dev/null +++ b/localinfra/pc/services/deliberi/doctor @@ -0,0 +1,4 @@ +if [ -z "${DELIBERI_CMD:-}" ]; then + if [ "$DELIBERI_BIN" = "$DELIBERI_BUILT" ]; then need go; else need "$DELIBERI_BIN"; fi +fi +[ "$DELIBERI_BOOTSTRAP_BIN" = "$DELIBERI_BOOTSTRAP_BUILT" ] || need "$DELIBERI_BOOTSTRAP_BIN" diff --git a/localinfra/pc/services/deliberi/packages b/localinfra/pc/services/deliberi/packages new file mode 100644 index 000000000..f63a3046c --- /dev/null +++ b/localinfra/pc/services/deliberi/packages @@ -0,0 +1,2 @@ +sqlite +jq diff --git a/localinfra/pc/services/deliberi/ports.env b/localinfra/pc/services/deliberi/ports.env new file mode 100644 index 000000000..09811d6e4 --- /dev/null +++ b/localinfra/pc/services/deliberi/ports.env @@ -0,0 +1 @@ +TNGL_DELIBERI_PORT=${TNGL_DELIBERI_PORT:-52622} diff --git a/localinfra/pc/services/deliberi/process.yaml b/localinfra/pc/services/deliberi/process.yaml new file mode 100644 index 000000000..1ebb494d6 --- /dev/null +++ b/localinfra/pc/services/deliberi/process.yaml @@ -0,0 +1,23 @@ +version: "0.5" +processes: + deliberi: + command: offline ${TNGL_PC}/services/deliberi/run + depends_on: + dns: + condition: process_healthy + caddy: + condition: process_healthy + pds: + condition: process_healthy + hydrant: + condition: process_healthy + init-data: + condition: process_completed_successfully + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /xrpc/_health + port: ${TNGL_DELIBERI_PORT} + period_seconds: 1 + failure_threshold: 120 diff --git a/localinfra/pc/services/deliberi/run b/localinfra/pc/services/deliberi/run new file mode 100755 index 000000000..94842b0aa --- /dev/null +++ b/localinfra/pc/services/deliberi/run @@ -0,0 +1,26 @@ +#!/bin/sh +set -eu +data=$TNGL_STATE/data/deliberi +mkdir -p "$data" + +[ -f "$TNGL_ROOT/localinfra/pds.env" ] && . "$TNGL_ROOT/localinfra/pds.env" + +export DELIBERI_DEV=true +export DELIBERI_HOSTNAME=deliberi.tngl.boltless.dev +export DELIBERI_LISTEN_ADDR=127.0.0.1:$TNGL_DELIBERI_PORT +export DELIBERI_DB_PATH=$data/deliberi.db +export DELIBERI_PLC_URL=$TNGL_PLC_URL +export DELIBERI_JETSTREAM_ENDPOINT=wss://hydrant.tngl.boltless.dev/subscribe +export DELIBERI_BOBBIN_API_URL=http://127.0.0.1:${TNGL_BOBBIN_PORT:-52681} +export DELIBERI_BASE_URL=https://web.tngl.boltless.dev +export DELIBERI_PDS_HOST=https://pds.tngl.boltless.dev +export DELIBERI_PDS_ADMIN_SECRET=${PDS_ADMIN_PASSWORD:-d6a902588cd93bee1af83f924f60cfd3} +export DELIBERI_TRUSTED_SERVICE_DIDS=did:web:mirror.tngl.boltless.dev,did:web:delegates.tngl.boltless.dev +export SSL_CERT_FILE=$TNGL_CERTS/root.crt + +if [ -f "$TNGL_STATE/shared/verified-emails.jsonl" ]; then + "$DELIBERI_BOOTSTRAP_BIN" --file "$TNGL_STATE/shared/verified-emails.jsonl" +fi + +default='exec "$DELIBERI_BIN" serve' +exec sh -c "${DELIBERI_CMD:-$default}" diff --git a/localinfra/pc/services/deliberi/setup b/localinfra/pc/services/deliberi/setup new file mode 100755 index 000000000..a6089f423 --- /dev/null +++ b/localinfra/pc/services/deliberi/setup @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu +if [ -z "${DELIBERI_CMD:-}" ] && [ "$DELIBERI_BIN" = "$DELIBERI_BUILT" ] && [ ! -x "$DELIBERI_BUILT" ]; then + (cd "$TNGL_ROOT" && go build -tags libsqlite3 -o "$TNGL_STATE/bin/deliberi" ./cmd/deliberi) +fi +if [ "$DELIBERI_BOOTSTRAP_BIN" = "$DELIBERI_BOOTSTRAP_BUILT" ] && [ ! -x "$DELIBERI_BOOTSTRAP_BUILT" ]; then + (cd "$TNGL_ROOT" && go build -tags libsqlite3 -o "$TNGL_STATE/bin/deliberi-bootstrap" ./localinfra/cmd/deliberi-bootstrap) +fi diff --git a/localinfra/pc/services/github-stub/Caddyfile b/localinfra/pc/services/github-stub/Caddyfile new file mode 100644 index 000000000..b60ee7359 --- /dev/null +++ b/localinfra/pc/services/github-stub/Caddyfile @@ -0,0 +1,4 @@ +github.tngl.boltless.dev, api.github.tngl.boltless.dev { + tls internal + reverse_proxy 127.0.0.1:{$TNGL_GITHUB_STUB_PORT} +} diff --git a/localinfra/pc/services/github-stub/commands.env b/localinfra/pc/services/github-stub/commands.env new file mode 100644 index 000000000..e30c06304 --- /dev/null +++ b/localinfra/pc/services/github-stub/commands.env @@ -0,0 +1 @@ +GITHUB_STUB_BIN=${GITHUB_STUB_BIN:-python3} diff --git a/localinfra/pc/services/github-stub/doctor b/localinfra/pc/services/github-stub/doctor new file mode 100644 index 000000000..7765240cf --- /dev/null +++ b/localinfra/pc/services/github-stub/doctor @@ -0,0 +1,3 @@ +if [ -z "${GITHUB_STUB_CMD:-}" ]; then + need "$GITHUB_STUB_BIN" git +fi diff --git a/localinfra/pc/services/github-stub/packages b/localinfra/pc/services/github-stub/packages new file mode 100644 index 000000000..dae424542 --- /dev/null +++ b/localinfra/pc/services/github-stub/packages @@ -0,0 +1,2 @@ +python3 +git diff --git a/localinfra/pc/services/github-stub/ports.env b/localinfra/pc/services/github-stub/ports.env new file mode 100644 index 000000000..6b0204de4 --- /dev/null +++ b/localinfra/pc/services/github-stub/ports.env @@ -0,0 +1 @@ +TNGL_GITHUB_STUB_PORT=${TNGL_GITHUB_STUB_PORT:-52662} diff --git a/localinfra/pc/services/github-stub/process.yaml b/localinfra/pc/services/github-stub/process.yaml new file mode 100644 index 000000000..eb0c268b0 --- /dev/null +++ b/localinfra/pc/services/github-stub/process.yaml @@ -0,0 +1,15 @@ +version: "0.5" +processes: + github-stub: + command: offline ${TNGL_PC}/services/github-stub/run + depends_on: + caddy: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /user + port: ${TNGL_GITHUB_STUB_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/github-stub/run b/localinfra/pc/services/github-stub/run new file mode 100755 index 000000000..4365e9195 --- /dev/null +++ b/localinfra/pc/services/github-stub/run @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu + +data=$TNGL_STATE/data/github-stub +mkdir -p "$data/repos" + +export PORT=$TNGL_GITHUB_STUB_PORT +export REPOS_DIR=$data/repos +export GITHUB_PUBLIC_ORIGIN=https://github.tngl.boltless.dev +export GITHUB_API_ORIGIN=https://github.tngl.boltless.dev +export GITHUB_AVATARS_ORIGIN=https://github.tngl.boltless.dev +export GITHUB_APP_SETUP_URL=${GITHUB_APP_SETUP_URL:-https://web.tngl.boltless.dev/_internal/github/install/callback} +export PYTHONUNBUFFERED=1 + +default='exec "$GITHUB_STUB_BIN" "$TNGL_ROOT/localinfra/github-stub/server.py"' +exec sh -c "${GITHUB_STUB_CMD:-$default}" diff --git a/localinfra/pc/services/github-stub/setup b/localinfra/pc/services/github-stub/setup new file mode 100755 index 000000000..a33c49b75 --- /dev/null +++ b/localinfra/pc/services/github-stub/setup @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +data=$TNGL_STATE/data/github-stub +mkdir -p "$data/repos" + +if [ -z "${GITHUB_STUB_CMD:-}" ]; then + REPOS_DIR="$data/repos" "$GITHUB_STUB_BIN" -c 'import sys; sys.path.insert(0, "'"$TNGL_ROOT"'/localinfra/github-stub"); from server import seed_repositories; seed_repositories()' +fi diff --git a/localinfra/pc/services/gitmirror/Caddyfile b/localinfra/pc/services/gitmirror/Caddyfile new file mode 100644 index 000000000..95a0cff63 --- /dev/null +++ b/localinfra/pc/services/gitmirror/Caddyfile @@ -0,0 +1,4 @@ +mirror.tngl.boltless.dev { + tls internal + import cors 127.0.0.1:{$TNGL_GITMIRROR_PORT} +} diff --git a/localinfra/pc/services/gitmirror/commands.env b/localinfra/pc/services/gitmirror/commands.env new file mode 100644 index 000000000..3af4dacff --- /dev/null +++ b/localinfra/pc/services/gitmirror/commands.env @@ -0,0 +1,2 @@ +GITMIRROR_BUILT=${CARGO_TARGET_DIR:-$TNGL_ROOT/target}/debug/gitmirror +GITMIRROR_BIN=${GITMIRROR_BIN:-$GITMIRROR_BUILT} diff --git a/localinfra/pc/services/gitmirror/doctor b/localinfra/pc/services/gitmirror/doctor new file mode 100644 index 000000000..b48d049ec --- /dev/null +++ b/localinfra/pc/services/gitmirror/doctor @@ -0,0 +1,3 @@ +if [ -z "${GITMIRROR_CMD:-}" ]; then + if [ "$GITMIRROR_BIN" = "$GITMIRROR_BUILT" ]; then need cargo git; else need "$GITMIRROR_BIN" git; fi +fi diff --git a/localinfra/pc/services/gitmirror/packages b/localinfra/pc/services/gitmirror/packages new file mode 100644 index 000000000..5664e303b --- /dev/null +++ b/localinfra/pc/services/gitmirror/packages @@ -0,0 +1 @@ +git diff --git a/localinfra/pc/services/gitmirror/ports.env b/localinfra/pc/services/gitmirror/ports.env new file mode 100644 index 000000000..232c160d9 --- /dev/null +++ b/localinfra/pc/services/gitmirror/ports.env @@ -0,0 +1,2 @@ +TNGL_GITMIRROR_PORT=${TNGL_GITMIRROR_PORT:-52600} +TNGL_GITMIRROR_METRICS_PORT=${TNGL_GITMIRROR_METRICS_PORT:-52601} diff --git a/localinfra/pc/services/gitmirror/process.yaml b/localinfra/pc/services/gitmirror/process.yaml new file mode 100644 index 000000000..117c42624 --- /dev/null +++ b/localinfra/pc/services/gitmirror/process.yaml @@ -0,0 +1,21 @@ +version: "0.5" +processes: + gitmirror: + command: offline ${TNGL_PC}/services/gitmirror/run + depends_on: + dns: + condition: process_healthy + pg-init: + condition: process_completed_successfully + redis: + condition: process_healthy + hydrant: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /metrics + port: ${TNGL_GITMIRROR_METRICS_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/gitmirror/run b/localinfra/pc/services/gitmirror/run new file mode 100755 index 000000000..a1a0c7057 --- /dev/null +++ b/localinfra/pc/services/gitmirror/run @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu +data=$TNGL_STATE/data/gitmirror +mkdir -p "$data/repos" + +export GITMIRROR_BIND=127.0.0.1:$TNGL_GITMIRROR_PORT +export GITMIRROR_METRICS_BIND=127.0.0.1:$TNGL_GITMIRROR_METRICS_PORT +export GITMIRROR_SCAN_PATH=$data/repos +export GITMIRROR_SERVICE_DID=did:web:mirror.tngl.boltless.dev +export GITMIRROR_PLC_URL=$TNGL_PLC_URL +export GITMIRROR_REDIS_URL=redis://127.0.0.1:$TNGL_REDIS_PORT +export GITMIRROR_DATABASE_URL=postgres://tnglr:tnglr@127.0.0.1:$TNGL_PG_PORT/gitmirror +export GITMIRROR_HYDRANT_URL=ws://127.0.0.1:$TNGL_HYDRANT_PORT +# knot2 and plc are on loopback, which the outbound checks refuse unless this is set +export GITMIRROR_KNOT_ALLOW_PRIVATE=true +export GITMIRROR_KNOT_REQUIRE_HTTPS=false +export SSL_CERT_FILE=$TNGL_CERTS/root.crt +export RUST_LOG=${RUST_LOG:-gitmirror=debug} +export GITMIRROR_LOG=${GITMIRROR_LOG:-gitmirror_ingest=debug,gitmirror_sync=debug} + +default='exec "$GITMIRROR_BIN" serve' +exec sh -c "${GITMIRROR_CMD:-$default}" diff --git a/localinfra/pc/services/gitmirror/setup b/localinfra/pc/services/gitmirror/setup new file mode 100755 index 000000000..c033305ea --- /dev/null +++ b/localinfra/pc/services/gitmirror/setup @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu +if [ -z "${GITMIRROR_CMD:-}" ] && [ "$GITMIRROR_BIN" = "$GITMIRROR_BUILT" ]; then + (cd "$TNGL_ROOT" && cargo build -p gitmirror) +fi diff --git a/localinfra/pc/services/grafana/commands.env b/localinfra/pc/services/grafana/commands.env new file mode 100644 index 000000000..a49ab8967 --- /dev/null +++ b/localinfra/pc/services/grafana/commands.env @@ -0,0 +1 @@ +GRAFANA_BIN=${GRAFANA_BIN:-grafana} diff --git a/localinfra/pc/services/grafana/doctor b/localinfra/pc/services/grafana/doctor new file mode 100644 index 000000000..e478a067f --- /dev/null +++ b/localinfra/pc/services/grafana/doctor @@ -0,0 +1 @@ +[ -n "${GRAFANA_CMD:-}" ] || need "$GRAFANA_BIN" diff --git a/localinfra/pc/services/grafana/packages b/localinfra/pc/services/grafana/packages new file mode 100644 index 000000000..59081c90a --- /dev/null +++ b/localinfra/pc/services/grafana/packages @@ -0,0 +1 @@ +grafana diff --git a/localinfra/pc/services/grafana/ports.env b/localinfra/pc/services/grafana/ports.env new file mode 100644 index 000000000..fc6a69558 --- /dev/null +++ b/localinfra/pc/services/grafana/ports.env @@ -0,0 +1 @@ +TNGL_GRAFANA_PORT=${TNGL_GRAFANA_PORT:-52755} diff --git a/localinfra/pc/services/grafana/process.yaml b/localinfra/pc/services/grafana/process.yaml new file mode 100644 index 000000000..cd3cbf400 --- /dev/null +++ b/localinfra/pc/services/grafana/process.yaml @@ -0,0 +1,19 @@ +version: "0.5" +processes: + grafana: + command: offline ${TNGL_PC}/services/grafana/run + depends_on: + prometheus: + condition: process_healthy + loki: + condition: process_healthy + tempo: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /api/health + port: ${TNGL_GRAFANA_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/grafana/profile b/localinfra/pc/services/grafana/profile new file mode 100644 index 000000000..d57392940 --- /dev/null +++ b/localinfra/pc/services/grafana/profile @@ -0,0 +1 @@ +observability diff --git a/localinfra/pc/services/grafana/run b/localinfra/pc/services/grafana/run new file mode 100755 index 000000000..2e4dcfc12 --- /dev/null +++ b/localinfra/pc/services/grafana/run @@ -0,0 +1,39 @@ +#!/bin/sh +set -eu +export data=$TNGL_STATE/data/grafana +prov=$data/provisioning +mkdir -p "$data/data" "$data/logs" "$prov/datasources" "$prov/dashboards" + +# same source compose mounts, so the dashboards can't drift between the two +cp "$TNGL_ROOT"/localinfra/observability/grafana/provisioning/dashboards/*.json "$prov/dashboards/" +cat > "$prov/dashboards/spindle.yml" < "$prov/datasources/prometheus.yml" + +export GF_PATHS_DATA=$data/data +export GF_PATHS_LOGS=$data/logs +export GF_PATHS_PROVISIONING=$prov +export GF_SERVER_HTTP_ADDR=127.0.0.1 +export GF_SERVER_HTTP_PORT=$TNGL_GRAFANA_PORT +export GF_ANALYTICS_REPORTING_ENABLED=false +export GF_ANALYTICS_CHECK_FOR_UPDATES=false + +export home=$(dirname "$(readlink -f "$(command -v "$GRAFANA_BIN")")")/../share/grafana +default='exec "$GRAFANA_BIN" server --homepath "$home"' +exec sh -c "${GRAFANA_CMD:-$default}" diff --git a/localinfra/pc/services/loki/commands.env b/localinfra/pc/services/loki/commands.env new file mode 100644 index 000000000..a70b32462 --- /dev/null +++ b/localinfra/pc/services/loki/commands.env @@ -0,0 +1 @@ +LOKI_BIN=${LOKI_BIN:-loki} diff --git a/localinfra/pc/services/loki/doctor b/localinfra/pc/services/loki/doctor new file mode 100644 index 000000000..797a738df --- /dev/null +++ b/localinfra/pc/services/loki/doctor @@ -0,0 +1 @@ +[ -n "${LOKI_CMD:-}" ] || need "$LOKI_BIN" diff --git a/localinfra/pc/services/loki/packages b/localinfra/pc/services/loki/packages new file mode 100644 index 000000000..f60163489 --- /dev/null +++ b/localinfra/pc/services/loki/packages @@ -0,0 +1 @@ +grafana-loki diff --git a/localinfra/pc/services/loki/ports.env b/localinfra/pc/services/loki/ports.env new file mode 100644 index 000000000..a50648fe8 --- /dev/null +++ b/localinfra/pc/services/loki/ports.env @@ -0,0 +1,2 @@ +TNGL_LOKI_PORT=${TNGL_LOKI_PORT:-52751} +TNGL_LOKI_GRPC_PORT=${TNGL_LOKI_GRPC_PORT:-52752} diff --git a/localinfra/pc/services/loki/process.yaml b/localinfra/pc/services/loki/process.yaml new file mode 100644 index 000000000..a68047def --- /dev/null +++ b/localinfra/pc/services/loki/process.yaml @@ -0,0 +1,12 @@ +version: "0.5" +processes: + loki: + command: offline ${TNGL_PC}/services/loki/run + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /ready + port: ${TNGL_LOKI_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/loki/profile b/localinfra/pc/services/loki/profile new file mode 100644 index 000000000..d57392940 --- /dev/null +++ b/localinfra/pc/services/loki/profile @@ -0,0 +1 @@ +observability diff --git a/localinfra/pc/services/loki/run b/localinfra/pc/services/loki/run new file mode 100755 index 000000000..040ac204e --- /dev/null +++ b/localinfra/pc/services/loki/run @@ -0,0 +1,49 @@ +#!/bin/sh +set -eu +export data=$TNGL_STATE/data/loki +mkdir -p "$data" + +cat > "$data/loki.yml" <&2; exit 1; } + master=$(head -c 32 /dev/urandom | base64) + token=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n') + cat > "$data/migrator.env" < "$data/ncps.yaml" < "$data/nginx.conf" < "$TNGL_STATE/data/pdsls/Caddyfile" < "$data/prometheus.yml" <&2; exit 1; } + need "qemu-system-$(uname -m)" mkfs.ext4 unshare ip slirp4netns + # opening the device is what loads vhost_vsock, nothing else triggers it + { : < /dev/vhost-vsock; } 2>/dev/null || { echo "microvm needs the vhost_vsock module: sudo modprobe vhost_vsock" >&2; exit 1; } + for dev in /dev/kvm /dev/vhost-vsock /dev/net/tun; do + [ -w "$dev" ] || { echo "microvm needs to write $dev" >&2; exit 1; } + done +fi diff --git a/localinfra/pc/services/spindle/ports.env b/localinfra/pc/services/spindle/ports.env new file mode 100644 index 000000000..9f6142be9 --- /dev/null +++ b/localinfra/pc/services/spindle/ports.env @@ -0,0 +1,2 @@ +TNGL_SPINDLE_PORT=${TNGL_SPINDLE_PORT:-52740} +TNGL_SPINDLE_METRICS_PORT=${TNGL_SPINDLE_METRICS_PORT:-52741} diff --git a/localinfra/pc/services/spindle/process.yaml b/localinfra/pc/services/spindle/process.yaml new file mode 100644 index 000000000..d0a3a1de3 --- /dev/null +++ b/localinfra/pc/services/spindle/process.yaml @@ -0,0 +1,21 @@ +version: "0.5" +processes: + spindle: + command: offline ${TNGL_PC}/services/spindle/run + depends_on: + dns: + condition: process_healthy + plc: + condition: process_healthy + hydrant: + condition: process_healthy + caddy: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: / + port: ${TNGL_SPINDLE_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/spindle/profile b/localinfra/pc/services/spindle/profile new file mode 100644 index 000000000..a082ebf85 --- /dev/null +++ b/localinfra/pc/services/spindle/profile @@ -0,0 +1,2 @@ +spindle +microvm diff --git a/localinfra/pc/services/spindle/run b/localinfra/pc/services/spindle/run new file mode 100755 index 000000000..075c6899a --- /dev/null +++ b/localinfra/pc/services/spindle/run @@ -0,0 +1,47 @@ +#!/bin/sh +set -eu +data=$TNGL_STATE/data/spindle +mkdir -p "$data/hydrant" "$data/repos" "$data/logs" + +export SPINDLE_SERVER_HOSTNAME=spindle.tngl.boltless.dev +export SPINDLE_SERVER_OWNER=did:plc:igzuueb63dytyy3tpaweofuu +export SPINDLE_SERVER_LISTEN_ADDR=127.0.0.1:$TNGL_SPINDLE_PORT +export SPINDLE_SERVER_METRICS_LISTEN_ADDR=127.0.0.1:$TNGL_SPINDLE_METRICS_PORT +export SPINDLE_SERVER_DB_PATH=$data/spindle.db +export SPINDLE_SERVER_REPO_DIR=$data/repos +export SPINDLE_SERVER_LOG_DIR=$data/logs +export SPINDLE_SERVER_HYDRANT_DIR=$data/hydrant +export SPINDLE_SERVER_PLC_URL=$TNGL_PLC_URL +export SPINDLE_SERVER_JETSTREAM_ENDPOINT=wss://hydrant.tngl.boltless.dev/subscribe +export SPINDLE_SERVER_DEV=true +export SPINDLE_SERVER_DEV_EXTRA_HOSTS=knot2.tngl.boltless.dev,mirror.tngl.boltless.dev +export SPINDLE_SERVER_CORS_ORIGINS='*' +export SSL_CERT_FILE=$TNGL_CERTS/root.crt + +# 0 is the off switch for these quotas, compose does the same +for scope in USER REPO; do + for resource in CACHE_STORAGE_MIB WORKFLOWS VCPUS MEMORY_MIB DISK_MIB; do + export "SPINDLE_QUOTA_${scope}_${resource}=0" + done +done + +if services | grep -qx ncps; then + export SPINDLE_CACHE_BACKEND=disk + export SPINDLE_NIX_CACHE_READ_URLS=http://127.0.0.1:$TNGL_NCPS_PORT + export SPINDLE_NIX_CACHE_UPLOAD_URL=http://127.0.0.1:$TNGL_NCPS_PORT/upload + export SPINDLE_NIX_CACHE_TRUSTED_PUBLIC_KEYS=cache.local:F7YqpMzuBdILYd/v+wMZN2YKxCzliXQyFmeezOxw7rU= +fi + +if profile-on microvm; then + mkdir -p "$data/overlays" + export SPINDLE_MICROVM_PIPELINES_IMAGE_DIR=$SPINDLE_IMAGE_DIR + export SPINDLE_MICROVM_PIPELINES_OVERLAY_DIR=$data/overlays + export SPINDLE_MICROVM_PIPELINES_AGENT_PORT=11240 + # the default image is nixos-x86_64, which won't match what this machine builds + export SPINDLE_MICROVM_PIPELINES_DEFAULT_IMAGE=nixos + # a normal user has no delegated cgroup subtree, so cgroups stay off + export SPINDLE_MICROVM_PIPELINES_ENABLE_CGROUPS=false +fi + +default='exec "$SPINDLE_BIN" run' +exec sh -c "${SPINDLE_CMD:-$default}" diff --git a/localinfra/pc/services/spindle/setup b/localinfra/pc/services/spindle/setup new file mode 100644 index 000000000..bd53fc747 --- /dev/null +++ b/localinfra/pc/services/spindle/setup @@ -0,0 +1,28 @@ +#!/bin/sh +set -eu + +# plain go build skips the hydrant rust staticlib, so build the flake package +if [ -z "${SPINDLE_CMD:-}" ] && [ "$SPINDLE_BIN" = "$SPINDLE_BUILT" ]; then + out=$(cd "$TNGL_ROOT" && nix build .#spindle --no-link --print-out-paths) + mkdir -p "$TNGL_STATE/bin" + ln -sf "$out/bin/spindle" "$SPINDLE_BUILT" +fi + +if profile-on microvm; then + arch=$(uname -m) + mkdir -p "$SPINDLE_IMAGE_DIR" + for name in ${SPINDLE_IMAGES:-nixos alpine}; do + tarball=$(cd "$TNGL_ROOT" && nix build ".#spindle-$name-image-tarball" --no-link --print-out-paths) + dest=$SPINDLE_IMAGE_DIR/$name-$arch + # outside dest, or the rm -rf on a new tarball takes the stamp with it + stamp=$SPINDLE_IMAGE_DIR/.$name-$arch.tarball + if [ "$(cat "$stamp" 2>/dev/null)" != "$tarball" ]; then + [ -d "$dest" ] && chmod -R +w "$dest" + rm -rf "$dest" + mkdir -p "$dest" + tar -C "$dest" -xzf "$tarball" + echo "$tarball" > "$stamp" + fi + ln -sfn "$name-$arch" "$SPINDLE_IMAGE_DIR/$name" + done +fi diff --git a/localinfra/pc/services/tempo/commands.env b/localinfra/pc/services/tempo/commands.env new file mode 100644 index 000000000..65c700828 --- /dev/null +++ b/localinfra/pc/services/tempo/commands.env @@ -0,0 +1 @@ +TEMPO_BIN=${TEMPO_BIN:-tempo} diff --git a/localinfra/pc/services/tempo/doctor b/localinfra/pc/services/tempo/doctor new file mode 100644 index 000000000..317176c43 --- /dev/null +++ b/localinfra/pc/services/tempo/doctor @@ -0,0 +1 @@ +[ -n "${TEMPO_CMD:-}" ] || need "$TEMPO_BIN" diff --git a/localinfra/pc/services/tempo/packages b/localinfra/pc/services/tempo/packages new file mode 100644 index 000000000..e764a30b3 --- /dev/null +++ b/localinfra/pc/services/tempo/packages @@ -0,0 +1 @@ +tempo diff --git a/localinfra/pc/services/tempo/ports.env b/localinfra/pc/services/tempo/ports.env new file mode 100644 index 000000000..7dff7798e --- /dev/null +++ b/localinfra/pc/services/tempo/ports.env @@ -0,0 +1,3 @@ +TNGL_TEMPO_PORT=${TNGL_TEMPO_PORT:-52753} +TNGL_TEMPO_GRPC_PORT=${TNGL_TEMPO_GRPC_PORT:-52754} +TNGL_TEMPO_OTLP_PORT=${TNGL_TEMPO_OTLP_PORT:-52758} diff --git a/localinfra/pc/services/tempo/process.yaml b/localinfra/pc/services/tempo/process.yaml new file mode 100644 index 000000000..90cbd8b10 --- /dev/null +++ b/localinfra/pc/services/tempo/process.yaml @@ -0,0 +1,12 @@ +version: "0.5" +processes: + tempo: + command: offline ${TNGL_PC}/services/tempo/run + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: /ready + port: ${TNGL_TEMPO_PORT} + period_seconds: 1 + failure_threshold: 60 diff --git a/localinfra/pc/services/tempo/profile b/localinfra/pc/services/tempo/profile new file mode 100644 index 000000000..d57392940 --- /dev/null +++ b/localinfra/pc/services/tempo/profile @@ -0,0 +1 @@ +observability diff --git a/localinfra/pc/services/tempo/run b/localinfra/pc/services/tempo/run new file mode 100755 index 000000000..fc57f8af5 --- /dev/null +++ b/localinfra/pc/services/tempo/run @@ -0,0 +1,33 @@ +#!/bin/sh +set -eu +export data=$TNGL_STATE/data/tempo +mkdir -p "$data" + +cat > "$data/tempo.yml" </dev/null) +fi +if [ -z "${TRANQUIL_CMD:-}" ] && [ "$TRANQUIL_BIN" = "$TRANQUIL_BUILT" ] && [ ! -x "$TRANQUIL_BUILT" ]; then + (cd "$src" && SQLX_OFFLINE=true cargo build --locked -p tranquil-server --features reqwest/rustls-tls-native-roots) +fi diff --git a/localinfra/pc/services/web/Caddyfile b/localinfra/pc/services/web/Caddyfile new file mode 100644 index 000000000..4e3d31fa2 --- /dev/null +++ b/localinfra/pc/services/web/Caddyfile @@ -0,0 +1,9 @@ +web.tngl.boltless.dev { + tls internal + # macos drops past a backlog of 128 and a page asks for a hundred chunks, so caddy queues the rest + reverse_proxy 127.0.0.1:{$TNGL_WEB_PORT} { + transport http { + max_conns_per_host 32 + } + } +} diff --git a/localinfra/pc/services/web/commands.env b/localinfra/pc/services/web/commands.env new file mode 100644 index 000000000..5dfc93533 --- /dev/null +++ b/localinfra/pc/services/web/commands.env @@ -0,0 +1,2 @@ +WEB_BUILT=$TNGL_ROOT/web/node_modules/.bin/vite +WEB_BIN=${WEB_BIN:-$WEB_BUILT} diff --git a/localinfra/pc/services/web/doctor b/localinfra/pc/services/web/doctor new file mode 100644 index 000000000..ba2b84116 --- /dev/null +++ b/localinfra/pc/services/web/doctor @@ -0,0 +1,3 @@ +if [ -z "${WEB_CMD:-}" ]; then + if [ "$WEB_BIN" = "$WEB_BUILT" ]; then need node corepack; else need "$WEB_BIN"; fi +fi diff --git a/localinfra/pc/services/web/packages b/localinfra/pc/services/web/packages new file mode 100644 index 000000000..fc406b7e5 --- /dev/null +++ b/localinfra/pc/services/web/packages @@ -0,0 +1 @@ +nodejs_22 diff --git a/localinfra/pc/services/web/ports.env b/localinfra/pc/services/web/ports.env new file mode 100644 index 000000000..15ffbd948 --- /dev/null +++ b/localinfra/pc/services/web/ports.env @@ -0,0 +1 @@ +TNGL_WEB_PORT=${TNGL_WEB_PORT:-52720} diff --git a/localinfra/pc/services/web/process.yaml b/localinfra/pc/services/web/process.yaml new file mode 100644 index 000000000..6ce442312 --- /dev/null +++ b/localinfra/pc/services/web/process.yaml @@ -0,0 +1,19 @@ +version: "0.5" +processes: + web: + command: offline ${TNGL_PC}/services/web/run + depends_on: + dns: + condition: process_healthy + caddy: + condition: process_healthy + bobbin: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: / + port: ${TNGL_WEB_PORT} + period_seconds: 1 + failure_threshold: 120 diff --git a/localinfra/pc/services/web/run b/localinfra/pc/services/web/run new file mode 100755 index 000000000..913ea5a0c --- /dev/null +++ b/localinfra/pc/services/web/run @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu +set -a +. "$TNGL_PC/services/web/web.env" +set +a + +default='cd "$TNGL_ROOT/web" && exec "$WEB_BIN" preview --host 127.0.0.1 --port "$TNGL_WEB_PORT" --strictPort' +exec sh -c "${WEB_CMD:-$default}" diff --git a/localinfra/pc/services/web/setup b/localinfra/pc/services/web/setup new file mode 100755 index 000000000..e54a09a16 --- /dev/null +++ b/localinfra/pc/services/web/setup @@ -0,0 +1,24 @@ +#!/bin/sh +set -eu +[ -z "${WEB_CMD:-}" ] && [ "$WEB_BIN" = "$WEB_BUILT" ] || exit 0 +web=$TNGL_ROOT/web +data=$TNGL_STATE/data/web +mkdir -p "$data" + +# the ghstub client id and secret have to match make-certs.sh and github-stub +if [ ! -s "$TNGL_CERTS/github.env" ]; then + secret=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n') + printf 'GITHUB_CLIENT_ID=ghstub-client-id\nGITHUB_CLIENT_SECRET=ghstub-client-secret\nGITHUB_SESSION_SECRET=%s\nGITHUB_APP_SLUG=tangled-dev-stub\n' "$secret" > "$TNGL_CERTS/github.env" +fi + +want="$(node "$TNGL_ROOT/e2e/harness/web-hash.ts") $(cksum < "$TNGL_PC/services/web/web.env" | cut -d ' ' -f 1)" +[ "$(cat "$data/built" 2>/dev/null || true)" != "$want" ] || { patch-workerd "$web"; exit 0; } + +set -a +. "$TNGL_PC/services/web/web.env" +set +a +cd "$web" +corepack pnpm install --frozen-lockfile +patch-workerd "$web" +"$WEB_BIN" build +echo "$want" > "$data/built" diff --git a/localinfra/pc/services/web/web.env b/localinfra/pc/services/web/web.env new file mode 100644 index 000000000..c36be8c01 --- /dev/null +++ b/localinfra/pc/services/web/web.env @@ -0,0 +1,31 @@ +# VITE_ vars are baked in at build time, so a change needs a web rebuild +. "$TNGL_CERTS/github.env" +SVELTE_ADAPTER=cloudflare +E2E_TESTIDS=true +BOBBIN_URL=https://bobbin.tngl.boltless.dev +TANGLED_API_URL=http://127.0.0.1:3000 +SITES_DOMAIN=tngl.io +DELIBERI_URL=https://deliberi.tngl.boltless.dev +PRIMARY_KNOT=knot2.tngl.boltless.dev +PRIMARY_SPINDLE=spindle.tngl.boltless.dev +DELEGATION_URL=https://delegates.tngl.boltless.dev +DELEGATION_DID=did:web:tranquil.tngl.boltless.dev +DELEGATION_HANDLE_DOMAIN=tranquil.tngl.boltless.dev +CAMO_URL=https://camo.tngl.boltless.dev +CAMO_SHARED_SECRET=localinfra-camo-secret +AVATAR_URL=https://avatar.tngl.boltless.dev +AVATAR_SHARED_SECRET=localinfra-avatar-secret +MODERATION_SERVICE_DID=did:plc:zu6y5f5sbudyxhv5ytiirkjx +VITE_HANDLE_RESOLVER_URL=https://pds.tngl.boltless.dev +VITE_PLC_DIRECTORY_URL=https://plc.tngl.boltless.dev +MIGRATOR_URL=https://migrator.tngl.boltless.dev +GITHUB_PUBLIC_ORIGIN=https://github.tngl.boltless.dev +GITHUB_API_ORIGIN=https://github.tngl.boltless.dev +GITHUB_AVATARS_ORIGIN=https://github.tngl.boltless.dev +TURNSTILE_SITE_KEY=${TANGLED_CLOUDFLARE_TURNSTILE_SITE_KEY:-} +VITE_DEV_PORT=$TNGL_WEB_PORT +VITE_ALLOWED_HOSTS=.tngl.boltless.dev +VITE_OAUTH_CLIENT_ID=https://web.tngl.boltless.dev/oauth-client-metadata.json +VITE_OAUTH_REDIRECT_URI=https://web.tngl.boltless.dev/oauth/callback +NODE_EXTRA_CA_CERTS=$TNGL_CERTS/root.crt +WRANGLER_SEND_METRICS=false diff --git a/localinfra/pc/services/zoekt-tngl-indexserver/commands.env b/localinfra/pc/services/zoekt-tngl-indexserver/commands.env new file mode 100644 index 000000000..5beb4d198 --- /dev/null +++ b/localinfra/pc/services/zoekt-tngl-indexserver/commands.env @@ -0,0 +1 @@ +ZOEKT_TNGL_INDEXSERVER_BIN=${ZOEKT_TNGL_INDEXSERVER_BIN:-$TNGL_STATE/bin/zoekt-tngl-indexserver} diff --git a/localinfra/pc/services/zoekt-tngl-indexserver/doctor b/localinfra/pc/services/zoekt-tngl-indexserver/doctor new file mode 100644 index 000000000..d65fa44a9 --- /dev/null +++ b/localinfra/pc/services/zoekt-tngl-indexserver/doctor @@ -0,0 +1,4 @@ +case "${ZOEKT_TNGL_INDEXSERVER_BIN:-}" in +"$TNGL_STATE/bin/zoekt-tngl-indexserver") [ -n "${ZOEKT_TNGL_INDEXSERVER_CMD:-}" ] || need go git ;; +*) [ -n "${ZOEKT_TNGL_INDEXSERVER_CMD:-}" ] || need "$ZOEKT_TNGL_INDEXSERVER_BIN" ;; +esac diff --git a/localinfra/pc/services/zoekt-tngl-indexserver/packages b/localinfra/pc/services/zoekt-tngl-indexserver/packages new file mode 100644 index 000000000..5664e303b --- /dev/null +++ b/localinfra/pc/services/zoekt-tngl-indexserver/packages @@ -0,0 +1 @@ +git diff --git a/localinfra/pc/services/zoekt-tngl-indexserver/ports.env b/localinfra/pc/services/zoekt-tngl-indexserver/ports.env new file mode 100644 index 000000000..abaff7543 --- /dev/null +++ b/localinfra/pc/services/zoekt-tngl-indexserver/ports.env @@ -0,0 +1 @@ +TNGL_ZOEKT_TNGL_INDEXSERVER_PORT=${TNGL_ZOEKT_TNGL_INDEXSERVER_PORT:-52641} diff --git a/localinfra/pc/services/zoekt-tngl-indexserver/process.yaml b/localinfra/pc/services/zoekt-tngl-indexserver/process.yaml new file mode 100644 index 000000000..c3b6e9b60 --- /dev/null +++ b/localinfra/pc/services/zoekt-tngl-indexserver/process.yaml @@ -0,0 +1,23 @@ +version: "0.5" +processes: + zoekt-tngl-indexserver: + command: offline ${TNGL_PC}/services/zoekt-tngl-indexserver/run + depends_on: + init-data: + condition: process_completed_successfully + knot2: + condition: process_healthy + caddy: + condition: process_healthy + dns: + condition: process_healthy + plc: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: / + port: ${TNGL_ZOEKT_TNGL_INDEXSERVER_PORT} + period_seconds: 1 + failure_threshold: 30 diff --git a/localinfra/pc/services/zoekt-tngl-indexserver/run b/localinfra/pc/services/zoekt-tngl-indexserver/run new file mode 100755 index 000000000..56018a204 --- /dev/null +++ b/localinfra/pc/services/zoekt-tngl-indexserver/run @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu +index_dir=$TNGL_STATE/data/zoekt-index +mkdir -p "$index_dir" "$TNGL_STATE/data/zoekt-tngl-indexserver" +export PATH=$TNGL_STATE/bin:$PATH +export TANGLED_ZOEKT_INDEX_DIR=$index_dir +export TANGLED_ZOEKT_PLC_URL=${TNGL_PLC_URL:-https://plc.tngl.boltless.dev} +export TANGLED_ZOEKT_ALLOW_HTTP=true +# result links use this as their base, so it has to be the web app +export TANGLED_ZOEKT_APPVIEW_URL=https://web.tngl.boltless.dev +export TANGLED_ZOEKT_SERVER_LISTEN=127.0.0.1:$TNGL_ZOEKT_TNGL_INDEXSERVER_PORT +# local knots and plc use the dev ca, which the system store doesn't trust +export SSL_CERT_FILE=$TNGL_CERTS/root.crt +export GIT_SSL_CAINFO=$TNGL_CERTS/root.crt +default='exec "$ZOEKT_TNGL_INDEXSERVER_BIN" serve' +exec sh -c "${ZOEKT_TNGL_INDEXSERVER_CMD:-$default}" diff --git a/localinfra/pc/services/zoekt-tngl-indexserver/setup b/localinfra/pc/services/zoekt-tngl-indexserver/setup new file mode 100755 index 000000000..a94322da0 --- /dev/null +++ b/localinfra/pc/services/zoekt-tngl-indexserver/setup @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu +mkdir -p "$TNGL_STATE/bin" +if [ ! -f "$TNGL_STATE/bin/zoekt-tngl-indexserver" ]; then + (cd "$TNGL_ROOT" && go build -o "$TNGL_STATE/bin/zoekt-tngl-indexserver" ./cmd/zoekt-tngl-indexserver) +fi +if [ ! -f "$TNGL_STATE/bin/zoekt-git-index" ]; then + zoekt_src=$TNGL_STATE/src/zoekt + if [ ! -d "$zoekt_src/.git" ]; then + mkdir -p "$zoekt_src" + git -C "$zoekt_src" init -q + git -C "$zoekt_src" fetch -q --depth 1 https://tangled.org/boltless.me/zoekt.git cfc4aa0d9ea620143fef099bfed61a88e434c9cd + git -C "$zoekt_src" checkout -q FETCH_HEAD + fi + (cd "$zoekt_src" && go build -o "$TNGL_STATE/bin/zoekt-git-index" ./cmd/zoekt-git-index) +fi diff --git a/localinfra/pc/services/zoekt-webserver/Caddyfile b/localinfra/pc/services/zoekt-webserver/Caddyfile new file mode 100644 index 000000000..1e014084b --- /dev/null +++ b/localinfra/pc/services/zoekt-webserver/Caddyfile @@ -0,0 +1,11 @@ +zoekt.tngl.boltless.dev { + tls internal + + handle_path /indexserver/* { + reverse_proxy 127.0.0.1:{$TNGL_ZOEKT_TNGL_INDEXSERVER_PORT:52641} + } + + handle { + reverse_proxy 127.0.0.1:{$TNGL_ZOEKT_WEBSERVER_PORT:52640} + } +} diff --git a/localinfra/pc/services/zoekt-webserver/commands.env b/localinfra/pc/services/zoekt-webserver/commands.env new file mode 100644 index 000000000..56239d89f --- /dev/null +++ b/localinfra/pc/services/zoekt-webserver/commands.env @@ -0,0 +1 @@ +ZOEKT_WEBSERVER_BIN=${ZOEKT_WEBSERVER_BIN:-$TNGL_STATE/bin/zoekt-webserver} diff --git a/localinfra/pc/services/zoekt-webserver/doctor b/localinfra/pc/services/zoekt-webserver/doctor new file mode 100644 index 000000000..67a5397cf --- /dev/null +++ b/localinfra/pc/services/zoekt-webserver/doctor @@ -0,0 +1,4 @@ +case "${ZOEKT_WEBSERVER_BIN:-}" in +"$TNGL_STATE/bin/zoekt-webserver") [ -n "${ZOEKT_WEBSERVER_CMD:-}" ] || need go git ;; +*) [ -n "${ZOEKT_WEBSERVER_CMD:-}" ] || need "$ZOEKT_WEBSERVER_BIN" ;; +esac diff --git a/localinfra/pc/services/zoekt-webserver/packages b/localinfra/pc/services/zoekt-webserver/packages new file mode 100644 index 000000000..5664e303b --- /dev/null +++ b/localinfra/pc/services/zoekt-webserver/packages @@ -0,0 +1 @@ +git diff --git a/localinfra/pc/services/zoekt-webserver/ports.env b/localinfra/pc/services/zoekt-webserver/ports.env new file mode 100644 index 000000000..9fc638a7f --- /dev/null +++ b/localinfra/pc/services/zoekt-webserver/ports.env @@ -0,0 +1 @@ +TNGL_ZOEKT_WEBSERVER_PORT=${TNGL_ZOEKT_WEBSERVER_PORT:-52640} diff --git a/localinfra/pc/services/zoekt-webserver/process.yaml b/localinfra/pc/services/zoekt-webserver/process.yaml new file mode 100644 index 000000000..de621e004 --- /dev/null +++ b/localinfra/pc/services/zoekt-webserver/process.yaml @@ -0,0 +1,17 @@ +version: "0.5" +processes: + zoekt-webserver: + command: offline ${TNGL_PC}/services/zoekt-webserver/run + depends_on: + init-data: + condition: process_completed_successfully + caddy: + condition: process_healthy + readiness_probe: + http_get: + host: 127.0.0.1 + scheme: http + path: / + port: ${TNGL_ZOEKT_WEBSERVER_PORT} + period_seconds: 1 + failure_threshold: 30 diff --git a/localinfra/pc/services/zoekt-webserver/run b/localinfra/pc/services/zoekt-webserver/run new file mode 100755 index 000000000..54524aba9 --- /dev/null +++ b/localinfra/pc/services/zoekt-webserver/run @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu +export ZOEKT_INDEX_DIR=${ZOEKT_INDEX_DIR:-$TNGL_STATE/data/zoekt-index} +mkdir -p "$ZOEKT_INDEX_DIR" "$TNGL_STATE/data/zoekt-webserver" +default='exec "$ZOEKT_WEBSERVER_BIN" -listen 127.0.0.1:$TNGL_ZOEKT_WEBSERVER_PORT -index "'"$ZOEKT_INDEX_DIR"'" -pprof -rpc' +exec sh -c "${ZOEKT_WEBSERVER_CMD:-$default}" diff --git a/localinfra/pc/services/zoekt-webserver/setup b/localinfra/pc/services/zoekt-webserver/setup new file mode 100755 index 000000000..5af91f0b7 --- /dev/null +++ b/localinfra/pc/services/zoekt-webserver/setup @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu +mkdir -p "$TNGL_STATE/src" "$TNGL_STATE/bin" +zoekt_src=$TNGL_STATE/src/zoekt +if [ ! -d "$zoekt_src/.git" ]; then + mkdir -p "$zoekt_src" + git -C "$zoekt_src" init -q + git -C "$zoekt_src" fetch -q --depth 1 https://tangled.org/boltless.me/zoekt.git cfc4aa0d9ea620143fef099bfed61a88e434c9cd + git -C "$zoekt_src" checkout -q FETCH_HEAD +fi +if [ ! -f "$TNGL_STATE/bin/zoekt-webserver" ]; then + (cd "$zoekt_src" && go build -o "$TNGL_STATE/bin/zoekt-webserver" ./cmd/zoekt-webserver) +fi +if [ ! -f "$TNGL_STATE/bin/zoekt-git-index" ]; then + (cd "$zoekt_src" && go build -o "$TNGL_STATE/bin/zoekt-git-index" ./cmd/zoekt-git-index) +fi diff --git a/localinfra/scripts/init-data.sh b/localinfra/scripts/init-data.sh index 776cc112e..eabf0dc8b 100644 --- a/localinfra/scripts/init-data.sh +++ b/localinfra/scripts/init-data.sh @@ -6,7 +6,7 @@ # - load records to users PDS set -eu -. /scripts/lib.sh +. "${SCRIPTS:-/scripts}/lib.sh" ALICE_DID=did:plc:igzuueb63dytyy3tpaweofuu BOB_DID=did:plc:xbserj4jfuhzkbl3mszkvvrb @@ -60,7 +60,7 @@ printf '%s' "$SYSTEM_DID" > "${SHARED_DIR}/system-did" printf '[system] %s → %s/system-did\n' "$SYSTEM_USER" "$SHARED_DIR" >&2 seed account moderation "$MODERATION_DID" >/dev/null -seed labeler "$MODERATION_DID" "$OZONE_PUBLIC_URL" +[ -z "${OZONE_PUBLIC_URL:-}" ] || seed labeler "$MODERATION_DID" "$OZONE_PUBLIC_URL" # tranquil-pds account helper: same shape as add-accounts.sh, but against # tranquil's own URL/hostname, password, and no invite code. @@ -188,6 +188,21 @@ knot_create() { printf '[knot] repo %s\n' "$2" >&2 } +# the knot reports fetching until the source import is done, and a fork copies that source +wait_import() { + wi_attempt=0 + while [ "$wi_attempt" -lt 60 ]; do + # a failed describe keeps polling, so an unreachable knot never counts as done + if wi_body=$(curl -fsS "${KNOT_URL}/xrpc/sh.tangled.repo.describeRepo?repoDid=$1"); then + wi_content=$(printf '%s' "$wi_body" | jq -r '.content // empty') + [ "$wi_content" = fetching ] || return 0 + fi + wi_attempt=$((wi_attempt + 1)) + sleep 2 + done + fail "the import of $1 never finished" +} + # --- knot2 fixtures --- curl -fsS -o /dev/null \ @@ -199,6 +214,7 @@ printf '[knot2] member did:plc:xbserj4jfuhzkbl3mszkvvrb\n' >&2 create_repo did:plc:igzuueb63dytyy3tpaweofuu eab5nhp7li3ul6pnyltb4eri44 did:plc:2pt74ankjflp6tryyp7aloqe '{"name":"core","defaultBranch":"master","did":"did:plc:2pt74ankjflp6tryyp7aloqe","source":{"$type":"org.tangled.repo.defs#sourceExternal","uri":"https://knot1.tangled.sh/did:plc:ioighzh4jnrhybayw65gfbia"}}' create_repo did:plc:xbserj4jfuhzkbl3mszkvvrb u6rcitsebxtvqt2ghf2dcdvmeu did:plc:35vbwlqbtciobm5emdvgg7by '{"name":"empty-repo","defaultBranch":"master","did":"did:plc:35vbwlqbtciobm5emdvgg7by"}' +wait_import did:plc:2pt74ankjflp6tryyp7aloqe create_repo did:plc:xbserj4jfuhzkbl3mszkvvrb i5jq24nrdl7bvjetvflion5f34 did:plc:5a5citbzkm5lnzxpoxbhvpae '{"name":"core-fork","defaultBranch":"master","did":"did:plc:5a5citbzkm5lnzxpoxbhvpae","source":{"$type":"org.tangled.repo.defs#sourceTangled","did":"did:plc:2pt74ankjflp6tryyp7aloqe"}}' # --- repo fixtures --- @@ -294,6 +310,7 @@ put_record at://did:plc:xbserj4jfuhzkbl3mszkvvrb/sh.tangled.graph.vouch/did:plc: # knot rkeys are knot-local; core, core-fork and empty-repo are taken by the sh.tangled repos above create_repo did:plc:igzuueb63dytyy3tpaweofuu mrnt3a4g3bjjzj3odx3ruuxwdq did:plc:b5yvrkvscma5lulapq4wgb3l '{"name":"org-core","defaultBranch":"master","did":"did:plc:b5yvrkvscma5lulapq4wgb3l","source":{"$type":"org.tangled.repo.defs#sourceExternal","uri":"https://knot1.tangled.sh/did:plc:ioighzh4jnrhybayw65gfbia"}}' +wait_import did:plc:b5yvrkvscma5lulapq4wgb3l create_repo did:plc:xbserj4jfuhzkbl3mszkvvrb dql6gpe3nhnl6cct5lij6ii6ai did:plc:ar26riy42ojkoa2d7hkqzfzr '{"name":"org-core-fork","defaultBranch":"master","did":"did:plc:ar26riy42ojkoa2d7hkqzfzr","source":{"$type":"org.tangled.repo.defs#sourceTangled","did":"did:plc:b5yvrkvscma5lulapq4wgb3l"}}' create_repo did:plc:xbserj4jfuhzkbl3mszkvvrb tee7mkhzubkuk5q25w3phbxuqe did:plc:y2lr7veruwjr3v6hb3qbnzgg '{"name":"org-empty-repo","defaultBranch":"master","did":"did:plc:y2lr7veruwjr3v6hb3qbnzgg"}' diff --git a/localinfra/scripts/lib.sh b/localinfra/scripts/lib.sh index bf1494f61..547fc6955 100644 --- a/localinfra/scripts/lib.sh +++ b/localinfra/scripts/lib.sh @@ -62,7 +62,7 @@ put_record() { pr_rest=${pr_rest#*/} pr_collection=${pr_rest%%/*} pr_rkey=${pr_rest#*/} - pr_record=$(cat "/fixtures/records/$pr_did/$pr_collection/$pr_rkey/$2.json") + pr_record=$(cat "${FIXTURES:-/fixtures}/records/$pr_did/$pr_collection/$pr_rkey/$2.json") # one login per record — the dev PDS runs with rate limits disabled pr_jwt=$(login "$pr_did") @@ -88,7 +88,7 @@ put_record() { # upload_blob DID CID MIME_TYPE # → uploads /fixtures/blobs/DID/CID, fails unless the pds returns CID. upload_blob() { - ub_cid=$(curl -fsS --data-binary "@/fixtures/blobs/$1/$2" \ + ub_cid=$(curl -fsS --data-binary "@${FIXTURES:-/fixtures}/blobs/$1/$2" \ -H "Content-Type: $3" \ -H "Authorization: Bearer $(login "$1")" \ "${PDS_URL}/xrpc/com.atproto.repo.uploadBlob" \ diff --git a/nix/pkgs/hydrant-bin.nix b/nix/pkgs/hydrant-bin.nix new file mode 100644 index 000000000..d21b8aa89 --- /dev/null +++ b/nix/pkgs/hydrant-bin.nix @@ -0,0 +1,10 @@ +{ + callPackage, + rustPlatform, + hydrant, +}: +# without the jetstream feature there is no /subscribe feed for spindle, appview and deliberi +# the derivation reads cargoBuildFeatures, so a buildFeatures override silently does nothing +(callPackage "${hydrant}/default.nix" {inherit rustPlatform;}).overrideAttrs { + cargoBuildFeatures = ["jetstream"]; +}