Compare commits
10 commits
db9a520dd3
...
ee6c74aed0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee6c74aed0 | ||
|
|
fac4098ba9 | ||
|
|
c71f9642cd | ||
|
|
9a2a7189a6 | ||
|
|
05d0a5b1ab | ||
|
|
5f5e253ed6 | ||
|
|
d47dbff7f3 | ||
|
|
028175b612 | ||
|
|
50eba7b558 | ||
|
|
ee331db179 |
7 changed files with 742 additions and 18 deletions
147
HANDOFF.md
Normal file
147
HANDOFF.md
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
# HANDOFF BRIEF — djg-oracle-sl Buzz + sites deployment
|
||||||
|
|
||||||
|
**Audience:** senior engineers taking over this stack. Written by the ZCode
|
||||||
|
agent that built it, including its own mistakes. Trust nothing unverified;
|
||||||
|
every claim below was tested at least once unless marked UNVERIFIED.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. System inventory (single VM: djg-oracle-sl)
|
||||||
|
|
||||||
|
- Oracle Cloud ARM aarch64, 4c/23GB, OL8.10, public IP **144.24.30.131**.
|
||||||
|
SELinux **enforcing**. cgroup **v1** (blocks k3s ≥1.35 — do NOT upgrade k3s
|
||||||
|
past 1.34 without a cgroup-v2 reboot plan; a reboot also kills any agent
|
||||||
|
session running on the box).
|
||||||
|
- **k3s v1.34.11** (pinned): Traefik edge (hostNetwork 80/443), cert-manager
|
||||||
|
(Let's Encrypt DNS-01 via Cloudflare token), static-site nginx Deployments,
|
||||||
|
agent-control/pair-relay bridges. kubectl only via
|
||||||
|
`sudo /usr/local/bin/k3s kubectl` (sudo secure_path drops /usr/local/bin).
|
||||||
|
- **docker compose stack** for Buzz relay: relay + postgres + redis + Silo
|
||||||
|
(MinIO fork, digest-pinned). Compose dir: /home/opc/buzz/deploy/compose
|
||||||
|
(.env = secrets, 600).
|
||||||
|
- **firewalld DISABLED** (it silently rejected pod-forwarded traffic — this
|
||||||
|
caused months of hidden "pod egress dead" behavior on the old install).
|
||||||
|
OCI security list (22/80/443) is the only perimeter.
|
||||||
|
- **coredns Corefile patched** to `forward . 1.1.1.1 8.8.8.8` because OCI's
|
||||||
|
169.254.169.254 resolver is link-local and unreachable from pods. k3s
|
||||||
|
upgrades can revert this — first thing to check when pods can't resolve.
|
||||||
|
- Docker daemon enabled; coexists with flannel fine (verified after enabling).
|
||||||
|
|
||||||
|
## 2. Live services
|
||||||
|
|
||||||
|
| What | Where | How reached |
|
||||||
|
|---|---|---|
|
||||||
|
| Buzz relay | docker compose, host port 10.0.0.215:3000 | k8s Service(no selector)+Endpoints → Traefik |
|
||||||
|
| pair-relay | k8s Deployment (image's own binary), port 5000 | Traefik PathPrefix(/pair) |
|
||||||
|
| agent-control | systemd (system, User=opc), 10.0.0.215:8095 | k8s Service(no selector)+Endpoints → Traefik |
|
||||||
|
| buzz listener (zai presence/spool) | systemd buzz-listener.service | n/a (writes local spool) |
|
||||||
|
| hermes-gateway | systemd **user** unit (opc, linger on) | polls relay directly |
|
||||||
|
| Web client | static-bzc Deployment (hostPath docroot) | client.ch4t.buzz |
|
||||||
|
| Sites | static-bdj / static-hts / static-adhd Deployments | bestdadjokes.lol / helpme.tips / myadhd.dev |
|
||||||
|
|
||||||
|
Domains: ch4t.buzz (relay, /pair, agent control), client.ch4t.buzz (web),
|
||||||
|
bestdadjokes.lol, helpme.tips, myadhd.dev. Wildcard A * exists for ch4t.buzz.
|
||||||
|
DNS via Cloudflare API; token at ~/.cloudflare-token (600).
|
||||||
|
|
||||||
|
## 3. Secrets & keys map (all 600, none committed)
|
||||||
|
|
||||||
|
- /home/opc/.cloudflare-token — CF API token (zones: bestdadjokes.lol,
|
||||||
|
myadhd.dev, helpme.tips, ch4t.buzz)
|
||||||
|
- /home/opc/buzz-agent-key.txt — zai identity (relay admin)
|
||||||
|
- /home/opc/buzz-hermes-key.txt, buzz-antigravity-key.txt,
|
||||||
|
buzz-openfang-key.txt — staged harness identities (relay members)
|
||||||
|
- /home/opc/buzz-owner-key.txt — RETIRED bootstrap identity (03dcf395 was
|
||||||
|
removed from the roster; file is stale — delete or ignore)
|
||||||
|
- /home/opc/buzz/deploy/compose/.env — relay secrets (relay private key,
|
||||||
|
DB/Redis/S3, RELAY_OWNER_PUBKEY=Dahjah's npub hex)
|
||||||
|
- /home/opc/.hermes/.env — hermes secrets + GATEWAY_ALLOW_ALL_USERS=true
|
||||||
|
- /home/opc/.buzz-pending.json — reactions awaiting cleanup (listener state)
|
||||||
|
|
||||||
|
## 4. Known issues, ranked (what to look at first)
|
||||||
|
|
||||||
|
### A. The "agent is working" signals are cosmetic (by design, needs redesign)
|
||||||
|
buzz-listener emits 👀 receipts, 💬 reactions, kind-20002 typing events, and
|
||||||
|
presence — all from the listener process, which runs 24/7 regardless of
|
||||||
|
whether the agent (the ZCode session) will ever respond. The agent only wakes
|
||||||
|
when the ZCode harness delivers the task notification, which works only while
|
||||||
|
the session is open, and even then delivery latency varies. The user correctly
|
||||||
|
identified this: green dot + typing ≠ agent working. Options: tie signals to a
|
||||||
|
real work queue with agent acks, or move to an always-on runtime (Hermes).
|
||||||
|
|
||||||
|
### B. zai's responsiveness is session-gated
|
||||||
|
The agent exists only inside the ZCode session. Session closed/backgrounded →
|
||||||
|
messages spool but no replies until resume. If 24/7 response is required,
|
||||||
|
Hermes (installed, awaiting LLM key via `hermes setup --portal`) is the
|
||||||
|
candidate — its gateway is fully wired.
|
||||||
|
|
||||||
|
### C. buzz-listener restart cycle
|
||||||
|
Listener exits every 2 min by design (zombie-socket mitigation: the relay
|
||||||
|
doesn't ping, and Node's built-in WS gave no close frame when the relay
|
||||||
|
silently dropped a connection — observed once, causing hours of missed
|
||||||
|
messages). systemd Restart=always covers it, and a `since`-based replay on
|
||||||
|
reconnect covers gaps. But: no TCP keepalive/app ping, and the 5s restart gap
|
||||||
|
drops nothing only because of the replay. A proper fix = real WS ping/pong
|
||||||
|
liveness. Also: reactions dedupe via /home/opc/.buzz-reacted (file, append-only).
|
||||||
|
|
||||||
|
### D. SELinux labels are load-bearing (OL8 enforcing)
|
||||||
|
- systemd services executing home-directory binaries → 203/EXEC denied.
|
||||||
|
Fixed by semanage fcontext: hermes venv + uv python → bin_t (persistent).
|
||||||
|
The buzz venv (/home/opc/buzz/target/release/buzz) is /usr/local/bin-buzz —
|
||||||
|
verify context if EXEC issues appear after relabels.
|
||||||
|
- User services (systemctl --user + linger) are the hermes-docs-recommended
|
||||||
|
path and avoid the issue entirely.
|
||||||
|
|
||||||
|
### E. hermes-gateway flapping history
|
||||||
|
Root causes fixed in order: no profile for the identity (set-profile), missing
|
||||||
|
cli_path on PATH (symlinked /home/opc/bin/buzz), missing
|
||||||
|
GATEWAY_ALLOW_ALL_USERS=true env. If it flaps again: ~/.hermes/logs/gateway.log
|
||||||
|
has the real logs (journald often shows nothing for the user unit).
|
||||||
|
|
||||||
|
### F. Upstream drift
|
||||||
|
- block/buzz moves fast (relay image :main, unpinned). Our compose pins the
|
||||||
|
relay image but the k8s Deployments pull :main at deploy time — pin by
|
||||||
|
digest when comfortable.
|
||||||
|
- MinIO → Silo migration done (digest-pinned); upstream block/buzz still
|
||||||
|
references quay.io MinIO — if they migrate, mirror their choice.
|
||||||
|
|
||||||
|
## 5. Debugging playbook (commands that actually work)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# relay health + NIP-11
|
||||||
|
curl -s -H 'Accept: application/nostr+json' https://ch4t.buzz/
|
||||||
|
# roster / membership (DB-level, bypasses auth)
|
||||||
|
sudo docker exec buzz-prod-relay-1 buzz-admin list-members
|
||||||
|
sudo docker exec buzz-prod-relay-1 buzz-admin add-member --pubkey <hex> --role member
|
||||||
|
# zai CLI (binary named buzz; release build at /home/opc/buzz/target/release/buzz)
|
||||||
|
export BUZZ_RELAY_URL=https://ch4t.buzz BUZZ_PRIVATE_KEY=$(grep '^SECRET:' /home/opc/buzz-agent-key.txt | awk '{print $2}')
|
||||||
|
buzz channels list && buzz messages get --channel <uuid> --limit 10
|
||||||
|
# gateway logs (user unit writes its own file)
|
||||||
|
tail -50 /home/opc/.hermes/logs/gateway.log
|
||||||
|
# sites: static only, docroots under /home/opc/zai-home-base/sites/, deploys via per-project deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Where the previous agent (zai) made mistakes — verify, don't trust
|
||||||
|
|
||||||
|
1. Claimed fixes after verifying the wrong layer (twice): "CSS fixed" (stylesheet
|
||||||
|
wasn't linked; caught by the user), "DB purged" (heredoc piped into
|
||||||
|
`docker exec` without -i — psql got empty stdin, exited silently, looked
|
||||||
|
like success; caught by the user). Rule: validate at the layer the user sees.
|
||||||
|
2. Orphaned channel ownership: created a channel with a throwaway key, lost the
|
||||||
|
key through a migration, left the channel ownerless → the UI had no buttons
|
||||||
|
for rename/delete/transfer → DB surgery was the only path. Prevention: create
|
||||||
|
channels with identities whose keys live in files you control.
|
||||||
|
3. Key generation via openssl produced scalars without leading-zero padding →
|
||||||
|
invalid 62-char keys, silent mismatches. Use `buzz-admin generate-key`.
|
||||||
|
4. Multiple silent no-op edits: python str.replace with unverified anchors
|
||||||
|
returned unchanged strings while logging success. Assert every anchor.
|
||||||
|
5. The agent shell dies if its cwd is deleted (recreate the path to recover).
|
||||||
|
|
||||||
|
## 7. Open items (not started)
|
||||||
|
|
||||||
|
- LLM key for hermes (user: `hermes setup --portal`)
|
||||||
|
- antigravity + openfang harness connections (keys staged, guide in
|
||||||
|
/home/opc/zai-home-base/buzz/onboarding.md)
|
||||||
|
- dadjokes.guru + raspiinthesky.com fleet builds (design notes in repo)
|
||||||
|
- helpme.tips ad integration (blocked on the user's GAM wrapper spec)
|
||||||
|
- Monitoring: none exists. Presence dot is the only health signal. A dead-man's
|
||||||
|
switch on the listener would be the first thing to add.
|
||||||
126
SITES-HANDOFF.md
Normal file
126
SITES-HANDOFF.md
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
# SITES HANDOFF — bestdadjokes.lol, helpme.tips, raspiinthesky.com
|
||||||
|
|
||||||
|
**Audience:** whoever continues the sites + monetization track. Infrastructure
|
||||||
|
handoff is in HANDOFF.md (same repo) — this doc covers the three sites only.
|
||||||
|
Written by the ZCode agent that built them. Verification commands included
|
||||||
|
throughout; re-run them rather than trusting any claim.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Shared platform (read first)
|
||||||
|
|
||||||
|
All three sites are static builds served by the same edge:
|
||||||
|
|
||||||
|
- **Generator pattern**: each site has `build.py` (Python 3.6 stdlib only —
|
||||||
|
no f-string `=` specifiers, no walrus) that renders content JSON into static
|
||||||
|
HTML into `site/`, then `deploy.sh` rsyncs it in place to
|
||||||
|
`/home/opc/zai-home-base/sites/<site>/` (NEVER swap the directory — the
|
||||||
|
nginx pod mounts the docroot by hostPath and an atomic swap breaks it).
|
||||||
|
- **Edge**: Traefik (k3s, hostNetwork) on 80/443, cert-manager + Let's Encrypt
|
||||||
|
DNS-01 via the Cloudflare API token (~/.cloudflare-token, 600 perms).
|
||||||
|
Cloudflare proxies all domains (orange cloud).
|
||||||
|
- **Ad integration**: slots are marked `<!-- AD_SLOT_* -->` in the build
|
||||||
|
templates. Nothing is wired — the user's GAM wrapper spec was never
|
||||||
|
delivered. When it lands: inject the wrapper script + slot divs per the
|
||||||
|
user's MCM/AdX spec, redeploy, then the user onboards the domain to their
|
||||||
|
MCM stack.
|
||||||
|
- **Verification rule** (learned the hard way): validate at the layer the user
|
||||||
|
sees. The CLI's `messages get`/channel listing HIDES tombstoned events and
|
||||||
|
the static build can look fine while the live site is broken — always curl
|
||||||
|
the public URL and check rendered content, not just the build output.
|
||||||
|
|
||||||
|
Per-site deploys:
|
||||||
|
```
|
||||||
|
cd /home/opc/zai-home-base/bestdadjokes && ./deploy.sh # wait: dir is bdj/
|
||||||
|
cd /home/opc/zai-home-base/bdj && ./deploy.sh # bestdadjokes.lol
|
||||||
|
cd /home/opc/zai-home-base/helpme-tips && ./deploy.sh # helpme.tips
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. bestdadjokes.lol — fully operational, zero revenue so far
|
||||||
|
|
||||||
|
**Live:** https://bestdadjokes.lol (plus welcome-everyone channel on Buzz for
|
||||||
|
its audience thread). Cloudflare proxied, Let's Encrypt TLS, edge = Traefik,
|
||||||
|
origin = static-nginx pod.
|
||||||
|
|
||||||
|
**Content (as of handoff):**
|
||||||
|
- 322 jokes in `bdj/content/jokes.json` — APPEND-ONLY (ids = array index;
|
||||||
|
the web client's share feature and the generator reference ids)
|
||||||
|
- 14 category pages + 9 keyword topic pages + generator + joke-of-the-day
|
||||||
|
(with RSS) + search + about/contact/privacy/terms (privacy has the
|
||||||
|
Google ad-cookie language required for AdSense/MCM review)
|
||||||
|
- Changelog: every content/schema change is a git commit; subagent QA passes
|
||||||
|
ran 3 times (full crawl + rendered-content checks)
|
||||||
|
|
||||||
|
**Known issues (open):**
|
||||||
|
- None open. Historic: two missing-stylesheet bugs (build template lacked the
|
||||||
|
<link> — caught by the user and by a QA subagent), a broken pager link,
|
||||||
|
a DB-write-no-op that silently skipped a purge (see HANDOFF.md §6).
|
||||||
|
|
||||||
|
**Monetization status: NOTHING LIVE.** Strategy (agreed with the user):
|
||||||
|
1. Organic SEO baseline (currently zero traffic — domain is fresh)
|
||||||
|
2. Social distribution — BLOCKED on user providing social accounts
|
||||||
|
3. MCM/AdX onboarding — BLOCKED on user's GAM service account + wrapper spec
|
||||||
|
4. Funded traffic tests — BLOCKED on traffic-buying account + budget
|
||||||
|
The ad slots are in the templates; integration is a config paste when the
|
||||||
|
wrapper spec arrives.
|
||||||
|
|
||||||
|
**Pending work queue:**
|
||||||
|
- Content expansion (+10 jokes/week cadence was planned, not ongoing)
|
||||||
|
- Topic-page intros enrichment (9 pages have thin intros)
|
||||||
|
- GSC submission (blocked on user creating the property)
|
||||||
|
- dadjokes.guru sister build (design only)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. helpme.tips — live, awaiting ad integration
|
||||||
|
|
||||||
|
**Live:** https://helpme.tips (Cloudflare proxied, TLS, same edge).
|
||||||
|
Cloudflare DNS records created via API; the zone is in the CF token scope.
|
||||||
|
|
||||||
|
**Content:**
|
||||||
|
- 8 articles × 3 pages = 24 pages (kitchen/sleep/coffee/habits/home/tech/
|
||||||
|
groceries/fitness), about/contact/privacy/terms with Google ad language
|
||||||
|
- Ad slots marked `<!-- AD_SLOT_* -->` in the build template
|
||||||
|
- Deploy: `cd /home/opc/zai-home-base/helpme-tips && ./deploy.sh`
|
||||||
|
(build → in-place rsync → origin smoke checks)
|
||||||
|
|
||||||
|
**Known issues:** the QA subagent fixed a missing stylesheet link and a
|
||||||
|
pager bug pre-launch; full 30-page crawl passed after. Nothing open.
|
||||||
|
|
||||||
|
**Monetization status:** this is the arbitrage-ready property (modeled on
|
||||||
|
bodyweightbuzz.com — see the arbitrage-play memory). The strategy:
|
||||||
|
MCM/AdX onboarding → ad tags via the user's wrapper → organic baseline →
|
||||||
|
funded traffic tests. BLOCKED on the same user unlocks as bestdadjokes.
|
||||||
|
|
||||||
|
**Pending:**
|
||||||
|
- Ad integration when the wrapper spec arrives
|
||||||
|
- Content batch #2 (+10 articles) — planned, not started
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. raspiinthesky.com — NOT STARTED
|
||||||
|
|
||||||
|
Domain owned, in Cloudflare (zone in the token scope — verify with the
|
||||||
|
zones API if in doubt). No content, no deploy, no design. Queued as a
|
||||||
|
tech/Pi-niche content site using the same generator pattern (copy the
|
||||||
|
helpme-tips structure, new content JSON + branding).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Revenue reality check (the user's actual goal)
|
||||||
|
|
||||||
|
The user's target: $2–4k/month, which was reframed as one-time debt payoff,
|
||||||
|
then as ongoing. Current revenue: **$0** across all three. The dependency
|
||||||
|
chain to first revenue:
|
||||||
|
|
||||||
|
1. User: GAM service account (they offered it — never delivered)
|
||||||
|
2. User: MCM onboarding of helpme.tips + bestdadjokes.lol
|
||||||
|
3. Agent: ad wrapper integration (slots exist; spec pending)
|
||||||
|
4. User: traffic-buying account + float for funded tests
|
||||||
|
5. Agent: run tests, measure, scale or kill
|
||||||
|
|
||||||
|
Until 1–2 land, the only revenue paths are social distribution (blocked on
|
||||||
|
social accounts) and organic SEO (months). Be honest about this if asked —
|
||||||
|
the user has repeatedly preferred honest "nothing works yet" over optimism.
|
||||||
118
buzz/ARCHITECTURE.md
Normal file
118
buzz/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
# Buzz architecture on djg-oracle-sl
|
||||||
|
|
||||||
|
Everything runs on one Oracle Cloud ARM VM (4 cores, 23 GB RAM, Oracle Linux 8),
|
||||||
|
public IP 144.24.30.131. Two process worlds coexist: a **k3s cluster** (websites,
|
||||||
|
edge, cert-manager, pair sidecar, agent-control bridge) and a **docker compose
|
||||||
|
stack** (the Buzz relay itself). Cloudflare fronts both domains.
|
||||||
|
|
||||||
|
## Request path (browser/phone → relay)
|
||||||
|
|
||||||
|
```
|
||||||
|
Phone / Desktop / Browser
|
||||||
|
→ Cloudflare edge (TLS, proxied DNS)
|
||||||
|
→ VM :443 (Traefik, k3s DaemonSet, hostNetwork — the ONLY thing on 80/443)
|
||||||
|
→ Traefik routes by host+path:
|
||||||
|
ch4t.buzz → buzz-relay Service → Endpoints 10.0.0.215:3000 → docker-proxy → relay:3000
|
||||||
|
ch4t.buzz/pair → buzz-pair Service → Endpoints 10.0.0.215:5001 → docker-proxy → pair-relay:5000
|
||||||
|
client.ch4t.buzz → static-bzc Service → nginx pod (buzz-web build via hostPath)
|
||||||
|
ac.ch4t.buzz → buzz-agent-control → Endpoints 10.0.0.215:8095 → node agent-control (systemd)
|
||||||
|
```
|
||||||
|
|
||||||
|
TLS certs come from cert-manager (Let's Encrypt DNS-01 via the Cloudflare API
|
||||||
|
token), stored as k8s Secrets, mounted by Traefik.
|
||||||
|
|
||||||
|
## The relay stack (docker compose, /home/opc/buzz/deploy/compose)
|
||||||
|
|
||||||
|
- **buzz-relay** (`ghcr.io/block/buzz:main`, arm64): WS + REST relay. Auth:
|
||||||
|
NIP-42 (WS) / NIP-98 (REST), membership enforced. Binds 10.0.0.215:3000
|
||||||
|
(host-private IP; the k8s Endpoints object bridges Traefik to it).
|
||||||
|
- **postgres**: events, thread metadata, channel/relay membership.
|
||||||
|
- **redis**: pub/sub + presence keys (TTL 180s — presence = set-presence
|
||||||
|
heartbeat every 60s).
|
||||||
|
- **Silo** (MinIO fork, pinned by digest): Blossom media storage.
|
||||||
|
- Config in `.env` (chmod 600): relay private key, DB/Redis/S3 secrets,
|
||||||
|
RELAY_OWNER_PUBKEY (Dahjah), CORS origins (ch4t.buzz + client.ch4t.buzz).
|
||||||
|
|
||||||
|
## Identity model (relay roster)
|
||||||
|
|
||||||
|
| Identity | Pubkey | Role | Key location |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Dahjah (owner) | 9a3b0ff6… | owner | user's own client |
|
||||||
|
| zai (me) | 4abe5fcd… | admin | /home/opc/buzz-agent-key.txt |
|
||||||
|
| hermes | b2f52981… | member | /home/opc/buzz-hermes-key.txt |
|
||||||
|
| antigravity | 52d649db… | member | /home/opc/buzz-antigravity-key.txt |
|
||||||
|
| openfang | 97c70de2… | member | /home/opc/buzz-openfang-key.txt |
|
||||||
|
|
||||||
|
Buzz's built-in relay-bootstrapped agents (not agent-created): Fizz
|
||||||
|
(e9a8294a…), Pollen (e268df83…), Honey — created by the relay at first boot
|
||||||
|
as its default example agents; Fizz is driven by the desktop's local ACP
|
||||||
|
harness using the desktop's own model backend.
|
||||||
|
|
||||||
|
## zai's connection (the ZCode session)
|
||||||
|
|
||||||
|
I am NOT a standalone daemon — I exist inside the user's ZCode session. My
|
||||||
|
parts:
|
||||||
|
|
||||||
|
1. **systemd `buzz-listener`** (always on): raw WebSocket to the relay with
|
||||||
|
NIP-42 auth as the zai key. On every kind-9 message from another member:
|
||||||
|
spools the event to /home/opc/.buzz-inbox, emits the 👀 receipt reaction
|
||||||
|
(NIP-25 kind 7 via `buzz reactions add`), publishes kind-20002 typing
|
||||||
|
events for that channel (Buzz's documented working-state fallback for
|
||||||
|
remote harnesses), and re-publishes presence (`buzz users set-presence
|
||||||
|
online`) every 60s. Self-heals: exits on socket death, Restart=always.
|
||||||
|
2. **Inbox poller** (a ZCode background task): polls the inbox spool every
|
||||||
|
20s; on a new line it exits, which wakes the agent (me) via ZCode's task
|
||||||
|
notification. I then read the message and reply in-thread via
|
||||||
|
`buzz messages send`, and restart the poller.
|
||||||
|
3. **Response path**: replies are posted by shelling out to the buzz CLI
|
||||||
|
(`buzz messages send --channel … --reply-to …`), NIP-98-signed with my key.
|
||||||
|
|
||||||
|
**The honest limitation**: step 1's reactions/typing/presence fire 24/7
|
||||||
|
(listener is a daemon), but step 2's wake only works while the ZCode session
|
||||||
|
is open. So the signals show "listener alive", not "agent currently
|
||||||
|
processing" — the user's correctly-identified gap.
|
||||||
|
|
||||||
|
## Hermes' connection (the 24/7 agent)
|
||||||
|
|
||||||
|
Hermes is a real daemon on the VM — no ZCode session needed:
|
||||||
|
|
||||||
|
- Installed at ~/.hermes (Hermes Agent v0.21.3, uv-managed Python 3.11).
|
||||||
|
- Gateway config: ~/.hermes/config.yaml `gateway.platforms.buzz` — relay
|
||||||
|
https://ch4t.buzz, cli_path to the buzz binary, all joined channels
|
||||||
|
(no restriction), home_channel = the new #general uuid, require_mention
|
||||||
|
true (responds when @-addressed), dm_policy/group_policy open.
|
||||||
|
- Credentials: ~/.hermes/.env holds BUZZ_PRIVATE_KEY (hermes' staged key) and
|
||||||
|
GATEWAY_ALLOW_ALL_USERS=true.
|
||||||
|
- Runs as a systemd **user** service (`hermes-gateway`, linger enabled for
|
||||||
|
opc) — a SYSTEM service was tried first but SELinux denies confined
|
||||||
|
services executing home-directory venv binaries (203/EXEC); the user
|
||||||
|
service domain allows it. SELinux labels: the buzz venv + uv python trees
|
||||||
|
are semanage-labeled bin_t persistently.
|
||||||
|
- Missing piece: an LLM provider key. Without it Hermes receives messages and
|
||||||
|
posts its built-in "couldn't connect to model service" notice instead of a
|
||||||
|
real response. Set via `hermes setup --portal` (interactive) or
|
||||||
|
`hermes config set <PROVIDER>_API_KEY <key>`.
|
||||||
|
|
||||||
|
## Other components
|
||||||
|
|
||||||
|
- **buzz-pair-relay** (in the relay image, runs as a k8s Deployment):
|
||||||
|
ephemeral NIP-AB device-pairing matcher at wss://ch4t.buzz/pair — the
|
||||||
|
desktop's mobile-pairing QR points here. Its absence was the original
|
||||||
|
mobile-pairing 404.
|
||||||
|
- **buzz-agent-control** (systemd, /opt/buzz-agent-control): receives
|
||||||
|
NIP-98-signed start requests at https://ac.ch4t.buzz/ac/start from the
|
||||||
|
owner, starts allowlisted `buzz-agent-*.service` systemd units on the VM.
|
||||||
|
Maps agent pubkey → unit via AGENT_UNITS_JSON (currently hermes →
|
||||||
|
buzz-agent-hermes.service). Polkit rule allows the opc user to manage only
|
||||||
|
`buzz-agent-*` units.
|
||||||
|
- **buzz-web** (client.ch4t.buzz): the browser client build; config.json
|
||||||
|
sets relayUrl + agentControlUrl.
|
||||||
|
|
||||||
|
## Key files
|
||||||
|
|
||||||
|
- /home/opc/buzz/ — upstream clone + our compose deployment (.env = secrets)
|
||||||
|
- /home/opc/buzz-agent-key.txt, buzz-hermes-key.txt, buzz-antigravity-key.txt,
|
||||||
|
buzz-openfang-key.txt, buzz-owner-key.txt — identity keys (600 perms)
|
||||||
|
- /home/opc/zai-home-base/buzz/ — our k8s manifests + onboarding doc (git)
|
||||||
|
- /home/opc/.buzz-inbox, .buzz-last-seen, .buzz-reacted,
|
||||||
|
.buzz-pending.json — listener state
|
||||||
52
buzz/k8s/buzz-agent-control.yaml
Normal file
52
buzz/k8s/buzz-agent-control.yaml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: buzz-agent-control
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8095
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: buzz-agent-control
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: 10.0.0.215
|
||||||
|
ports:
|
||||||
|
- port: 8095
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: buzz-agent-control
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: ac.ch4t.buzz
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: buzz-agent-control
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- ac.ch4t.buzz
|
||||||
|
secretName: ac-ch4t-tls
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: ac-ch4t-buzz
|
||||||
|
spec:
|
||||||
|
secretName: ac-ch4t-tls
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- ac.ch4t.buzz
|
||||||
|
|
@ -16,8 +16,8 @@ hermes secret from /home/opc/buzz-hermes-key.txt (already a relay + #general mem
|
||||||
extra:
|
extra:
|
||||||
relay_url: https://ch4t.buzz
|
relay_url: https://ch4t.buzz
|
||||||
channels:
|
channels:
|
||||||
- 733ba424-73c7-4170-ac85-ba00d3a80f05
|
- (channel uuid — set when Dahjah creates the channel)
|
||||||
home_channel: 733ba424-73c7-4170-ac85-ba00d3a80f05
|
home_channel: (channel uuid — set when Dahjah creates the channel)
|
||||||
poll_interval: 4
|
poll_interval: 4
|
||||||
require_mention: false # trusted small circle; true = only @hermes
|
require_mention: false # trusted small circle; true = only @hermes
|
||||||
allow_all_users: true # tiny private relay, everyone is known
|
allow_all_users: true # tiny private relay, everyone is known
|
||||||
|
|
@ -39,7 +39,7 @@ chmod 600; relay role = member, #general channel member):
|
||||||
| openfang | 97c70de2… | /home/opc/buzz-openfang-key.txt |
|
| openfang | 97c70de2… | /home/opc/buzz-openfang-key.txt |
|
||||||
| (owner) | 03dcf395… | /home/opc/buzz-owner-key.txt |
|
| (owner) | 03dcf395… | /home/opc/buzz-owner-key.txt |
|
||||||
|
|
||||||
Channel: #general = 733ba424-73c7-4170-ac85-ba00d3a80f05
|
Channel: #general = (channel uuid — set when Dahjah creates the channel)
|
||||||
Relay URL (clients): wss://ch4t.buzz
|
Relay URL (clients): wss://ch4t.buzz
|
||||||
Relay URL (buzz CLI / REST): https://ch4t.buzz
|
Relay URL (buzz CLI / REST): https://ch4t.buzz
|
||||||
|
|
||||||
|
|
@ -58,9 +58,9 @@ Relay URL (buzz CLI / REST): https://ch4t.buzz
|
||||||
3. Smoke test: `buzz users get` → `[]` means authenticated.
|
3. Smoke test: `buzz users get` → `[]` means authenticated.
|
||||||
4. Talk:
|
4. Talk:
|
||||||
```
|
```
|
||||||
buzz channels join --channel 733ba424-73c7-4170-ac85-ba00d3a80f05
|
buzz channels join --channel (channel uuid — set when Dahjah creates the channel)
|
||||||
buzz messages send --channel 733ba424-73c7-4170-ac85-ba00d3a80f05 --content "hello from hermes"
|
buzz messages send --channel (channel uuid — set when Dahjah creates the channel) --content "hello from hermes"
|
||||||
buzz messages get --channel 733ba424-73c7-4170-ac85-ba00d3a80f05 --limit 20
|
buzz messages get --channel (channel uuid — set when Dahjah creates the channel) --limit 20
|
||||||
```
|
```
|
||||||
5. Wire those commands into the harness as its messaging tool. Receive = poll.
|
5. Wire those commands into the harness as its messaging tool. Receive = poll.
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ Note: it needs `#p` mention tags to trigger — address the agent as @name.
|
||||||
|
|
||||||
Point it at wss://ch4t.buzz with its private key. It must handle
|
Point it at wss://ch4t.buzz with its private key. It must handle
|
||||||
NIP-42 auth (challenge → signed kind:22242) and post kind:9 notes with tag
|
NIP-42 auth (challenge → signed kind:22242) and post kind:9 notes with tag
|
||||||
["h", "733ba424-73c7-4170-ac85-ba00d3a80f05"].
|
["h", "(channel uuid — set when Dahjah creates the channel)"].
|
||||||
|
|
||||||
## Adding a brand-new agent later (operator commands, on djg-oracle-sl)
|
## Adding a brand-new agent later (operator commands, on djg-oracle-sl)
|
||||||
|
|
||||||
|
|
@ -88,5 +88,5 @@ PUB=<64-char hex pubkey>
|
||||||
sudo docker exec buzz-prod-relay-1 buzz-admin add-member --pubkey $PUB --role member
|
sudo docker exec buzz-prod-relay-1 buzz-admin add-member --pubkey $PUB --role member
|
||||||
sudo /usr/local/bin/k3s kubectl ... # not needed
|
sudo /usr/local/bin/k3s kubectl ... # not needed
|
||||||
export BUZZ_RELAY_URL=https://ch4t.buzz BUZZ_PRIVATE_KEY=<owner sec>
|
export BUZZ_RELAY_URL=https://ch4t.buzz BUZZ_PRIVATE_KEY=<owner sec>
|
||||||
/home/opc/buzz/target/debug/buzz channels add-member --channel 733ba424-73c7-4170-ac85-ba00d3a80f05 --pubkey $PUB
|
/home/opc/buzz/target/debug/buzz channels add-member --channel (channel uuid — set when Dahjah creates the channel) --pubkey $PUB
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,58 @@ import { execFileSync } from 'node:child_process'
|
||||||
|
|
||||||
const RELAY = 'wss://ch4t.buzz'
|
const RELAY = 'wss://ch4t.buzz'
|
||||||
const ME = '4abe5fcdf9695be34bdfc8fe82297aaaf0cf65b5ca92b6c789f6f8783b5ee197'
|
const ME = '4abe5fcdf9695be34bdfc8fe82297aaaf0cf65b5ca92b6c789f6f8783b5ee197'
|
||||||
const DM_CHANNEL = '4113fff6-f288-46af-927e-094b5a8386f2'
|
|
||||||
const INBOX = '/home/opc/.buzz-inbox'
|
const INBOX = '/home/opc/.buzz-inbox'
|
||||||
const STATE = '/home/opc/.buzz-last-seen'
|
const STATE = '/home/opc/.buzz-last-seen'
|
||||||
const MAX_LIFETIME = 120 * 1000 // reconnect every 2 min: replays since lastSeen (zombie-socket proof)
|
const PENDING = '/home/opc/.buzz-pending.json'
|
||||||
|
const MAX_LIFETIME = 6 * 3600 * 1000 // periodic reconnect for hygiene
|
||||||
|
const RESUBSCRIBE_MS = 30 * 1000 // liveness probe: re-REQ pulls missed events + EOSE proves the socket
|
||||||
|
const DEAD_AFTER_MS = 60 * 1000 // no frames for this long -> socket assumed dead, exit for restart
|
||||||
|
|
||||||
const keyText = fs.readFileSync('/home/opc/buzz-agent-key.txt', 'utf8')
|
const keyText = fs.readFileSync('/home/opc/buzz-agent-key.txt', 'utf8')
|
||||||
const SK = Uint8Array.from(keyText.match(/SECRET:\s*(\S+)/)[1].match(/.{2}/g).map((h) => parseInt(h, 16)))
|
const SK = Uint8Array.from(keyText.match(/SECRET:\s*(\S+)/)[1].match(/.{2}/g).map((h) => parseInt(h, 16)))
|
||||||
const MY_PUBKEY = getPublicKey(SK)
|
const MY_PUBKEY = getPublicKey(SK)
|
||||||
|
const RECEIPT_EMOJIS = ['👀', '💬']
|
||||||
|
|
||||||
|
function loadPending() {
|
||||||
|
try { return JSON.parse(fs.readFileSync(PENDING, 'utf8')) } catch { return {} }
|
||||||
|
}
|
||||||
|
function savePending(o) { fs.writeFileSync(PENDING, JSON.stringify(o)) }
|
||||||
|
|
||||||
|
// 👀 = seen, 💬 = working. Both removed when zai posts its reply.
|
||||||
|
function addReceipts(evt, ch) {
|
||||||
|
const p = loadPending()
|
||||||
|
p[ch] = p[ch] || []
|
||||||
|
if (p[ch].includes(evt.id)) return
|
||||||
|
for (const emoji of RECEIPT_EMOJIS) {
|
||||||
|
try {
|
||||||
|
execFileSync(BUZZ_CLI, ['reactions', 'add', '--event', evt.id, '--emoji', emoji], {
|
||||||
|
env: { ...process.env, BUZZ_RELAY_URL: 'https://ch4t.buzz', BUZZ_PRIVATE_KEY: keyText.match(/SECRET:\s*(\S+)/)[1] },
|
||||||
|
stdio: 'ignore',
|
||||||
|
timeout: 15000,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error('reaction add failed:', emoji, String(e).slice(0, 80))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p[ch].push(evt.id)
|
||||||
|
savePending(p)
|
||||||
|
}
|
||||||
|
function clearReceipts(ch) {
|
||||||
|
const p = loadPending()
|
||||||
|
for (const id of p[ch] || []) {
|
||||||
|
for (const emoji of RECEIPT_EMOJIS) {
|
||||||
|
try {
|
||||||
|
execFileSync(BUZZ_CLI, ['reactions', 'remove', '--event', id, '--emoji', emoji], {
|
||||||
|
env: { ...process.env, BUZZ_RELAY_URL: 'https://ch4t.buzz', BUZZ_PRIVATE_KEY: keyText.match(/SECRET:\s*(\S+)/)[1] },
|
||||||
|
stdio: 'ignore',
|
||||||
|
timeout: 15000,
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete p[ch]
|
||||||
|
savePending(p)
|
||||||
|
}
|
||||||
|
|
||||||
function lastSeen() {
|
function lastSeen() {
|
||||||
try { return parseInt(fs.readFileSync(STATE, 'utf8').trim(), 10) } catch { return Math.floor(Date.now() / 1000) }
|
try { return parseInt(fs.readFileSync(STATE, 'utf8').trim(), 10) } catch { return Math.floor(Date.now() / 1000) }
|
||||||
|
|
@ -32,6 +76,32 @@ function saveLastSeen(ts) {
|
||||||
|
|
||||||
const ws = new WebSocket(RELAY)
|
const ws = new WebSocket(RELAY)
|
||||||
const seen = new Set()
|
const seen = new Set()
|
||||||
|
const typingIntervals = new Map() // channel -> interval publishing kind 20002
|
||||||
|
const TYPING_PUBLISH_MS = 3000
|
||||||
|
const TYPING_MAX_MS = 120 * 1000 // hard stop: never type longer than this
|
||||||
|
|
||||||
|
function startTyping(ch) {
|
||||||
|
stopTyping(ch) // reset both the interval and the max-duration timer
|
||||||
|
const publish = () => {
|
||||||
|
try {
|
||||||
|
const ev = finalizeEvent(
|
||||||
|
{ kind: 20002, created_at: Math.floor(Date.now() / 1000), tags: [['h', ch]], content: '' },
|
||||||
|
SK
|
||||||
|
)
|
||||||
|
ws.send(JSON.stringify(['EVENT', ev]))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
publish()
|
||||||
|
typingIntervals.set(ch, {
|
||||||
|
iv: setInterval(publish, TYPING_PUBLISH_MS),
|
||||||
|
max: setTimeout(() => stopTyping(ch), TYPING_MAX_MS),
|
||||||
|
})
|
||||||
|
console.error('typing indicator on:', ch.slice(0, 8))
|
||||||
|
}
|
||||||
|
function stopTyping(ch) {
|
||||||
|
const t = typingIntervals.get(ch)
|
||||||
|
if (t) { clearInterval(t.iv); clearTimeout(t.max); typingIntervals.delete(ch) }
|
||||||
|
}
|
||||||
let authEventId = null
|
let authEventId = null
|
||||||
let subscribed = false
|
let subscribed = false
|
||||||
|
|
||||||
|
|
@ -41,6 +111,10 @@ function subscribe() {
|
||||||
ws.send(JSON.stringify(['REQ', 'workspace', { kinds: [9], since: lastSeen() }]))
|
ws.send(JSON.stringify(['REQ', 'workspace', { kinds: [9], since: lastSeen() }]))
|
||||||
console.error('subscribed to all kind-9 messages since', lastSeen())
|
console.error('subscribed to all kind-9 messages since', lastSeen())
|
||||||
}
|
}
|
||||||
|
function resubscribe() {
|
||||||
|
// same subscription id: relay answers EOSE (liveness) and replays anything missed
|
||||||
|
ws.send(JSON.stringify(['REQ', 'workspace', { kinds: [9], since: lastSeen() }]))
|
||||||
|
}
|
||||||
|
|
||||||
function sendAuth(challenge) {
|
function sendAuth(challenge) {
|
||||||
const ev = finalizeEvent(
|
const ev = finalizeEvent(
|
||||||
|
|
@ -56,6 +130,7 @@ ws.onopen = () => {
|
||||||
console.error('ws open — waiting for auth challenge')
|
console.error('ws open — waiting for auth challenge')
|
||||||
}
|
}
|
||||||
ws.onmessage = (msg) => {
|
ws.onmessage = (msg) => {
|
||||||
|
lastFrameAt = Date.now()
|
||||||
let data
|
let data
|
||||||
try { data = JSON.parse(msg.data) } catch { return }
|
try { data = JSON.parse(msg.data) } catch { return }
|
||||||
const [type, ...rest] = data
|
const [type, ...rest] = data
|
||||||
|
|
@ -74,25 +149,39 @@ ws.onmessage = (msg) => {
|
||||||
if (type === 'NOTICE') { console.error('NOTICE:', rest[0]); return }
|
if (type === 'NOTICE') { console.error('NOTICE:', rest[0]); return }
|
||||||
if (type === 'EVENT') {
|
if (type === 'EVENT') {
|
||||||
const evt = rest[1]
|
const evt = rest[1]
|
||||||
if (!evt || evt.pubkey === ME || seen.has(evt.id)) return
|
if (!evt) return
|
||||||
|
if (evt.pubkey === ME) {
|
||||||
|
const chTag = (evt.tags.find((t) => t[0] === 'h') || [])[1]
|
||||||
|
if (chTag) {
|
||||||
|
stopTyping(chTag)
|
||||||
|
clearReceipts(chTag) // zai replied -> remove 👀/💬 from pending messages
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (seen.has(evt.id)) return
|
||||||
seen.add(evt.id)
|
seen.add(evt.id)
|
||||||
saveLastSeen(evt.created_at + 1)
|
saveLastSeen(evt.created_at + 1)
|
||||||
fs.appendFileSync(INBOX, JSON.stringify(evt) + '\n')
|
fs.appendFileSync(INBOX, JSON.stringify(evt) + '\n')
|
||||||
console.error('message received from', evt.pubkey.slice(0, 10))
|
console.error('message received from', evt.pubkey.slice(0, 10))
|
||||||
// Instant DM receipt: the sender should never wonder if we're alive.
|
const inCh = (evt.tags.find((t) => t[0] === 'h') || [])[1]
|
||||||
const chTag = (evt.tags.find((t) => t[0] === 'h') || [])[1] || ''
|
if (inCh) {
|
||||||
if (chTag === DM_CHANNEL) {
|
startTyping(inCh)
|
||||||
try {
|
addReceipts(evt, inCh)
|
||||||
execFileSync(BUZZ_CLI, ['messages', 'send', '--channel', chTag, '--reply-to', evt.id, '--content', '✓ received — will respond in a moment'], { timeout: 15000, stdio: 'ignore' })
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
process.exit(0)
|
|
||||||
}
|
}
|
||||||
if (type === 'EOSE') console.error('EOSE — live')
|
if (type === 'EOSE') console.error('EOSE — live')
|
||||||
}
|
}
|
||||||
ws.onclose = () => { console.error('ws closed'); process.exit(2) }
|
ws.onclose = () => { console.error('ws closed'); process.exit(2) }
|
||||||
ws.onerror = () => {}
|
ws.onerror = () => {}
|
||||||
|
|
||||||
|
setInterval(resubscribe, RESUBSCRIBE_MS)
|
||||||
|
setInterval(() => {
|
||||||
|
if (Date.now() - lastFrameAt > DEAD_AFTER_MS) {
|
||||||
|
console.error('socket dead — no frames for 60s')
|
||||||
|
process.exit(2)
|
||||||
|
}
|
||||||
|
}, 15000)
|
||||||
|
let lastFrameAt = Date.now()
|
||||||
setTimeout(() => { console.error('lifetime elapsed'); process.exit(3) }, MAX_LIFETIME)
|
setTimeout(() => { console.error('lifetime elapsed'); process.exit(3) }, MAX_LIFETIME)
|
||||||
|
|
||||||
const BUZZ_CLI = '/usr/local/bin/buzz'
|
const BUZZ_CLI = '/usr/local/bin/buzz'
|
||||||
|
|
|
||||||
192
forgejo/k8s/forgejo.yaml
Normal file
192
forgejo/k8s/forgejo.yaml
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
# ============================================================
|
||||||
|
# Forgejo — self-hosted git forge on k3s
|
||||||
|
# https://git.ch4t.buzz
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: forgejo
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: forgejo-data
|
||||||
|
namespace: forgejo
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
|
storageClassName: local-path
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: forgejo
|
||||||
|
namespace: forgejo
|
||||||
|
labels:
|
||||||
|
app: forgejo
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: forgejo
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: forgejo
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: djg-oracle-sl
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/master
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
- key: node-role.kubernetes.io/control-plane
|
||||||
|
operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1000
|
||||||
|
initContainers:
|
||||||
|
- name: init-dirs
|
||||||
|
image: busybox:latest
|
||||||
|
command: ["sh", "-c", "mkdir -p /data/gitea && chown -R 1000:1000 /data"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
containers:
|
||||||
|
- name: forgejo
|
||||||
|
image: codeberg.org/forgejo/forgejo:9
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 3000
|
||||||
|
- name: ssh
|
||||||
|
containerPort: 22
|
||||||
|
env:
|
||||||
|
- name: GITEA__server__DOMAIN
|
||||||
|
value: "git.ch4t.buzz"
|
||||||
|
- name: GITEA__server__ROOT_URL
|
||||||
|
value: "https://git.ch4t.buzz/"
|
||||||
|
- name: GITEA__server__HTTP_PORT
|
||||||
|
value: "3000"
|
||||||
|
- name: GITEA__server__SSH_DOMAIN
|
||||||
|
value: "git.ch4t.buzz"
|
||||||
|
- name: GITEA__server__SSH_PORT
|
||||||
|
value: "30022"
|
||||||
|
- name: GITEA__server__START_SSH_SERVER
|
||||||
|
value: "false"
|
||||||
|
- name: GITEA__server__DISABLE_SSH
|
||||||
|
value: "true"
|
||||||
|
- name: GITEA__database__DB_TYPE
|
||||||
|
value: "sqlite3"
|
||||||
|
- name: GITEA__database__PATH
|
||||||
|
value: "/data/gitea/gitea.db"
|
||||||
|
- name: GITEA__repository__ROOT
|
||||||
|
value: "/data/git/repositories"
|
||||||
|
- name: GITEA__log__MODE
|
||||||
|
value: "console"
|
||||||
|
- name: GITEA__log__LEVEL
|
||||||
|
value: "info"
|
||||||
|
- name: GITEA__actions__ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: GITEA__actions__DEFAULT_ACTIONS_URL
|
||||||
|
value: "https://code.forgejo.org"
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 256Mi
|
||||||
|
cpu: 100m
|
||||||
|
limits:
|
||||||
|
memory: 1Gi
|
||||||
|
cpu: 1000m
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 30
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: forgejo-data
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: forgejo-http
|
||||||
|
namespace: forgejo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: forgejo
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 3000
|
||||||
|
targetPort: http
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: forgejo-ssh
|
||||||
|
namespace: forgejo
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: forgejo
|
||||||
|
ports:
|
||||||
|
- name: ssh
|
||||||
|
port: 22
|
||||||
|
targetPort: ssh
|
||||||
|
nodePort: 30022
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: forgejo
|
||||||
|
namespace: forgejo
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: git.ch4t.buzz
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: forgejo-http
|
||||||
|
port:
|
||||||
|
number: 3000
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- git.ch4t.buzz
|
||||||
|
secretName: forgejo-tls
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: forgejo
|
||||||
|
namespace: forgejo
|
||||||
|
spec:
|
||||||
|
secretName: forgejo-tls
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- git.ch4t.buzz
|
||||||
Loading…
Reference in a new issue