diff --git a/.env.example b/.env.example index e6e861f685..9acbfb6e33 100644 --- a/.env.example +++ b/.env.example @@ -225,13 +225,13 @@ GITTENSORY_REVIEW_DRAFT=false # # write trust can differ. Unrelated to ADMIN_GITHUB_LOGINS, which # # scopes dashboard sign-in, not the shared MCP token's per-repo reach. # PORT=8787 -# DATABASE_PATH=/data/gittensory.sqlite # SQLite file on the mounted data volume; all migrations auto-apply +# DATABASE_PATH=/data/loopover.sqlite # SQLite file on the mounted data volume; all migrations auto-apply # POSTGRES_PASSWORD=change-this-long-random-value # used by the --profile postgres / --profile pgbouncer services # DATABASE_URL= # set to postgres://user:pw@host:5432/db to use Postgres instead of # # SQLite (shared DB → multi-instance). Overrides DATABASE_PATH. # # Compose examples: -# # postgres://gittensory:@postgres:5432/gittensory -# # postgres://gittensory:@pgbouncer:5432/gittensory +# # postgres://loopover:@postgres:5432/loopover +# # postgres://loopover:@pgbouncer:5432/loopover # PGVECTOR_ENABLED=false # set true only when using the Postgres pgvector table for RAG. # # Leave false when QDRANT_URL is set; Qdrant remains the preferred # # dedicated vector store for review context at scale. @@ -469,14 +469,14 @@ REDIS_URL=redis://redis:6379 # REQUIRED for the self-host review # RUNNER_REPO_URL=https://github.com/org/repo # RUNNER_ACCESS_TOKEN= # PAT with repo scope (alternative to RUNNER_TOKEN) # RUNNER_SCOPE=repo # repo | org | enterprise -# RUNNER_NAME=gittensory-runner +# RUNNER_NAME=loopover-runner # RUNNER_LABELS=self-hosted,linux # RUNNER_MEM_LIMIT=2g # per-runner-container memory ceiling; raise for memory-heavy CI jobs # --- Docker disk hygiene (#audit-rate-headroom / #selfhost-runtime-pressure) --- # Build cache and unused images accumulate fast on a box that builds from source or runs CI runners; a root # disk over ~80-85% full slows down the WHOLE host (fsync latency, container scheduling), not just Docker. -# Install the systemd timer (systemd/gittensory-docker-prune.{service,timer}.example) to reclaim stopped +# Install the systemd timer (systemd/loopover-docker-prune.{service,timer}.example) to reclaim stopped # containers, unused images, and build cache on a schedule -- never volumes/application data. Run it # manually at any time with `sh scripts/selfhost-docker-prune.sh`, or preview first with `--dry-run`. # GITTENSORY_DOCKER_PRUNE_RETAIN_HOURS=168 # age floor before something unused is eligible for pruning (7 days) diff --git a/.env.selfhost.example b/.env.selfhost.example index 2d8903f986..3690df5c7d 100644 --- a/.env.selfhost.example +++ b/.env.selfhost.example @@ -55,7 +55,7 @@ ADMIN_GITHUB_LOGINS=your-github-login # SQLite is the default database (this file on the mounted data volume; migrations auto-apply). # It's fine for a single maintainer instance. Move to Postgres (--profile postgres, see # .env.example) once you're running multiple instances or a serious volume of repos. -DATABASE_PATH=/data/gittensory.sqlite +DATABASE_PATH=/data/loopover.sqlite # Redis is REQUIRED and starts automatically with the default compose stack (webhook dedup, rate # limiting, the GitHub GET-response cache). Only override this if you're pointing at an external diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-ai-providers.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-ai-providers.tsx index a9a5d5e5c8..99019fef33 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-ai-providers.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-ai-providers.tsx @@ -185,7 +185,7 @@ CLAUDE_AI_EFFORT=medium`} creation time, so a plain restart keeps serving the old value — recreate the service instead:

- +

Prefer not pasting the raw token into .env? Write it into{" "} secrets/claude_code_oauth_token.txt instead (see{" "} diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx index 3eff2db4d3..1026679e0b 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx @@ -55,7 +55,7 @@ LITESTREAM_REGION=us-east-1`}

Scheduled backups

The bundled backup profile writes the active app database to the{" "} - gittensory-backups volume. SQLite installs use an online backup; Postgres + loopover-backups volume. SQLite installs use an online backup; Postgres installs use pg_dump. The same run also snapshots Qdrant when it is enabled.

@@ -64,7 +64,7 @@ LITESTREAM_REGION=us-east-1`}

Each run keeps the newest BACKUP_RETAIN backups (default 7) — applied independently per target: postgres/, sqlite/, and{" "} - qdrant/ in the gittensory-backups volume each retain their own + qdrant/ in the loopover-backups volume each retain their own newest 7, not 7 combined across all three. Set it in .env to change the window:

@@ -108,7 +108,7 @@ LITESTREAM_REGION=us-east-1`} -DATABASE_URL=postgres://gittensory:@pgbouncer:5432/gittensory +DATABASE_URL=postgres://loopover:@pgbouncer:5432/loopover REDIS_URL=redis://redis:6379 QDRANT_URL=http://qdrant:6333`} /> @@ -131,9 +131,9 @@ QDRANT_URL=http://qdrant:6333`}

@pgbouncer:5432/gittensory -npm run selfhost:postgres:migrate -- --sqlite /data/gittensory.sqlite -npm run selfhost:postgres:migrate -- --sqlite /data/gittensory.sqlite --execute`} + code={`export DATABASE_URL=postgres://loopover:@pgbouncer:5432/loopover +npm run selfhost:postgres:migrate -- --sqlite /data/loopover.sqlite +npm run selfhost:postgres:migrate -- --sqlite /data/loopover.sqlite --execute`} />

Restore checks

diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-docs-audit.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-docs-audit.tsx index e6c24ca19f..4e6632911b 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-docs-audit.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-docs-audit.tsx @@ -130,7 +130,7 @@ function SelfHostingDocsAudit() {
  • Operator-owned paths: gittensory-config/,{" "} - gittensory-data, and secrets via .env or *_FILE{" "} + loopover-data, and secrets via .env or *_FILE{" "} mounts — never baked into images.
  • diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx index 9962358ef9..50ad3918c2 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx @@ -55,7 +55,7 @@ function SelfHostingOperations() { @@ -406,7 +406,7 @@ DISCORD_REPO_WEBHOOKS={"owner/repoA":"https://discord.com/api/webhooks/...","own expected steady state, not a leak — this instance runs{" "} scripts/deploy-selfhost-prebuilt.sh, which rebuilds the image from the current git checkout on every deploy and intentionally keeps prior layers around in the build cache - for faster rebuilds. The gittensory-docker-safe-prune systemd timer (below) + for faster rebuilds. The loopover-docker-safe-prune systemd timer (below) already runs daily against this exact instance and reclaims it on a schedule, so this is not a number to chase down manually.

    @@ -486,11 +486,11 @@ DISCORD_REPO_WEBHOOKS={"owner/repoA":"https://discord.com/api/webhooks/...","own

    Run it manually at any time with docker system df before and after to see what @@ -549,7 +549,7 @@ services: condition: service_completed_successfully environment: <<: *runner-tmp-env - RUNNER_NAME: gittensory-runner-2 + RUNNER_NAME: loopover-runner-2 RUNNER_SCOPE: \${RUNNER_SCOPE:-repo} REPO_URL: \${RUNNER_REPO_URL:-} RUNNER_TOKEN: \${RUNNER_TOKEN:-} @@ -581,7 +581,7 @@ SENTRY_RELEASE=gittensory-selfhost@2026.07.05 Official release images bake GITTENSORY_VERSION as the default release id; override with SENTRY_RELEASE when you tag custom builds. Mount secrets with{" "} SENTRY_DSN_FILE instead of inline env when you prefer a file-backed DSN. After - changing Sentry env, restart the gittensory service — there is no hot reload. + changing Sentry env, restart the loopover service — there is no hot reload.

    Community self-hosters should send events only to their own DSN. The shipped stack never @@ -885,8 +885,8 @@ SENTRY_ORG_SLUG=

    Updating and rolling back

    - Day-two operator flow: pull or build a new app image, restart only the{" "} - gittensory service, verify /ready, and confirm the release id. Use{" "} + Day-two operator flow: pull or build a new app image, restart only the loopover{" "} + service, verify /ready, and confirm the release id. Use{" "} Releases and images to pick a tag; use the checklists below so updates never overwrite operator-owned secrets, config, or data.

    @@ -903,9 +903,9 @@ SENTRY_ORG_SLUG= .gittensory.yml policy.
  • - Named data volumes — especially gittensory-data (SQLite DB, Codex/Claude - auth under /data), gittensory-pg, qdrant-data,{" "} - gittensory-backups, and Grafana's grafana-data. + Named data volumes — especially loopover-data (SQLite DB, Codex/Claude auth + under /data), loopover-pg, qdrant-data,{" "} + loopover-backups, and Grafana's grafana-data.
  • Optional docker-compose.override.yml — still loaded via{" "} @@ -918,14 +918,14 @@ SENTRY_ORG_SLUG= @@ -953,7 +953,7 @@ SENTRY_ORG_SLUG=
  • Image path only: note the current tag or digest from docker inspect on the - running gittensory container so rollback has a known-good target. + running loopover container so rollback has a known-good target.
  • Confirm routine health is green before you start —{" "} @@ -964,7 +964,7 @@ SENTRY_ORG_SLUG=

    Path 1: pull a published image

    scripts/deploy-selfhost-image.sh pulls a tag or digest, restarts only the{" "} - gittensory service, waits for it to report healthy via{" "} + loopover service, waits for it to report healthy via{" "} docker inspect's health status (configurable timeout, default 180s), and then persists the resolved image reference back to GITTENSORY_IMAGE in{" "} .env so the next plain invocation reuses it. @@ -1036,7 +1036,7 @@ git merge --ff-only origin/main scripts/deploy-selfhost-prebuilt.sh is the actual rebuild step (this is how{" "} GITTENSORY_VERSION ends up as a short git SHA instead of an image tag). It builds the bundle inside a Dockerized Node container — the host itself never needs Node or - npm installed — then restarts only the gittensory service the same way as the + npm installed — then restarts only the loopover service the same way as the image path. SENTRY_RELEASE defaults to{" "} gittensory-selfhost@<short git SHA of the current HEAD> unless you override it, so each deploy from a new commit gets a distinct release id automatically. When{" "} @@ -1062,7 +1062,7 @@ git merge --ff-only origin/main curl http://localhost:8787/ready returns HTTP 200.

  • - docker compose ps gittensory shows healthy. + docker compose ps loopover shows healthy.
  • Tail logs for selfhost_listening and, on first boot after a schema bump,{" "} @@ -1078,9 +1078,9 @@ git merge --ff-only origin/main code={`./scripts/selfhost-post-update-check.sh # equivalent manual checks: curl -sf http://localhost:8787/ready -docker compose ps gittensory +docker compose ps loopover grep -E '^(GITTENSORY_IMAGE|GITTENSORY_VERSION|SENTRY_RELEASE)=' .env -docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q gittensory)"`} +docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q loopover)"`} />

    If any check fails, see Troubleshooting @@ -1147,8 +1147,8 @@ docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q gittensory)"

    Stopping the container does not delete anything — docker compose stop or{" "} docker compose down (without -v) leaves every named volume ( - gittensory-data, gittensory-pg, qdrant-data,{" "} - gittensory-backups, grafana-data, and the rest declared in{" "} + loopover-data, loopover-pg, qdrant-data,{" "} + loopover-backups, grafana-data, and the rest declared in{" "} docker-compose.yml) on disk, along with the ./gittensory-config{" "} host directory (a bind mount, not a named volume, so it is never affected by -v{" "} either way). Pick one: @@ -1168,7 +1168,7 @@ docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q gittensory)" { title: "Delete everything", description: - "docker compose down -v removes every named volume permanently — the review database, vector index, Grafana dashboards state, and any local backup archives in gittensory-backups go with it. This does not touch ./gittensory-config (delete that host directory yourself if it should go too).", + "docker compose down -v removes every named volume permanently — the review database, vector index, Grafana dashboards state, and any local backup archives in loopover-backups go with it. This does not touch ./gittensory-config (delete that host directory yourself if it should go too).", }, ]} /> @@ -1215,13 +1215,13 @@ docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q gittensory)" request (isAuthorizedGitHubSessionLogin in src/auth/security.ts) — it is never cached at startup or baked into an issued session. To remove someone's operator access, delete their login from the comma/whitespace-separated list in{" "} - .env and restart the gittensory service so the process picks up - the new value: + .env and restart the loopover service so the process picks up the + new value:

    This takes effect on their very next control-panel request after the restart — no signed-in diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-security.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-security.tsx index f721b91164..8df5692da9 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-security.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-security.tsx @@ -67,7 +67,7 @@ function SelfHostingSecurity() { filename="shell" code={`./scripts/selfhost-init-secrets.sh # creates empty placeholder files (idempotent) printf '%s' 'your-real-secret-value' > secrets/github_webhook_secret.txt -docker compose up -d --no-deps gittensory`} +docker compose up -d --no-deps loopover`} />

    Private policy

    diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 197bb96683..9feea41a46 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -1,15 +1,15 @@ -# Caddy reverse proxy for gittensory (#980 self-host). +# Caddy reverse proxy for loopover (#980 self-host). # Activated via: docker compose --profile caddy up # # DOMAIN is injected from the DOMAIN env var in docker-compose.yml. # Set DOMAIN=reviews.yourcompany.com in .env — Caddy fetches a TLS cert from Let's Encrypt automatically. # For local testing without a domain, set DOMAIN=localhost (self-signed cert, browser will warn). # -# When using this profile, remove the `ports:` entry from the gittensory service in docker-compose.yml +# When using this profile, remove the `ports:` entry from the loopover service in docker-compose.yml # so port 8787 is NOT exposed publicly — all traffic should flow through Caddy on 443. {$DOMAIN} { - reverse_proxy gittensory:8787 { + reverse_proxy loopover:8787 { # Surface the real client IP to the app (logged in access events). header_up X-Forwarded-For {remote_host} header_up X-Real-IP {remote_host} diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example index 96b5605f1d..ced8e187b6 100644 --- a/docker-compose.override.yml.example +++ b/docker-compose.override.yml.example @@ -1,10 +1,10 @@ -# Optional CPU-priority tuning for boxes running `--profile runners` alongside the main `gittensory` +# Optional CPU-priority tuning for boxes running `--profile runners` alongside the main `loopover` # app on the SAME host. Copy to `docker-compose.override.yml` (gitignored, auto-loaded by `docker # compose` alongside docker-compose.yml) and adjust the numbers for your host if you use this pattern. # # WHY THIS EXISTS: with no limits set (the default -- see docker-compose.yml), every container competes # for the host's CPUs on equal footing. On a box that ALSO runs `--profile runners`, a burst of CI jobs -# can starve the `gittensory` app itself of CPU, slowing down (or queuing up) the very review work the +# can starve the `loopover` app itself of CPU, slowing down (or queuing up) the very review work the # app exists to do -- discovered running this exact pattern in production on an 8-vCPU box: 3 uncapped # runner containers left the app starved under load. # @@ -21,16 +21,16 @@ # scheduler (driven by the relative `cpu_shares` below) resolves real contention when multiple containers # actually want the CPU at once. # -# Sizing guide for a different host: pick a `gittensory` : `runner` cpu_shares ratio reflecting how +# Sizing guide for a different host: pick a `loopover` : `runner` cpu_shares ratio reflecting how # strongly you want review work to win contention (8:1 below is deliberately aggressive), and keep each # runner replica's `cpus` ceiling at roughly (host vCPUs) / (number of runner replicas you plan to run), # so a single busy runner can't monopolize the box even before shares kick in. # # This is the HOST-level (Docker CPU scheduling) half of the fix. The APPLICATION-level half is the -# MAINTENANCE_ADMISSION_* knobs in .env.example: gittensory itself defers its own maintenance sweeps +# MAINTENANCE_ADMISSION_* knobs in .env.example: loopover itself defers its own maintenance sweeps # (contributor evidence, RAG indexing, drift scans...) under queue/host pressure so live webhook/review # work always wins there too. Also see ./scripts/selfhost-docker-prune.sh (and its systemd timer under -# systemd/gittensory-docker-prune.*.example) for the disk-usage side of running CI builds on the same box +# systemd/loopover-docker-prune.*.example) for the disk-usage side of running CI builds on the same box # (build cache/image growth, not just CPU). # The RECOMMENDED alternative to all of this: don't co-locate runners with the review stack at all -- use # GitHub-hosted CI, or a separate dedicated runner host (see the runner service's comment in @@ -47,7 +47,7 @@ # operations docs for a complete, copy-pasteable multi-runner service snippet. services: - gittensory: + loopover: cpu_shares: 4096 runner: diff --git a/docker-compose.yml b/docker-compose.yml index b8999ed90e..77d1994363 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -# Self-host for gittensory (#980). Two ways to boot the `gittensory` service — everything else in +# Self-host for loopover (#980). Two ways to boot the `loopover` service — everything else in # this compose file (profiles, env, health checks) is identical either way: # # RECOMMENDED: ./scripts/deploy-selfhost-image.sh # pulls the published image, no local build @@ -72,7 +72,7 @@ x-runner-tmp-env: &runner-tmp-env services: # ── Core app (always runs) ───────────────────────────────────────────────── - gittensory: + loopover: build: context: . args: @@ -88,7 +88,7 @@ services: required: false environment: PORT: "8787" - DATABASE_PATH: /data/gittensory.sqlite + DATABASE_PATH: /data/loopover.sqlite # Container-private per-repo config (#1390): point at a mounted dir holding {owner}__{repo}/.gittensory.yml # files layered over an optional root .gittensory.yml global default, itself layered over an optional # _shared/.gittensory.yml cross-repo base for multi-repo operators (#1959) — a higher layer overrides only the @@ -108,10 +108,10 @@ services: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: "${OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE:-cumulative}" OTEL_METRIC_EXPORT_INTERVAL: "${OTEL_METRIC_EXPORT_INTERVAL:-10000}" # Uncomment the next two lines and activate --profile postgres to use Postgres: - # DATABASE_URL: postgres://gittensory:${POSTGRES_PASSWORD:-CHANGEME}@postgres:5432/gittensory + # DATABASE_URL: postgres://loopover:${POSTGRES_PASSWORD:-CHANGEME}@postgres:5432/loopover # PGVECTOR_ENABLED: "true" # With --profile pgbouncer, route through the pooler instead of postgres directly: - # DATABASE_URL: postgres://gittensory:${POSTGRES_PASSWORD:-CHANGEME}@pgbouncer:5432/gittensory + # DATABASE_URL: postgres://loopover:${POSTGRES_PASSWORD:-CHANGEME}@pgbouncer:5432/loopover # Required shared transient state for review correctness, webhook dedup, and rate limiting. REDIS_URL: "${REDIS_URL:-redis://redis:6379}" # Enables the GitHub GET-response cache when >0 (0 disables); not a per-entry TTL, see .env.example. @@ -122,8 +122,8 @@ services: # additive, zero effect on an operator who has never touched secrets/. GITHUB_APP_PRIVATE_KEY_FILE: "${GITHUB_APP_PRIVATE_KEY_FILE:-/run/secrets/github_app_private_key}" GITHUB_WEBHOOK_SECRET_FILE: "${GITHUB_WEBHOOK_SECRET_FILE:-/run/secrets/github_webhook_secret}" - GITTENSORY_API_TOKEN_FILE: "${GITTENSORY_API_TOKEN_FILE:-/run/secrets/gittensory_api_token}" - GITTENSORY_MCP_TOKEN_FILE: "${GITTENSORY_MCP_TOKEN_FILE:-/run/secrets/gittensory_mcp_token}" + GITTENSORY_API_TOKEN_FILE: "${GITTENSORY_API_TOKEN_FILE:-/run/secrets/loopover_api_token}" + GITTENSORY_MCP_TOKEN_FILE: "${GITTENSORY_MCP_TOKEN_FILE:-/run/secrets/loopover_mcp_token}" INTERNAL_JOB_TOKEN_FILE: "${INTERNAL_JOB_TOKEN_FILE:-/run/secrets/internal_job_token}" SELFHOST_SETUP_TOKEN_FILE: "${SELFHOST_SETUP_TOKEN_FILE:-/run/secrets/selfhost_setup_token}" TOKEN_ENCRYPTION_SECRET_FILE: "${TOKEN_ENCRYPTION_SECRET_FILE:-/run/secrets/token_encryption_secret}" @@ -141,7 +141,7 @@ services: # short-lived GitHub tokens from the Orb on demand (no own App private key). See .env.example. # ORB_BROKER_URL: https://gittensory-api.aethereal.dev # override only for a private Orb deployment volumes: - - gittensory-data:/data + - loopover-data:/data # Container-private per-repo config dir (read-only). Create ./gittensory-config/{owner}__{repo}/.gittensory.yml # locally, optionally alongside a ./gittensory-config/.gittensory.yml global default that every per-repo file # is deep-merged over (gitignored — never commit real policy; see config/examples/ for generic templates). @@ -162,8 +162,8 @@ services: secrets: - github_app_private_key - github_webhook_secret - - gittensory_api_token - - gittensory_mcp_token + - loopover_api_token + - loopover_mcp_token - internal_job_token - selfhost_setup_token - token_encryption_secret @@ -180,7 +180,7 @@ services: qdrant: condition: service_healthy required: false - # Same pattern for --profile postgres / --profile pgbouncer: without this, compose starts gittensory + # Same pattern for --profile postgres / --profile pgbouncer: without this, compose starts loopover # in parallel with a cold Postgres initdb instead of waiting for it, and the app's own waitForPostgres() # fallback (~30s hard cap) commonly loses that race on a slow disk/first pull, crash-looping the app via # restart: unless-stopped before Postgres is reachable. required:false keeps the default SQLite path @@ -282,11 +282,11 @@ services: <<: *default-logging profiles: ["postgres", "pgbouncer"] environment: - POSTGRES_USER: gittensory + POSTGRES_USER: loopover POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-CHANGEME} - POSTGRES_DB: gittensory + POSTGRES_DB: loopover volumes: - - gittensory-pg:/var/lib/postgresql/data + - loopover-pg:/var/lib/postgresql/data deploy: resources: limits: @@ -296,7 +296,7 @@ services: # the pgvector extension) can plausibly take longer than the 5x10s=50s this healthcheck otherwise # allows before Docker marks the service unhealthy and fails every downstream service_healthy gate. healthcheck: - test: ["CMD-SHELL", "pg_isready -U gittensory"] + test: ["CMD-SHELL", "pg_isready -U loopover"] interval: 10s timeout: 5s start_period: 20s @@ -304,7 +304,7 @@ services: # ── PgBouncer (--profile pgbouncer) ─────────────────────────────────────── # Transaction-mode pooler — allows hundreds of app clients with a small PG connection cap. - # Set DATABASE_URL in gittensory to: postgres://gittensory:@pgbouncer:5432/gittensory + # Set DATABASE_URL in loopover to: postgres://loopover:@pgbouncer:5432/loopover pgbouncer: image: edoburu/pgbouncer:v1.25.2-p0 restart: unless-stopped @@ -316,8 +316,8 @@ services: environment: DB_HOST: postgres DB_PORT: "5432" - DB_NAME: gittensory - DB_USER: gittensory + DB_NAME: loopover + DB_USER: loopover DB_PASSWORD: ${POSTGRES_PASSWORD:-CHANGEME} LISTEN_PORT: "5432" POOL_MODE: transaction @@ -327,7 +327,7 @@ services: # The image ships the full postgresql-client toolchain (Alpine-based), so the same probe postgres itself # uses works here: pg_isready only checks that the server accepts the startup packet, not that auth # succeeds, so it reports "accepting connections" even before the upstream Postgres is reachable through - # the pooler — exactly the pooler-layer liveness signal gittensory's depends_on needs. + # the pooler — exactly the pooler-layer liveness signal loopover's depends_on needs. healthcheck: test: ["CMD", "pg_isready", "-h", "127.0.0.1", "-p", "5432"] interval: 10s @@ -346,8 +346,8 @@ services: postgres: condition: service_healthy environment: - DATA_SOURCE_URI: "postgres:5432/gittensory?sslmode=disable" - DATA_SOURCE_USER: gittensory + DATA_SOURCE_URI: "postgres:5432/loopover?sslmode=disable" + DATA_SOURCE_USER: loopover DATA_SOURCE_PASS: ${POSTGRES_PASSWORD:-CHANGEME} expose: - "9187" @@ -602,7 +602,7 @@ services: restart: unless-stopped <<: *default-logging profiles: ["rees"] - # Deliberately NOT `env_file: .env` like the gittensory service above: that would also hand this + # Deliberately NOT `env_file: .env` like the loopover service above: that would also hand this # analyzer service every unrelated secret in .env, and REES reads the same SENTRY_DSN key name the main # app uses (src/selfhost/sentry.ts) -- blanket-forwarding .env would silently point REES's error reporting # at the main app's Sentry project instead of a dedicated one. Add REES-specific Sentry vars via @@ -623,7 +623,7 @@ services: limits: memory: "${REES_MEM_LIMIT:-512m}" # /health and /ready are both unconditional no-ops (REES has no DB/queue to probe), so unlike the - # gittensory service above there's no deeper readiness signal to prefer here; /health also matches + # loopover service above there's no deeper readiness signal to prefer here; /health also matches # Railway's own healthcheckPath for this service. `node -e fetch(...)` avoids assuming curl/wget exist -- # review-enrichment/Dockerfile's runtime stage is node:22-slim, which installs neither. healthcheck: @@ -649,10 +649,10 @@ services: profiles: ["litestream"] command: replicate depends_on: - gittensory: + loopover: condition: service_healthy volumes: - - gittensory-data:/data + - loopover-data:/data - ./litestream.yml:/etc/litestream.yml:ro environment: LITESTREAM_ACCESS_KEY_ID: ${LITESTREAM_ACCESS_KEY_ID:-} @@ -662,7 +662,7 @@ services: # ── Caddy (--profile caddy) ──────────────────────────────────────────────── # Auto-TLS via Let's Encrypt. Set DOMAIN=reviews.yourcompany.com in .env, then - # remove the plain `ports:` entry from the gittensory service above. + # remove the plain `ports:` entry from the loopover service above. caddy: image: caddy:2-alpine restart: unless-stopped @@ -679,7 +679,7 @@ services: environment: DOMAIN: ${DOMAIN:-localhost} depends_on: - gittensory: + loopover: condition: service_healthy # ── Observability (--profile observability) ──────────────────────────────── @@ -787,11 +787,11 @@ services: <<: *default-logging profiles: ["observability"] depends_on: - gittensory: + loopover: condition: service_healthy volumes: # The exporter needs live DB read access to build the redacted snapshot. Grafana does not get this mount. - - gittensory-data:/appdb:ro + - loopover-data:/appdb:ro - grafana-reporting-data:/reporting # A DIRECTORY bind mount, not a single-file one (#reporting-exporter-stale-bind-mount): a single-file # bind mount pins the container to the file's INODE at container-creation time. `git pull` replaces a @@ -803,15 +803,15 @@ services: # provisioning below already does. - ./scripts:/scripts:ro environment: - # Default SQLite app DB path maps app /data/gittensory.sqlite to exporter /appdb/gittensory.sqlite. + # Default SQLite app DB path maps app /data/loopover.sqlite to exporter /appdb/loopover.sqlite. # If you override DATABASE_PATH, set this to the matching /appdb/ path. When DATABASE_URL points at # Postgres, the exporter reads Postgres with psql and still writes the same redacted SQLite reporting DB # for Grafana. Set GITTENSORY_REPORTING_SOURCE_DATABASE_URL only if the reporting reader uses a different URL. - GITTENSORY_REPORTING_SOURCE_DB: "${GITTENSORY_REPORTING_SOURCE_DB:-/appdb/gittensory.sqlite}" + GITTENSORY_REPORTING_SOURCE_DB: "${GITTENSORY_REPORTING_SOURCE_DB:-/appdb/loopover.sqlite}" GITTENSORY_REPORTING_SOURCE_DATABASE_URL: "${GITTENSORY_REPORTING_SOURCE_DATABASE_URL:-}" DATABASE_URL: "${DATABASE_URL:-}" GITTENSORY_REPORTING_DIR: /reporting - GITTENSORY_REPORTING_DB: "${GITTENSORY_REPORTING_DB:-/reporting/gittensory-reporting.sqlite}" + GITTENSORY_REPORTING_DB: "${GITTENSORY_REPORTING_DB:-/reporting/loopover-reporting.sqlite}" GRAFANA_REPORTING_EXPORT_INTERVAL_SECONDS: "${GRAFANA_REPORTING_EXPORT_INTERVAL_SECONDS:-30}" command: - /bin/sh @@ -828,7 +828,7 @@ services: test: [ "CMD-SHELL", - "db=\"$${GITTENSORY_REPORTING_DB:-/reporting/gittensory-reporting.sqlite}\"; test -s \"$$db\" && sqlite3 \"$$db\" 'PRAGMA quick_check;' | grep -q '^ok$'", + "db=\"$${GITTENSORY_REPORTING_DB:-/reporting/loopover-reporting.sqlite}\"; test -s \"$$db\" && sqlite3 \"$$db\" 'PRAGMA quick_check;' | grep -q '^ok$'", ] interval: 30s timeout: 5s @@ -888,7 +888,7 @@ services: # ── Log pipeline (--profile observability) ───────────────────────────────── # Loki stores logs; Promtail discovers every container in this compose project via the read-only # docker-proxy (NOT a raw socket) and ships their logs to Loki. Browse in Grafana → Explore → - # Loki, e.g. {service="gittensory"} | json | level="error". + # Loki, e.g. {service="loopover"} | json | level="error". loki: image: grafana/loki:3.7.3 restart: unless-stopped @@ -947,7 +947,7 @@ services: depends_on: [loki, docker-proxy] environment: # Promtail expands this into its Docker label filter so discovery stays inside this Compose project. - COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-gittensory} + COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-loopover} volumes: - ./promtail/promtail-config.yml:/etc/promtail/promtail-config.yml:ro # Persist read positions so restarts don't re-ship the whole log history. @@ -1007,7 +1007,7 @@ services: # ── Tailscale (--profile tailscale) ─────────────────────────────────────── # Joins your tailnet so the stack is accessible via Tailscale IP/hostname — no # public ports exposed. Generate an auth key at tailscale.com/settings/keys and - # set TS_AUTHKEY= in .env. The gittensory service is reachable at the tailnet IP on port 8787. + # set TS_AUTHKEY= in .env. The loopover service is reachable at the tailnet IP on port 8787. tailscale: # Kept on the `stable` release channel rather than a version tag: tailscale's ghcr tags use a # non-obvious scheme and the client is happiest tracking the current stable. For a fully immutable @@ -1017,7 +1017,7 @@ services: restart: unless-stopped <<: *default-logging profiles: ["tailscale"] - hostname: gittensory + hostname: loopover cap_add: - NET_ADMIN - SYS_MODULE @@ -1037,7 +1037,7 @@ services: # Runs `runs-on: self-hosted` jobs on this machine. Set RUNNER_TOKEN= (or ACCESS_TOKEN=) # and RUNNER_REPO_URL= (e.g. https://github.com/your-org/your-repo) in .env. # Get a token at: https://github.com///settings/actions/runners/new - # Running this alongside the main `gittensory` app on the SAME host? A burst of CI jobs can starve the + # Running this alongside the main `loopover` app on the SAME host? A burst of CI jobs can starve the # app of CPU with no limits set (the default below) -- confirmed in production: 3 uncapped runner # containers on an 8-vCPU box left the app starved under load. See docker-compose.override.yml.example # for a proven CPU-priority pattern (relative cpu_shares + a per-container cpus ceiling) before scaling @@ -1072,7 +1072,7 @@ services: REPO_URL: ${RUNNER_REPO_URL:-} RUNNER_TOKEN: ${RUNNER_TOKEN:-} ACCESS_TOKEN: ${RUNNER_ACCESS_TOKEN:-} - RUNNER_NAME: ${RUNNER_NAME:-gittensory-runner} + RUNNER_NAME: ${RUNNER_NAME:-loopover-runner} LABELS: ${RUNNER_LABELS:-self-hosted,linux,x64} RUNNER_WORKDIR: /tmp/runner # Docker builds are disabled by default. To enable DinD (Docker-in-Docker) for workflows that @@ -1092,7 +1092,7 @@ services: # ── Backups (--profile backup) ──────────────────────────────────────────── # Active database backup (Postgres pg_dump or WAL-safe SQLite online backup) + a Qdrant snapshot, on a loop - # (default daily), kept in the gittensory-backups volume with retention. Run on demand: + # (default daily), kept in the loopover-backups volume with retention. Run on demand: # docker compose --profile backup run --rm backup sh /scripts/backup.sh # Verify the newest backup is restorable (pg_restore --list / SQLite integrity_check; opt-in scratch restore): # docker compose --profile backup run --rm backup sh /scripts/verify-backup.sh @@ -1102,7 +1102,7 @@ services: <<: *default-logging profiles: ["backup"] environment: - DATABASE_PATH: /data/gittensory.sqlite + DATABASE_PATH: /data/loopover.sqlite DATABASE_URL: "${DATABASE_URL:-}" GITTENSORY_BACKUP_SOURCE_DATABASE_URL: "${GITTENSORY_BACKUP_SOURCE_DATABASE_URL:-}" QDRANT_URL: ${QDRANT_URL:-http://qdrant:6333} @@ -1113,8 +1113,8 @@ services: GITTENSORY_VERIFY_SCRATCH_DATABASE_URL: "${GITTENSORY_VERIFY_SCRATCH_DATABASE_URL:-}" volumes: # RW (not :ro) so SQLite's online-backup can use the WAL index; the script only ever reads the DB. - - gittensory-data:/data - - gittensory-backups:/backups + - loopover-data:/data + - loopover-backups:/backups # A DIRECTORY bind mount, not individual-file mounts (#reporting-exporter-stale-bind-mount): a # single-file bind mount pins the container to that file's INODE at container-creation time, so a # later `git pull` (which replaces a file via write-new-then-rename, not an in-place edit) leaves the @@ -1149,7 +1149,7 @@ services: <<: *default-logging profiles: ["backup"] volumes: - - gittensory-backups:/backups:ro + - loopover-backups:/backups:ro # Directory mount, not a single-file one -- same #reporting-exporter-stale-bind-mount rationale as # the backup service above. - ./scripts:/scripts:ro @@ -1167,20 +1167,20 @@ services: # Native Docker Compose secrets: file-mounted (at /run/secrets/), never # exposed via `docker inspect`/`docker compose config` the way a plain `environment:`/`env_file` value is. # Read by the app's existing generic _FILE loader (src/selfhost/load-file-secrets.ts) via the -# `environment:` defaults set below on the gittensory service. Every file here is created empty (a no-op -- +# `environment:` defaults set below on the loopover service. Every file here is created empty (a no-op -- # an inline .env value always wins, see secrets/README.md) by `scripts/selfhost-init-secrets.sh`, which # every deploy runs before `docker compose build/up` so a missing file source can never break a deploy for -# an operator who has not opted into this convention. Scoped to the always-on gittensory service's own +# an operator who has not opted into this convention. Scoped to the always-on loopover service's own # highest-value secrets for now -- the same convention extends to any other service the same way. secrets: github_app_private_key: file: ./secrets/github_app_private_key.pem github_webhook_secret: file: ./secrets/github_webhook_secret.txt - gittensory_api_token: - file: ./secrets/gittensory_api_token.txt - gittensory_mcp_token: - file: ./secrets/gittensory_mcp_token.txt + loopover_api_token: + file: ./secrets/loopover_api_token.txt + loopover_mcp_token: + file: ./secrets/loopover_mcp_token.txt internal_job_token: file: ./secrets/internal_job_token.txt selfhost_setup_token: @@ -1197,8 +1197,8 @@ secrets: file: ./secrets/claude_code_oauth_token.txt volumes: - gittensory-data: - gittensory-pg: + loopover-data: + loopover-pg: qdrant-data: ollama-models: caddy-data: @@ -1211,7 +1211,7 @@ volumes: promtail-data: tailscale-state: runner-work: - gittensory-backups: + loopover-backups: tempo-data: networks: diff --git a/grafana/dashboards/gittensory.json b/grafana/dashboards/gittensory.json index 1ccd1ab91c..d0dc0964ff 100644 --- a/grafana/dashboards/gittensory.json +++ b/grafana/dashboards/gittensory.json @@ -1452,7 +1452,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "sum(pg_stat_activity_count{datname=\"gittensory\"}) or vector(0)", + "expr": "sum(pg_stat_activity_count{datname=\"loopover\"}) or vector(0)", "legendFormat": "connections", "refId": "A" } @@ -1500,7 +1500,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "pg_database_size_bytes{datname=\"gittensory\"} or vector(0)", + "expr": "pg_database_size_bytes{datname=\"loopover\"} or vector(0)", "legendFormat": "size", "refId": "A" } @@ -1599,7 +1599,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "sum by (state) (pg_stat_activity_count{datname=\"gittensory\"}) or vector(0)", + "expr": "sum by (state) (pg_stat_activity_count{datname=\"loopover\"}) or vector(0)", "legendFormat": "{{state}}", "refId": "A" } @@ -1646,7 +1646,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "sum(pg_stat_activity_count{datname=\"gittensory\", wait_event_type=\"Lock\"}) or vector(0)", + "expr": "sum(pg_stat_activity_count{datname=\"loopover\", wait_event_type=\"Lock\"}) or vector(0)", "legendFormat": "lock waits", "refId": "A" }, @@ -1655,7 +1655,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "max(pg_stat_activity_max_tx_duration{datname=\"gittensory\"}) or vector(0)", + "expr": "max(pg_stat_activity_max_tx_duration{datname=\"loopover\"}) or vector(0)", "legendFormat": "max transaction seconds", "refId": "B" }, @@ -1664,7 +1664,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "rate(pg_stat_database_deadlocks{datname=\"gittensory\"}[5m]) or vector(0)", + "expr": "rate(pg_stat_database_deadlocks{datname=\"loopover\"}[5m]) or vector(0)", "legendFormat": "deadlocks/s", "refId": "C" } @@ -1711,7 +1711,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "pg_database_size_bytes{datname=\"gittensory\"} or vector(0)", + "expr": "pg_database_size_bytes{datname=\"loopover\"} or vector(0)", "legendFormat": "database bytes", "refId": "A" }, @@ -1720,7 +1720,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "topk(10, pg_stat_user_tables_n_live_tup{datname=\"gittensory\"}) or vector(0)", + "expr": "topk(10, pg_stat_user_tables_n_live_tup{datname=\"loopover\"}) or vector(0)", "legendFormat": "{{relname}} live rows", "refId": "B" } @@ -1767,7 +1767,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "topk(10, pg_stat_user_tables_n_dead_tup{datname=\"gittensory\"}) or vector(0)", + "expr": "topk(10, pg_stat_user_tables_n_dead_tup{datname=\"loopover\"}) or vector(0)", "legendFormat": "{{relname}} dead rows", "refId": "A" }, @@ -1776,7 +1776,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "sum by (relname) (increase(pg_stat_user_tables_autovacuum_count{datname=\"gittensory\"}[1h])) or vector(0)", + "expr": "sum by (relname) (increase(pg_stat_user_tables_autovacuum_count{datname=\"loopover\"}[1h])) or vector(0)", "legendFormat": "{{relname}} autovacuum/hour", "refId": "B" } diff --git a/grafana/dashboards/resource-hub.json b/grafana/dashboards/resource-hub.json index 8448daaa73..5822d043c7 100644 --- a/grafana/dashboards/resource-hub.json +++ b/grafana/dashboards/resource-hub.json @@ -55,7 +55,7 @@ "gridPos": { "h": 13, "w": 12, "x": 0, "y": 0 }, "options": { "mode": "markdown", - "content": "## 🧠 AI providers\n- **Claude Code / Codex** — subscription CLIs, the review brain. Health: app boot log `selfhost_ai_provider`.\n- **Ollama** — embeddings (`bge-m3`) for RAG · [API root](http://localhost:11434) · `docker exec gittensory-ollama-1 ollama list`\n\n## 🔎 Vector store (RAG)\n- **Qdrant** — [Dashboard](http://localhost:6333/dashboard) · [Collections API](http://localhost:6333/collections)\n- Embeddings indexed per repo; see the *RAG indexing* doc.\n\n## ⚙️ Engine / API\n- **gittensory app** — [/ ](http://localhost:8787/) · [/ready](http://localhost:8787/ready) · [/metrics](http://localhost:8787/metrics)\n- Internal jobs: `POST /v1/internal/jobs/rag-index` (bearer `INTERNAL_JOB_TOKEN`)\n\n## 💾 Data\n- **SQLite** (default) at `/data/gittensory.sqlite` — private to the app/exporter. Grafana reads `/reporting/gittensory-reporting.sqlite`, a redacted reporting snapshot.\n- **Postgres/pgvector** (optional `--profile postgres`).\n\n> Links assume the default published ports on the Docker host. If you run Grafana on a remote host, replace `localhost` with that host, and publish the service `ports:` you want to reach." + "content": "## 🧠 AI providers\n- **Claude Code / Codex** — subscription CLIs, the review brain. Health: app boot log `selfhost_ai_provider`.\n- **Ollama** — embeddings (`bge-m3`) for RAG · [API root](http://localhost:11434) · `docker exec loopover-ollama-1 ollama list`\n\n## 🔎 Vector store (RAG)\n- **Qdrant** — [Dashboard](http://localhost:6333/dashboard) · [Collections API](http://localhost:6333/collections)\n- Embeddings indexed per repo; see the *RAG indexing* doc.\n\n## ⚙️ Engine / API\n- **gittensory app** — [/ ](http://localhost:8787/) · [/ready](http://localhost:8787/ready) · [/metrics](http://localhost:8787/metrics)\n- Internal jobs: `POST /v1/internal/jobs/rag-index` (bearer `INTERNAL_JOB_TOKEN`)\n\n## 💾 Data\n- **SQLite** (default) at `/data/loopover.sqlite` — private to the app/exporter. Grafana reads `/reporting/loopover-reporting.sqlite`, a redacted reporting snapshot.\n- **Postgres/pgvector** (optional `--profile postgres`).\n\n> Links assume the default published ports on the Docker host. If you run Grafana on a remote host, replace `localhost` with that host, and publish the service `ports:` you want to reach." } }, diff --git a/grafana/provisioning/datasources/ams-ledgers.yml b/grafana/provisioning/datasources/ams-ledgers.yml index bd98513ca5..313c368941 100644 --- a/grafana/provisioning/datasources/ams-ledgers.yml +++ b/grafana/provisioning/datasources/ams-ledgers.yml @@ -3,7 +3,7 @@ # Grafana must not mount the miner's live GITTENSORY_MINER_CONFIG_DIR ledgers. The attempt log and prediction # ledger contain free-form reasons, payload JSON, repo/target identifiers, readiness scores, and blocker/warning # codes that are private AMS intelligence. Instead, expose only redacted reporting-export snapshots, mirroring the -# maintainer GittensoryDB pattern in sqlite.yml. +# maintainer LoopoverDB pattern in sqlite.yml. # # PATH — the absolute path varies per install, so `path:` below is a documented placeholder. Publish sanitized AMS # reporting exports into Grafana's existing reporting mount (for example /reporting/ams-attempt-log.sqlite and diff --git a/grafana/provisioning/datasources/sqlite.yml b/grafana/provisioning/datasources/sqlite.yml index ce2e7c8fc2..701127e3ad 100644 --- a/grafana/provisioning/datasources/sqlite.yml +++ b/grafana/provisioning/datasources/sqlite.yml @@ -2,10 +2,10 @@ # database produced by reporting-exporter; it never mounts the live application database. apiVersion: 1 datasources: - - name: GittensoryDB + - name: LoopoverDB type: frser-sqlite-datasource uid: gittensory-db access: proxy editable: false jsonData: - path: /reporting/gittensory-reporting.sqlite + path: /reporting/loopover-reporting.sqlite diff --git a/litestream.yml.example b/litestream.yml.example index dc4ae68b0c..fc52cb6609 100644 --- a/litestream.yml.example +++ b/litestream.yml.example @@ -1,13 +1,13 @@ # Litestream continuous-backup config for the self-host SQLite DB (SAMPLE — placeholders only). -# Streams /data/gittensory.sqlite to your object store so you can restore to any point in time. +# Streams /data/loopover.sqlite to your object store so you can restore to any point in time. # Copy to litestream.yml, edit the bucket/endpoint, and mount it at /etc/litestream.yml (see docker-compose.yml). # Docs: https://litestream.io dbs: - - path: /data/gittensory.sqlite + - path: /data/loopover.sqlite replicas: - type: s3 bucket: your-backup-bucket - path: gittensory + path: loopover # For S3-compatible stores (Backblaze B2, MinIO, R2) set the endpoint; omit it for AWS S3. endpoint: ${LITESTREAM_ENDPOINT} region: ${LITESTREAM_REGION} diff --git a/packages/gittensory-miner/DEPLOYMENT.md b/packages/gittensory-miner/DEPLOYMENT.md index 2fc2aa9881..d2850a3d25 100644 --- a/packages/gittensory-miner/DEPLOYMENT.md +++ b/packages/gittensory-miner/DEPLOYMENT.md @@ -100,7 +100,7 @@ sudo systemctl daemon-reload sudo systemctl enable --now gittensory-miner.service ``` -Because `loop` is a **long-running daemon that schedules its own cycles**, it is a persistent `Type=simple` service (with `Restart=on-failure`) — **not** a oneshot unit driven by a `.timer`, unlike the periodic `gittensory-docker-prune.*.example` hygiene job in [`systemd/`](../../systemd/). Keep `GITHUB_TOKEN` (and any coding-agent credentials) in a root-owned `0600` `EnvironmentFile`, never in the unit file. Follow the loop with `journalctl -u gittensory-miner -f`; `systemctl stop` sends SIGTERM, which the loop handles cleanly at its next kill-switch check. +Because `loop` is a **long-running daemon that schedules its own cycles**, it is a persistent `Type=simple` service (with `Restart=on-failure`) — **not** a oneshot unit driven by a `.timer`, unlike the periodic `loopover-docker-prune.*.example` hygiene job in [`systemd/`](../../systemd/). Keep `GITHUB_TOKEN` (and any coding-agent credentials) in a root-owned `0600` `EnvironmentFile`, never in the unit file. Follow the loop with `journalctl -u gittensory-miner -f`; `systemctl stop` sends SIGTERM, which the loop handles cleanly at its next kill-switch check. ## Invariants diff --git a/packages/gittensory-miner/docs/observability.md b/packages/gittensory-miner/docs/observability.md index 11f3732693..ad23a1f64d 100644 --- a/packages/gittensory-miner/docs/observability.md +++ b/packages/gittensory-miner/docs/observability.md @@ -26,7 +26,7 @@ The repo ships datasource provisioning at `AMS Prediction Ledger` (uid `ams-prediction-ledger`). Their default paths live under Grafana's reporting mount, not under the miner config directory. -1. **Install the SQLite plugin** in Grafana — the same one the maintainer `GittensoryDB` datasource uses: +1. **Install the SQLite plugin** in Grafana — the same one the maintainer `LoopoverDB` datasource uses: ```sh GF_INSTALL_PLUGINS=frser-sqlite-datasource diff --git a/packages/gittensory-miner/docs/operations-runbook.md b/packages/gittensory-miner/docs/operations-runbook.md index 13406d1ec2..e4504f384e 100644 --- a/packages/gittensory-miner/docs/operations-runbook.md +++ b/packages/gittensory-miner/docs/operations-runbook.md @@ -2,7 +2,7 @@ Operator-facing runbook for **local SQLite state**: what the concurrency guarantees actually mean, how to recover from corruption, what to do when two miner processes collide on the same files, and how schema upgrades migrate your on-disk ledgers after a package update. -> **Scope:** AMS local stores only. For laptop/fleet deployment layout see [`../DEPLOYMENT.md`](../DEPLOYMENT.md). For Grafana setup see [#5190](https://github.com/JSONbored/gittensory/issues/5190). For the optional hosted discovery plane see [`discovery-plane-operator-guide.md`](discovery-plane-operator-guide.md). This runbook does **not** cover the self-hosted **review stack** (Orb/API/GittensoryDB). +> **Scope:** AMS local stores only. For laptop/fleet deployment layout see [`../DEPLOYMENT.md`](../DEPLOYMENT.md). For Grafana setup see [#5190](https://github.com/JSONbored/gittensory/issues/5190). For the optional hosted discovery plane see [`discovery-plane-operator-guide.md`](discovery-plane-operator-guide.md). This runbook does **not** cover the self-hosted **review stack** (Orb/API/LoopoverDB). ## Local state at a glance diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml index 99e9565e08..66b8b18e10 100644 --- a/prometheus/prometheus.yml +++ b/prometheus/prometheus.yml @@ -1,4 +1,4 @@ -# Prometheus scrape config for gittensory self-host (#980 observability). +# Prometheus scrape config for loopover self-host (#980 observability). # Activated via: docker compose --profile observability up global: scrape_interval: 15s @@ -18,7 +18,7 @@ alerting: scrape_configs: - job_name: gittensory static_configs: - - targets: ["gittensory:8787"] + - targets: ["loopover:8787"] metrics_path: /metrics scrape_interval: 15s scrape_timeout: 10s diff --git a/prometheus/rules/alerts.yml b/prometheus/rules/alerts.yml index 8543bf46a2..defa84a204 100644 --- a/prometheus/rules/alerts.yml +++ b/prometheus/rules/alerts.yml @@ -226,7 +226,7 @@ groups: # consumes more than 80% of max_connections for 10m; use PgBouncer or lower app concurrency. expr: | ( - sum(pg_stat_activity_count{datname="gittensory"}) + sum(pg_stat_activity_count{datname="loopover"}) / max(pg_settings_max_connections) ) > 0.80 @@ -241,7 +241,7 @@ groups: - alert: GittensoryPostgresLockWaits # Wait-event labels are emitted by the postgres_exporter stat_activity collector. Any sustained # lock wait means a write path is blocked and queue processing can appear stuck. - expr: sum(pg_stat_activity_count{datname="gittensory", wait_event_type="Lock"}) > 0 + expr: sum(pg_stat_activity_count{datname="loopover", wait_event_type="Lock"}) > 0 for: 5m labels: severity: warning @@ -253,7 +253,7 @@ groups: - alert: GittensoryPostgresSlowTransaction # Long active transactions are the self-host proxy for slow/problem queries without requiring # pg_stat_statements or superuser-only setup. - expr: max(pg_stat_activity_max_tx_duration{datname="gittensory"}) > 120 + expr: max(pg_stat_activity_max_tx_duration{datname="loopover"}) > 120 for: 5m labels: severity: warning @@ -264,7 +264,7 @@ groups: - alert: GittensoryPostgresDeadlocks # Deadlocks should be effectively zero; even a low rate points at competing write paths. - expr: rate(pg_stat_database_deadlocks{datname="gittensory"}[5m]) > 0 + expr: rate(pg_stat_database_deadlocks{datname="loopover"}[5m]) > 0 for: 2m labels: severity: warning @@ -276,7 +276,7 @@ groups: - alert: GittensoryPostgresDatabaseGrowingFast # Growth over a 6h window smooths normal small-table churn while catching runaway queue/audit growth. # 262144 bytes/s is roughly 5.4 GiB over 6h. - expr: deriv(pg_database_size_bytes{datname="gittensory"}[6h]) > 262144 + expr: deriv(pg_database_size_bytes{datname="loopover"}[6h]) > 262144 for: 30m labels: severity: warning @@ -289,15 +289,15 @@ groups: # Autovacuum pressure: high dead tuple ratio and enough absolute dead rows to avoid tiny-table noise. expr: | ( - sum(pg_stat_user_tables_n_dead_tup{datname="gittensory"}) + sum(pg_stat_user_tables_n_dead_tup{datname="loopover"}) / clamp_min( - sum(pg_stat_user_tables_n_live_tup{datname="gittensory"} + pg_stat_user_tables_n_dead_tup{datname="gittensory"}), + sum(pg_stat_user_tables_n_live_tup{datname="loopover"} + pg_stat_user_tables_n_dead_tup{datname="loopover"}), 1 ) ) > 0.20 and - sum(pg_stat_user_tables_n_dead_tup{datname="gittensory"}) > 10000 + sum(pg_stat_user_tables_n_dead_tup{datname="loopover"}) > 10000 for: 30m labels: severity: warning @@ -314,9 +314,9 @@ groups: labels: severity: warning annotations: - summary: "No retained gittensory database backup found" + summary: "No retained loopover database backup found" description: "The backup profile is exposing metrics, but {{ $labels.target }} has no retained backup file after 2h." - runbook: "Run `docker compose --profile backup run --rm backup sh /backup.sh` and inspect the gittensory-backups volume. For Postgres, confirm DATABASE_URL is available to the backup service." + runbook: "Run `docker compose --profile backup run --rm backup sh /backup.sh` and inspect the loopover-backups volume. For Postgres, confirm DATABASE_URL is available to the backup service." - alert: GittensoryBackupStale # Default backup loop is daily. 26h allows one missed scrape/restart window before warning. diff --git a/scripts/backup-metrics.sh b/scripts/backup-metrics.sh index ad5fb6d621..8d73fc2f18 100644 --- a/scripts/backup-metrics.sh +++ b/scripts/backup-metrics.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Expose retained backup freshness from the gittensory-backups volume in Prometheus text format. +# Expose retained backup freshness from the loopover-backups volume in Prometheus text format. # Intended for the backup-exporter compose sidecar; it never touches the live database. set -eu diff --git a/scripts/backup.sh b/scripts/backup.sh index 628b976855..a7cfad4ecd 100644 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -2,7 +2,7 @@ # Self-host backup: active DB backup (Postgres dump or online SQLite backup) + a Qdrant snapshot, with retention. # Run by the `backup` compose service (--profile backup) on a loop, or on demand: # docker compose --profile backup run --rm backup sh /scripts/backup.sh -# Backups land in the `gittensory-backups` volume at /backups/{postgres,sqlite,qdrant}. +# Backups land in the `loopover-backups` volume at /backups/{postgres,sqlite,qdrant}. set -eu # shellcheck source=selfhost-pg-url.sh @@ -32,7 +32,7 @@ normalize_backup_retain() { TS=$(date -u +%Y%m%dT%H%M%SZ) RETAIN=$(normalize_backup_retain "${BACKUP_RETAIN:-7}") -DB=${DATABASE_PATH:-/data/gittensory.sqlite} +DB=${DATABASE_PATH:-/data/loopover.sqlite} PG_DB="${GITTENSORY_BACKUP_SOURCE_DATABASE_URL:-${DATABASE_URL:-}}" OUT=${BACKUP_OUT_DIR:-/backups} PGPASSFILE_CREATED="" @@ -233,7 +233,7 @@ prepare_pg_env() { exit 1 ;; esac - PGPASSFILE_CREATED=$(mktemp "${TMPDIR:-/tmp}/gittensory-pgpass.XXXXXX") + PGPASSFILE_CREATED=$(mktemp "${TMPDIR:-/tmp}/loopover-pgpass.XXXXXX") chmod 600 "$PGPASSFILE_CREATED" printf '*:*:*:*:%s\n' "$(pgpass_escape "$PGPASSWORD_VALUE")" > "$PGPASSFILE_CREATED" export PGPASSFILE="$PGPASSFILE_CREATED" @@ -248,7 +248,7 @@ case "$PG_DB" in exit 1 fi prepare_pg_env - POSTGRES_OUT="$OUT/postgres/gittensory-$TS.dump" + POSTGRES_OUT="$OUT/postgres/loopover-$TS.dump" pg_dump -Fc -f "$POSTGRES_OUT" "$PG_SANITIZED_URL" POSTGRES_MANIFEST_FILE="postgres/$(basename "$POSTGRES_OUT")" POSTGRES_MANIFEST_BYTES=$(file_bytes "$POSTGRES_OUT") @@ -256,7 +256,7 @@ case "$PG_DB" in ;; *) if [ -f "$DB" ]; then - SQLITE_OUT="$OUT/sqlite/gittensory-$TS.sqlite" + SQLITE_OUT="$OUT/sqlite/loopover-$TS.sqlite" # `.backup` can exit 0 while writing a partial/corrupt file, so verify the result # (non-empty AND `PRAGMA integrity_check` == ok) before we gzip it or let retention # prune older, good backups. A failed backup must be loud, not silently "successful". diff --git a/scripts/deploy-selfhost-image.sh b/scripts/deploy-selfhost-image.sh index 419de5d48c..68b648dac9 100755 --- a/scripts/deploy-selfhost-image.sh +++ b/scripts/deploy-selfhost-image.sh @@ -15,7 +15,7 @@ set -euo pipefail ENV_FILE="${SELFHOST_ENV_FILE:-.env}" -SERVICE="${SELFHOST_SERVICE:-gittensory}" +SERVICE="${SELFHOST_SERVICE:-loopover}" HEALTH_TIMEOUT_SECONDS="${SELFHOST_HEALTH_TIMEOUT_SECONDS:-180}" DEFAULT_IMAGE="ghcr.io/jsonbored/gittensory-selfhost:latest" diff --git a/scripts/deploy-selfhost-prebuilt.sh b/scripts/deploy-selfhost-prebuilt.sh index 33e3a2c28c..039e6cb747 100755 --- a/scripts/deploy-selfhost-prebuilt.sh +++ b/scripts/deploy-selfhost-prebuilt.sh @@ -14,7 +14,7 @@ set -euo pipefail ENV_FILE="${SELFHOST_ENV_FILE:-.env}" NODE_IMAGE="${SELFHOST_NODE_IMAGE:-public.ecr.aws/docker/library/node:24-slim}" -SERVICE="${SELFHOST_SERVICE:-gittensory}" +SERVICE="${SELFHOST_SERVICE:-loopover}" SKIP_SENTRY_UPLOAD="${SELFHOST_SKIP_SENTRY_UPLOAD:-0}" SENTRY_CLI_PACKAGE="${SENTRY_CLI_PACKAGE:-@sentry/cli@3.6.0}" @@ -117,7 +117,7 @@ require_cmd docker docker compose version >/dev/null if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "error: run this script from the gittensory git checkout" >&2 + echo "error: run this script from the loopover git checkout" >&2 exit 1 fi diff --git a/scripts/export-grafana-reporting-db.sh b/scripts/export-grafana-reporting-db.sh index e48f0e5fbd..4aef9f9d7d 100644 --- a/scripts/export-grafana-reporting-db.sh +++ b/scripts/export-grafana-reporting-db.sh @@ -9,10 +9,10 @@ set -eu # purely so a test can simulate "the script logic changed" without editing this file. SCRIPT_VERSION="${GITTENSORY_REPORTING_SCRIPT_VERSION:-2}" -APP_DB="${GITTENSORY_REPORTING_SOURCE_DB:-/appdb/gittensory.sqlite}" +APP_DB="${GITTENSORY_REPORTING_SOURCE_DB:-/appdb/loopover.sqlite}" PG_DB="${GITTENSORY_REPORTING_SOURCE_DATABASE_URL:-${DATABASE_URL:-}}" OUT_DIR="${GITTENSORY_REPORTING_DIR:-/reporting}" -OUT_DB="${GITTENSORY_REPORTING_DB:-$OUT_DIR/gittensory-reporting.sqlite}" +OUT_DB="${GITTENSORY_REPORTING_DB:-$OUT_DIR/loopover-reporting.sqlite}" TMP_DB="${OUT_DB}.tmp" FINGERPRINT_FILE="${OUT_DB}.fingerprint" CSV_TMP_DIR="$(mktemp -d)" diff --git a/scripts/migrate-selfhost-sqlite-to-postgres.ts b/scripts/migrate-selfhost-sqlite-to-postgres.ts index 8e4653f8d2..656290fa3f 100644 --- a/scripts/migrate-selfhost-sqlite-to-postgres.ts +++ b/scripts/migrate-selfhost-sqlite-to-postgres.ts @@ -43,7 +43,7 @@ rolled-back dry run. Pass --execute to commit the copy. Prefer DATABASE_URL over the Postgres credential is not exposed through process command lines. Options: - --sqlite SQLite source file. Defaults to DATABASE_PATH or /data/gittensory.sqlite. + --sqlite SQLite source file. Defaults to DATABASE_PATH or /data/loopover.sqlite. --postgres-url Postgres target URL. Defaults to DATABASE_URL; avoid this on shared hosts. --migrations-dir Migration directory. Defaults to migrations. --execute Commit the copy. Omit for a rollback dry run. @@ -54,7 +54,7 @@ Options: function parseArgs(argv: string[]): Options { const opts: Options = { - sqlitePath: process.env.DATABASE_PATH ?? "/data/gittensory.sqlite", + sqlitePath: process.env.DATABASE_PATH ?? "/data/loopover.sqlite", postgresUrl: process.env.DATABASE_URL ?? "", migrationsDir: process.env.MIGRATIONS_DIR ?? "migrations", execute: false, diff --git a/scripts/selfhost-docker-prune.sh b/scripts/selfhost-docker-prune.sh index 7bf53dda08..1ffcd35b4e 100755 --- a/scripts/selfhost-docker-prune.sh +++ b/scripts/selfhost-docker-prune.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Automated Docker resource hygiene for a 24/7 self-hosted gittensory stack (#audit-rate-headroom). Runs on -# the HOST (via the systemd timer in systemd/gittensory-docker-prune.{service,timer}.example), not as a +# Automated Docker resource hygiene for a 24/7 self-hosted loopover stack (#audit-rate-headroom). Runs on +# the HOST (via the systemd timer in systemd/loopover-docker-prune.{service,timer}.example), not as a # compose service: reclaiming unused images and build cache needs real Docker daemon access, which this # repo deliberately does not grant to any container (see docker-compose.yml's docker-proxy and runner # service comments on why raw docker.sock exposure into a container is avoided). @@ -22,7 +22,7 @@ # itself been stopped for at least RETAIN_HOURS is ever removed. # # SAFE BY DESIGN: only prunes stopped containers, unused images, and build cache -- NEVER volumes -# (gittensory-data, gittensory-backups, postgres-data, qdrant-storage, runner-work, etc.), so it cannot +# (loopover-data, loopover-backups, postgres-data, qdrant-storage, runner-work, etc.), so it cannot # delete application data, backups, vector-store state, or a runner's registration/job data. # # Usage: diff --git a/scripts/selfhost-init-secrets.sh b/scripts/selfhost-init-secrets.sh index 4a6175895f..af9ea3534c 100755 --- a/scripts/selfhost-init-secrets.sh +++ b/scripts/selfhost-init-secrets.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Ensure every Docker Compose secret file docker-compose.yml's `gittensory` service references +# Ensure every Docker Compose secret file docker-compose.yml's `loopover` service references # actually exists on disk, so `docker compose build`/`up` never fails on a missing `secrets:` source # file -- Compose requires the file to exist even for an operator who has never touched this feature # and is relying entirely on inline .env values (see secrets/README.md: an inline value always wins @@ -34,8 +34,8 @@ SECRETS_DIR="secrets" SECRET_FILES=( "github_app_private_key.pem" "github_webhook_secret.txt" - "gittensory_api_token.txt" - "gittensory_mcp_token.txt" + "loopover_api_token.txt" + "loopover_mcp_token.txt" "internal_job_token.txt" "selfhost_setup_token.txt" "token_encryption_secret.txt" diff --git a/scripts/selfhost-post-update-check.sh b/scripts/selfhost-post-update-check.sh index 77ef7f94bd..b23d1cf493 100755 --- a/scripts/selfhost-post-update-check.sh +++ b/scripts/selfhost-post-update-check.sh @@ -2,14 +2,14 @@ # Post-update verification for a self-host instance (#1823). # # Run after deploy-selfhost-image.sh, deploy-selfhost-prebuilt.sh, or any manual -# `docker compose up -d --no-deps gittensory` that ships a new app image. +# `docker compose up -d --no-deps loopover` that ships a new app image. # # Checks /ready, compose health, .env release metadata, and the running container image. # Does not modify .env, volumes, gittensory-config/, or any profile service. set -euo pipefail ENV_FILE="${SELFHOST_ENV_FILE:-.env}" -SERVICE="${SELFHOST_SERVICE:-gittensory}" +SERVICE="${SELFHOST_SERVICE:-loopover}" PORT="${PORT:-8787}" READY_URL="${SELFHOST_READY_URL:-http://127.0.0.1:${PORT}/ready}" @@ -32,7 +32,7 @@ fi # Retry, don't single-probe: `docker compose up -d` returns as soon as the container STARTS, well before # the app inside has bound its port -- a single immediate curl reliably false-fails on a normal boot. Budget -# matches the gittensory service's own Docker healthcheck start_period (60s, docker-compose.yml) plus margin, +# matches the loopover service's own Docker healthcheck start_period (60s, docker-compose.yml) plus margin, # polling often enough that a normal ~15-20s boot returns almost immediately once actually ready. READY_RETRIES="${SELFHOST_READY_RETRIES:-45}" READY_RETRY_DELAY_SECONDS="${SELFHOST_READY_RETRY_DELAY_SECONDS:-2}" diff --git a/scripts/selfhost-update.sh b/scripts/selfhost-update.sh index 08de0d1cb3..f341a06e69 100755 --- a/scripts/selfhost-update.sh +++ b/scripts/selfhost-update.sh @@ -14,9 +14,9 @@ # - gittensory-config/ (private per-repo .gittensory.yml policy) # - .deploy-backups/ (operator deploy-backup snapshots) # - any *.local or docker-compose.local-*.yml compose override, or alertmanager config files -# - named data volumes (gittensory-data, gittensory-pg, qdrant-data, gittensory-backups, +# - named data volumes (loopover-data, loopover-pg, qdrant-data, loopover-backups, # grafana-data) -- untouched because this script only fetches source and rebuilds the -# gittensory app image; it never runs `docker volume` commands or touches compose profiles. +# loopover app image; it never runs `docker volume` commands or touches compose profiles. # # Optional knobs: # SELFHOST_UPDATE_REMOTE=upstream SELFHOST_UPDATE_BRANCH=main ./scripts/selfhost-update.sh @@ -39,7 +39,7 @@ require_cmd() { require_cmd git if ! git -C "$SCRIPT_DIR/.." rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "error: run this script from the gittensory git checkout" >&2 + echo "error: run this script from the loopover git checkout" >&2 exit 1 fi diff --git a/scripts/verify-backup.sh b/scripts/verify-backup.sh index 6781a1fd35..69207fc925 100644 --- a/scripts/verify-backup.sh +++ b/scripts/verify-backup.sh @@ -6,7 +6,7 @@ # restores the Postgres dump into a throwaway database and runs a sanity query — it refuses to touch the live # database. Run on demand (newest backup, or a specific file): # docker compose --profile backup run --rm backup sh /scripts/verify-backup.sh -# docker compose --profile backup run --rm backup sh /scripts/verify-backup.sh /backups/postgres/gittensory-.dump +# docker compose --profile backup run --rm backup sh /scripts/verify-backup.sh /backups/postgres/loopover-.dump set -eu OUT=${BACKUP_OUT_DIR:-/backups} @@ -149,7 +149,7 @@ pg_connect_arg() { # Host/port/dbname/user are wildcarded: each passfile is single-purpose, deleted at the end of this # run via the `cleanup` trap, so there's no value in re-deriving the exact host/port/dbname libpq will # resolve -- which the query string can override anyway -- just to match them precisely. - pg_passfile=$(mktemp "${TMPDIR:-/tmp}/gittensory-pgpass.XXXXXX") + pg_passfile=$(mktemp "${TMPDIR:-/tmp}/loopover-pgpass.XXXXXX") chmod 600 "$pg_passfile" printf '*:*:*:*:%s\n' "$(pgpass_escape "$pg_password_value")" > "$pg_passfile" PG_PASSFILES="$PG_PASSFILES $pg_passfile" diff --git a/secrets/README.md b/secrets/README.md index 16276adc9a..a85b8e3cc2 100644 --- a/secrets/README.md +++ b/secrets/README.md @@ -46,7 +46,7 @@ To use a secret file instead of an inline `.env` value: ```sh cp /path/to/your-downloaded-key.pem secrets/github_app_private_key.pem ``` -3. Restart the `gittensory` service (`docker compose up -d --no-deps gittensory`, or run +3. Restart the `loopover` service (`docker compose up -d --no-deps loopover`, or run `./scripts/selfhost-update.sh`). Leave each file at the `644` the init script (`scripts/selfhost-init-secrets.sh`) sets by default — @@ -58,8 +58,8 @@ see the tradeoff explained above for why `600` breaks the app's own ability to r |---|---|---| | `github_app_private_key.pem` | `GITHUB_APP_PRIVATE_KEY_FILE` | Your GitHub App's private key (PEM). | | `github_webhook_secret.txt` | `GITHUB_WEBHOOK_SECRET_FILE` | HMAC key GitHub webhook deliveries are verified against. | -| `gittensory_api_token.txt` | `GITTENSORY_API_TOKEN_FILE` | Server-to-server API bearer token. | -| `gittensory_mcp_token.txt` | `GITTENSORY_MCP_TOKEN_FILE` | Shared MCP bearer token. | +| `loopover_api_token.txt` | `GITTENSORY_API_TOKEN_FILE` | Server-to-server API bearer token. | +| `loopover_mcp_token.txt` | `GITTENSORY_MCP_TOKEN_FILE` | Shared MCP bearer token. | | `internal_job_token.txt` | `INTERNAL_JOB_TOKEN_FILE` | Gates internal-only routes (e.g. `/v1/internal/*`). | | `selfhost_setup_token.txt` | `SELFHOST_SETUP_TOKEN_FILE` | Unlocks the first-run `/setup` wizard. | | `token_encryption_secret.txt` | `TOKEN_ENCRYPTION_SECRET_FILE` | AES-256-GCM master secret for maintainer BYOK keys at rest. | @@ -70,7 +70,7 @@ see the tradeoff explained above for why `600` breaks the app's own ability to r This is not the full list of every secret-shaped env var the stack supports (AI provider API keys, Discord/Slack webhooks, Postgres/Grafana credentials for their optional profiles, etc.) — it covers -the vars used by the always-on `gittensory` service. The same `_FILE` convention works for any +the vars used by the always-on `loopover` service. The same `_FILE` convention works for any of those too; add a matching `secrets:` entry in `docker-compose.yml` (or a `docker-compose.override.yml`) if you want the same treatment for one of them. diff --git a/src/server.ts b/src/server.ts index a263373cf3..cffa8d9215 100644 --- a/src/server.ts +++ b/src/server.ts @@ -225,7 +225,7 @@ function buildSqliteBackend( consume: (m: JobMessage) => Promise, ): Backend { const sqlite = new DatabaseSync( - process.env.DATABASE_PATH ?? "/data/gittensory.sqlite", + process.env.DATABASE_PATH ?? "/data/loopover.sqlite", ); sqlite.exec( "PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;", diff --git a/systemd/gittensory-miner.service.example b/systemd/gittensory-miner.service.example index 2250ff1ef7..761d3998a8 100644 --- a/systemd/gittensory-miner.service.example +++ b/systemd/gittensory-miner.service.example @@ -5,7 +5,7 @@ # WHY A PERSISTENT SERVICE, NOT A .timer: `gittensory-miner loop` is a long-running daemon that schedules its # own cycles internally (kill-switch check -> run-loop boundary gate -> attempt -> loop-closure -> reentry), so # it is a Type=simple service supervised by systemd, NOT a oneshot unit fired by a .timer. (Contrast -# gittensory-docker-prune.service.example, which IS a genuine periodic batch and therefore does pair with a +# loopover-docker-prune.service.example, which IS a genuine periodic batch and therefore does pair with a # .timer.) See DEPLOYMENT.md's "Bare-host (systemd, no Docker)" section. # # Install (adjust the placeholders below to your host): diff --git a/systemd/gittensory-docker-prune.service.example b/systemd/loopover-docker-prune.service.example similarity index 54% rename from systemd/gittensory-docker-prune.service.example rename to systemd/loopover-docker-prune.service.example index a88ee988bd..fa133f3047 100644 --- a/systemd/gittensory-docker-prune.service.example +++ b/systemd/loopover-docker-prune.service.example @@ -1,27 +1,27 @@ -# Runs scripts/selfhost-docker-prune.sh on a schedule (paired with gittensory-docker-prune.timer.example) to +# Runs scripts/selfhost-docker-prune.sh on a schedule (paired with loopover-docker-prune.timer.example) to # reclaim unused Docker images and build cache before disk fills up on a long-running self-host deployment. # -# Install (adjust the path below to wherever you cloned/deployed gittensory): -# sudo cp systemd/gittensory-docker-prune.service.example /etc/systemd/system/gittensory-docker-prune.service -# sudo cp systemd/gittensory-docker-prune.timer.example /etc/systemd/system/gittensory-docker-prune.timer -# sudo $EDITOR /etc/systemd/system/gittensory-docker-prune.service # fix WorkingDirectory / ExecStart path +# Install (adjust the path below to wherever you cloned/deployed loopover): +# sudo cp systemd/loopover-docker-prune.service.example /etc/systemd/system/loopover-docker-prune.service +# sudo cp systemd/loopover-docker-prune.timer.example /etc/systemd/system/loopover-docker-prune.timer +# sudo $EDITOR /etc/systemd/system/loopover-docker-prune.service # fix WorkingDirectory / ExecStart path # sudo systemctl daemon-reload -# sudo systemctl enable --now gittensory-docker-prune.timer +# sudo systemctl enable --now loopover-docker-prune.timer # # This is a HOST-level unit, not a Docker Compose service: reclaiming images/build-cache needs real Docker # daemon access, which no container in docker-compose.yml is granted (see that file's docker-proxy and # runner service comments for why raw /var/run/docker.sock exposure into a container is avoided). [Unit] -Description=Gittensory self-host Docker resource hygiene (image + build-cache prune) +Description=LoopOver self-host Docker resource hygiene (image + build-cache prune) After=docker.service Requires=docker.service [Service] Type=oneshot -# REQUIRED: point this at wherever you cloned gittensory. -WorkingDirectory=/opt/gittensory -ExecStart=/bin/sh /opt/gittensory/scripts/selfhost-docker-prune.sh +# REQUIRED: point this at wherever you cloned loopover. +WorkingDirectory=/opt/loopover +ExecStart=/bin/sh /opt/loopover/scripts/selfhost-docker-prune.sh # Optional: override the default 7-day (168h) safety window before an unused image/build-cache entry is # eligible for removal. # Environment=GITTENSORY_DOCKER_PRUNE_RETAIN_HOURS=168 diff --git a/systemd/gittensory-docker-prune.timer.example b/systemd/loopover-docker-prune.timer.example similarity index 56% rename from systemd/gittensory-docker-prune.timer.example rename to systemd/loopover-docker-prune.timer.example index 7fb52f8459..f9f959b0cc 100644 --- a/systemd/gittensory-docker-prune.timer.example +++ b/systemd/loopover-docker-prune.timer.example @@ -1,7 +1,7 @@ -# Pairs with gittensory-docker-prune.service.example. See that file for install instructions. +# Pairs with loopover-docker-prune.service.example. See that file for install instructions. [Unit] -Description=Run gittensory-docker-prune.service daily +Description=Run loopover-docker-prune.service daily [Timer] OnCalendar=daily diff --git a/test/unit/ams-ledgers-datasource.test.ts b/test/unit/ams-ledgers-datasource.test.ts index 5c83ce1e64..f7380e84e5 100644 --- a/test/unit/ams-ledgers-datasource.test.ts +++ b/test/unit/ams-ledgers-datasource.test.ts @@ -53,7 +53,7 @@ describe("AMS ledger Grafana datasources (#5184)", () => { for (const ds of amsDatasources) { expect(existingUids.has(ds.uid)).toBe(false); } - // the maintainer GittensoryDB datasource still exists — this change added a file, it did not edit one + // the maintainer LoopoverDB datasource still exists — this change added a file, it did not edit one expect(existingUids.has("gittensory-db")).toBe(true); }); }); diff --git a/test/unit/backup-script.test.ts b/test/unit/backup-script.test.ts index 31a96271e6..f64fbfc93b 100644 --- a/test/unit/backup-script.test.ts +++ b/test/unit/backup-script.test.ts @@ -169,7 +169,7 @@ describe("scripts/backup.sh sqlite online-backup verification (#2084)", () => { "ts", ]); expect(manifest.postgres).toBeNull(); - expect(manifest.sqlite?.file).toMatch(/^sqlite\/gittensory-\d{8}T\d{6}Z\.sqlite\.gz$/); + expect(manifest.sqlite?.file).toMatch(/^sqlite\/loopover-\d{8}T\d{6}Z\.sqlite\.gz$/); expect(manifest.sqlite?.bytes).toBe( statSync(join(harness.outDir, manifest.sqlite!.file)).size, ); @@ -181,7 +181,7 @@ describe("scripts/backup.sh sqlite online-backup verification (#2084)", () => { expect(res.status).toBe(0); const manifest = readManifest(harness); - expect(manifest.sqlite?.file).toMatch(/^sqlite\/gittensory-\d{8}T\d{6}Z\.sqlite\.gz$/); + expect(manifest.sqlite?.file).toMatch(/^sqlite\/loopover-\d{8}T\d{6}Z\.sqlite\.gz$/); expect(manifest.postgres).toBeNull(); expect(manifest.qdrant.file).toBe("qdrant/qdrant-snapshot-123.snap"); expect(readFileSync(join(harness.outDir, manifest.qdrant.file!), "utf8")).toBe( @@ -196,7 +196,7 @@ describe("scripts/backup.sh sqlite online-backup verification (#2084)", () => { expect(res.stderr).toContain("BACKUP_RETAIN=0"); const manifest = readManifest(harness); expect(manifest.retain).toBe(1); - expect(manifest.sqlite?.file).toMatch(/^sqlite\/gittensory-\d{8}T\d{6}Z\.sqlite\.gz$/); + expect(manifest.sqlite?.file).toMatch(/^sqlite\/loopover-\d{8}T\d{6}Z\.sqlite\.gz$/); expect(existsSync(join(harness.outDir, manifest.sqlite!.file))).toBe(true); expect(readdirSync(join(harness.outDir, "sqlite"))).toHaveLength(1); }); @@ -224,7 +224,7 @@ describe("scripts/backup.sh sqlite online-backup verification (#2084)", () => { expect(res.status).toBe(0); expect(res.stdout).toContain("[backup] postgres ->"); const manifest = readManifest(harness); - expect(manifest.postgres?.file).toMatch(/^postgres\/gittensory-\d{8}T\d{6}Z\.dump$/); + expect(manifest.postgres?.file).toMatch(/^postgres\/loopover-\d{8}T\d{6}Z\.dump$/); expect(manifest.postgres?.bytes).toBe( statSync(join(harness.outDir, manifest.postgres!.file)).size, ); diff --git a/test/unit/docker-compose-override-example.test.ts b/test/unit/docker-compose-override-example.test.ts index 17409b57fa..4f5da4b538 100644 --- a/test/unit/docker-compose-override-example.test.ts +++ b/test/unit/docker-compose-override-example.test.ts @@ -10,7 +10,7 @@ function readYaml(path: string): Record { return value as Record; } -// A burst of CI jobs (--profile runners) can starve the main `gittensory` app of CPU on a shared host +// A burst of CI jobs (--profile runners) can starve the main `loopover` app of CPU on a shared host // with no limits set (the default). docker-compose.override.yml.example documents a proven cpu_shares + // cpus pattern for that case -- pure structural checks only (no `docker` CLI invocation: the self-hosted // runner container this actually runs on does not have Docker-in-Docker access, so a test that shells out @@ -22,7 +22,7 @@ describe("docker-compose.override.yml.example", () => { const exampleServices = Object.keys(example.services as Record); const baseServices = Object.keys(base.services as Record); - expect(exampleServices).toEqual(["gittensory", "runner"]); + expect(exampleServices).toEqual(["loopover", "runner"]); for (const name of exampleServices) { expect(baseServices).toContain(name); } @@ -32,7 +32,7 @@ describe("docker-compose.override.yml.example", () => { const example = readYaml("docker-compose.override.yml.example"); const services = example.services as Record>; - const appShares = Number(services.gittensory?.cpu_shares); + const appShares = Number(services.loopover?.cpu_shares); const runnerShares = Number(services.runner?.cpu_shares); expect(Number.isFinite(appShares) && Number.isFinite(runnerShares)).toBe(true); expect(appShares).toBeGreaterThan(runnerShares); diff --git a/test/unit/docs-selfhost-update-rollback.test.ts b/test/unit/docs-selfhost-update-rollback.test.ts index b3fc8b155d..a4b7aea365 100644 --- a/test/unit/docs-selfhost-update-rollback.test.ts +++ b/test/unit/docs-selfhost-update-rollback.test.ts @@ -23,7 +23,7 @@ describe("self-host update + rollback docs (#1823)", () => { expect(operations).toContain("Post-update checklist"); expect(operations).toContain("Operator-owned"); expect(operations).toContain("gittensory-config/"); - expect(operations).toContain("gittensory-data"); + expect(operations).toContain("loopover-data"); expect(operations).toContain("Migrations are forward-only"); }); diff --git a/test/unit/selfhost-backup-script.test.ts b/test/unit/selfhost-backup-script.test.ts index e470113f6e..0374d8c012 100644 --- a/test/unit/selfhost-backup-script.test.ts +++ b/test/unit/selfhost-backup-script.test.ts @@ -120,7 +120,7 @@ describe("self-host backup script", () => { const postgresBackups = readdirSync(join(root, "backups", "postgres")); expect(output).toContain("[backup] postgres ->"); expect(postgresBackups).toHaveLength(1); - expect(postgresBackups[0]).toMatch(/^gittensory-\d{8}T\d{6}Z\.dump$/); + expect(postgresBackups[0]).toMatch(/^loopover-\d{8}T\d{6}Z\.dump$/); expect(readdirSync(join(root, "backups", "sqlite"))).toEqual([]); }); @@ -151,7 +151,7 @@ describe("self-host backup script", () => { // basename only, not a hardcoded /tmp/ prefix: mktemp resolves under $TMPDIR, which macOS sets to a // per-user private directory rather than /tmp (the CI runner's Linux environment does default to /tmp, // but the assertion shouldn't assume that). - expect(passfilePath).toMatch(/\/gittensory-pgpass\.[^/]+$/); + expect(passfilePath).toMatch(/\/loopover-pgpass\.[^/]+$/); expect(passfileContent).toBe("*:*:*:*:SuperSecret123!"); expect(passfileModeOk).toBe("yes"); }); @@ -351,7 +351,7 @@ describe("self-host backup script", () => { const sqliteBackups = readdirSync(join(root, "backups", "sqlite")); expect(output).toContain("[backup] sqlite ->"); expect(sqliteBackups).toHaveLength(1); - expect(sqliteBackups[0]).toMatch(/^gittensory-\d{8}T\d{6}Z\.sqlite\.gz$/); + expect(sqliteBackups[0]).toMatch(/^loopover-\d{8}T\d{6}Z\.sqlite\.gz$/); expect(existsSync(join(root, "backups", "postgres"))).toBe(true); expect(readdirSync(join(root, "backups", "postgres"))).toEqual([]); }); diff --git a/test/unit/selfhost-compose-db-health.test.ts b/test/unit/selfhost-compose-db-health.test.ts index 86ef1381dd..42087e9882 100644 --- a/test/unit/selfhost-compose-db-health.test.ts +++ b/test/unit/selfhost-compose-db-health.test.ts @@ -17,7 +17,7 @@ describe("docker-compose.yml — postgres/pgbouncer startup ordering (#2500, #25 it("gates the core app on a healthy postgres and pgbouncer without requiring either profile", () => { const compose = readYaml("docker-compose.yml"); const services = (compose.services as Record>) ?? {}; - const app = services.gittensory ?? {}; + const app = services.loopover ?? {}; const dependsOn = app.depends_on as Record; // redis stays required (always-on service); postgres/pgbouncer/qdrant are all required:false since only @@ -28,7 +28,7 @@ describe("docker-compose.yml — postgres/pgbouncer startup ordering (#2500, #25 expect(dependsOn.qdrant).toEqual({ condition: "service_healthy", required: false }); }); - it("gives pgbouncer a healthcheck so gittensory's depends_on has a real readiness signal to gate on", () => { + it("gives pgbouncer a healthcheck so loopover's depends_on has a real readiness signal to gate on", () => { const compose = readYaml("docker-compose.yml"); const services = (compose.services as Record>) ?? {}; const pgbouncer = services.pgbouncer ?? {}; @@ -41,7 +41,7 @@ describe("docker-compose.yml — postgres/pgbouncer startup ordering (#2500, #25 expect(healthcheck.retries).toBeGreaterThan(0); }); - it("still starts pgbouncer only after a healthy postgres (unaffected by the new gittensory dependency)", () => { + it("still starts pgbouncer only after a healthy postgres (unaffected by the new loopover dependency)", () => { const compose = readYaml("docker-compose.yml"); const services = (compose.services as Record>) ?? {}; const pgbouncer = services.pgbouncer ?? {}; diff --git a/test/unit/selfhost-compose-resource-limits.test.ts b/test/unit/selfhost-compose-resource-limits.test.ts index 501a1e5f8d..033bf8db62 100644 --- a/test/unit/selfhost-compose-resource-limits.test.ts +++ b/test/unit/selfhost-compose-resource-limits.test.ts @@ -15,7 +15,7 @@ function readYaml(path: string): Record { // would be unreliable/environment-dependent here (same constraint as docker-compose-override-example.test.ts). describe("docker-compose.yml — per-service memory limits (#1828, #2495, #3893)", () => { const EXPECTED_LIMITS: Record = { - gittensory: "${GITTENSORY_MEM_LIMIT:-2g}", + loopover: "${GITTENSORY_MEM_LIMIT:-2g}", redis: "${REDIS_MEM_LIMIT:-512m}", postgres: "${POSTGRES_MEM_LIMIT:-2g}", qdrant: "${QDRANT_MEM_LIMIT:-2g}", diff --git a/test/unit/selfhost-grafana-dashboard.test.ts b/test/unit/selfhost-grafana-dashboard.test.ts index ab02377d6d..f643017f9a 100644 --- a/test/unit/selfhost-grafana-dashboard.test.ts +++ b/test/unit/selfhost-grafana-dashboard.test.ts @@ -174,14 +174,14 @@ describe("Gittensory Self-Host Grafana dashboard", () => { expect(titles).toEqual(expect.arrayContaining(["Postgres & Backups", "Postgres Connections by State", "Postgres Locks & Slow Transactions", "Postgres Size & Table Growth", "Dead Tuples / Autovacuum", "Backup Freshness"])); expect(targets.some((target) => target.expr === 'pg_up or vector(0)')).toBe(true); - expect(targets.some((target) => target.expr === 'sum(pg_stat_activity_count{datname="gittensory"}) or vector(0)')).toBe(true); - expect(targets.some((target) => target.expr === 'sum by (state) (pg_stat_activity_count{datname="gittensory"}) or vector(0)')).toBe(true); - expect(targets.some((target) => target.expr === 'sum(pg_stat_activity_count{datname="gittensory", wait_event_type="Lock"}) or vector(0)')).toBe(true); - expect(targets.some((target) => target.expr === 'max(pg_stat_activity_max_tx_duration{datname="gittensory"}) or vector(0)')).toBe(true); - expect(targets.some((target) => target.expr === 'pg_database_size_bytes{datname="gittensory"} or vector(0)')).toBe(true); - expect(targets.some((target) => target.expr === 'topk(10, pg_stat_user_tables_n_live_tup{datname="gittensory"}) or vector(0)')).toBe(true); - expect(targets.some((target) => target.expr === 'topk(10, pg_stat_user_tables_n_dead_tup{datname="gittensory"}) or vector(0)')).toBe(true); - expect(targets.some((target) => target.expr === 'sum by (relname) (increase(pg_stat_user_tables_autovacuum_count{datname="gittensory"}[1h])) or vector(0)')).toBe(true); + expect(targets.some((target) => target.expr === 'sum(pg_stat_activity_count{datname="loopover"}) or vector(0)')).toBe(true); + expect(targets.some((target) => target.expr === 'sum by (state) (pg_stat_activity_count{datname="loopover"}) or vector(0)')).toBe(true); + expect(targets.some((target) => target.expr === 'sum(pg_stat_activity_count{datname="loopover", wait_event_type="Lock"}) or vector(0)')).toBe(true); + expect(targets.some((target) => target.expr === 'max(pg_stat_activity_max_tx_duration{datname="loopover"}) or vector(0)')).toBe(true); + expect(targets.some((target) => target.expr === 'pg_database_size_bytes{datname="loopover"} or vector(0)')).toBe(true); + expect(targets.some((target) => target.expr === 'topk(10, pg_stat_user_tables_n_live_tup{datname="loopover"}) or vector(0)')).toBe(true); + expect(targets.some((target) => target.expr === 'topk(10, pg_stat_user_tables_n_dead_tup{datname="loopover"}) or vector(0)')).toBe(true); + expect(targets.some((target) => target.expr === 'sum by (relname) (increase(pg_stat_user_tables_autovacuum_count{datname="loopover"}[1h])) or vector(0)')).toBe(true); expect(targets.some((target) => target.expr === 'gittensory_backup_files{target=~"postgres|sqlite|qdrant"} or vector(0)')).toBe(true); }); @@ -189,17 +189,17 @@ describe("Gittensory Self-Host Grafana dashboard", () => { const alerts = readFileSync(selfhostAlertsPath, "utf8"); expect(alerts).toContain("alert: GittensoryPostgresConnectionPressure"); - expect(alerts).toContain('sum(pg_stat_activity_count{datname="gittensory"})'); + expect(alerts).toContain('sum(pg_stat_activity_count{datname="loopover"})'); expect(alerts).toContain("alert: GittensoryPostgresLockWaits"); - expect(alerts).toContain('pg_stat_activity_count{datname="gittensory", wait_event_type="Lock"}'); + expect(alerts).toContain('pg_stat_activity_count{datname="loopover", wait_event_type="Lock"}'); expect(alerts).toContain("alert: GittensoryPostgresSlowTransaction"); - expect(alerts).toContain('pg_stat_activity_max_tx_duration{datname="gittensory"}'); + expect(alerts).toContain('pg_stat_activity_max_tx_duration{datname="loopover"}'); expect(alerts).toContain("alert: GittensoryPostgresDeadlocks"); - expect(alerts).toContain('pg_stat_database_deadlocks{datname="gittensory"}'); + expect(alerts).toContain('pg_stat_database_deadlocks{datname="loopover"}'); expect(alerts).toContain("alert: GittensoryPostgresDatabaseGrowingFast"); - expect(alerts).toContain('deriv(pg_database_size_bytes{datname="gittensory"}[6h]) > 262144'); + expect(alerts).toContain('deriv(pg_database_size_bytes{datname="loopover"}[6h]) > 262144'); expect(alerts).toContain("alert: GittensoryPostgresDeadTuplesHigh"); - expect(alerts).toContain('pg_stat_user_tables_n_dead_tup{datname="gittensory"}'); + expect(alerts).toContain('pg_stat_user_tables_n_dead_tup{datname="loopover"}'); expect(alerts).toContain("alert: GittensoryBackupMissing"); expect(alerts).toContain('gittensory_backup_files{target=~"postgres|sqlite"} == 0'); expect(alerts).toContain("alert: GittensoryBackupStale"); diff --git a/test/unit/selfhost-image-deploy.test.ts b/test/unit/selfhost-image-deploy.test.ts index 1d53db8ac9..c325c33d2c 100644 --- a/test/unit/selfhost-image-deploy.test.ts +++ b/test/unit/selfhost-image-deploy.test.ts @@ -30,7 +30,7 @@ function createHarness() { const envPath = join(dir, ".env"); mkdirSync(binDir); - writeFileSync(join(dir, "docker-compose.yml"), "services:\n gittensory:\n image: old\n"); + writeFileSync(join(dir, "docker-compose.yml"), "services:\n loopover:\n image: old\n"); writeFileSync( join(binDir, "docker"), `#!/usr/bin/env bash @@ -156,7 +156,7 @@ describe("self-host image deploy script", () => { // REGRESSION: without this reset, an operator's own docker-compose.override.yml build: block for this // service silently wins over the pulled image at `up --no-build` time (found deploying live). expect(harness.readImages()).toContain("build: !reset null"); - expect(harness.readCalls()).toContain("up -d --no-build --no-deps gittensory"); + expect(harness.readCalls()).toContain("up -d --no-build --no-deps loopover"); expect(harness.readCalls()).not.toContain(" build "); } finally { harness.cleanup(); diff --git a/test/unit/selfhost-observability-config.test.ts b/test/unit/selfhost-observability-config.test.ts index 09108994c9..4b793dc0a1 100644 --- a/test/unit/selfhost-observability-config.test.ts +++ b/test/unit/selfhost-observability-config.test.ts @@ -119,15 +119,15 @@ describe("self-host observability trace config", () => { expect(postgresExporter.profiles).toEqual(["postgres", "pgbouncer"]); expect(postgresExporter.depends_on?.postgres).toEqual({ condition: "service_healthy" }); expect(postgresExporter.environment).toMatchObject({ - DATA_SOURCE_URI: "postgres:5432/gittensory?sslmode=disable", - DATA_SOURCE_USER: "gittensory", + DATA_SOURCE_URI: "postgres:5432/loopover?sslmode=disable", + DATA_SOURCE_USER: "loopover", DATA_SOURCE_PASS: "${POSTGRES_PASSWORD:-CHANGEME}", }); expect(backupExporter.profiles).toEqual(["backup"]); expect(backupExporter.volumes).toEqual( expect.arrayContaining([ - "gittensory-backups:/backups:ro", + "loopover-backups:/backups:ro", "./scripts:/scripts:ro", ]), ); diff --git a/test/unit/selfhost-update-script.test.ts b/test/unit/selfhost-update-script.test.ts index 6a98aa0135..5b5528a517 100644 --- a/test/unit/selfhost-update-script.test.ts +++ b/test/unit/selfhost-update-script.test.ts @@ -285,6 +285,6 @@ describe("selfhost-update.sh", () => { }); expect(result.status).not.toBe(0); - expect(result.stderr).toContain("run this script from the gittensory git checkout"); + expect(result.stderr).toContain("run this script from the loopover git checkout"); }); });