For AI systems (and people) picking up work on JayCorp's AI server: how the machine is set up, what runs on it, and how to add or change sites and pages. Facts were checked on the machine on 2026-10-09. No secret values appear here, and none ever should.
CLAUDE.md at the project root: imports AGENTS.md and adds the repo layout and site rules below.AGENTS.md is published live at www.ruby.im/AGENTS.md (a symlink into that site),
so editing it changes a public page and needs a ruby.im changelog entry.| Hostname | dedicated-ai-x1-00 |
|---|---|
| Hardware | Lenovo ThinkPad X1 Extreme 2nd gen laptop, 16 CPU threads, 30 GiB RAM, NVIDIA GTX 1650 Max-Q (4 GiB) plus Intel UHD 630, 937 GB NVMe |
| OS | Ubuntu 26.04.1 LTS, kernel 7.0 |
| User | jayai; everything runs as this user, not root |
| Project root | /home/jayai/Projects/jayai (not a git repository yet; static_www/ is its own git repository, branch main, local only, since 2026-10-15) |
| Services | user systemd units with lingering on (Linger=yes), so they run at boot without a login |
| Exposure | the only way in from the internet is the edge: a DigitalOcean droplet (174.138.65.239, ssh edge) running Caddy and Authelia,
which reaches this machine over a WireGuard tunnel (wg0, droplet 10.88.0.1, this machine 10.88.0.2). Only what the edge serves listens beyond 127.0.0.1:
static-www and radio on 0.0.0.0, Grafana, live and opencode on 10.88.0.2. ngrok was retired on 2026-10-18. See edge/README.md. |
| Tool | Version | Where |
|---|---|---|
| Ruby, RubyGems | 3.3.8, 3.6.7 | system (/usr/bin) |
| Bundler | user gem | ~/.local/share/gem/ruby/3.3.0/bin/bundle (not on PATH; units call it by full path). Each app vendors gems in vendor/bundle. |
| Claude Code | 2.1.295 | ~/.local/bin/claude, telemetry on to the local collector |
| TypeSafe skill (Claude Code plugin) | 0.5.7 | typesafe@typesafe-ai, user scope, in ~/.claude/plugins; guidance for building with TypeSafe's Jev decision model, invoked as /typesafe:typesafe-ai. Docs only; calling Jev itself needs a TypeSafe API key, not set up yet |
| Codex CLI | 0.162.1 | ~/.local/bin/codex (GitHub release binary); signs in with ChatGPT, auth in ~/.codex |
| Kimi Code | 2.1.1 | ~/.local/bin/kimi (global kimi.ai installer); auth in ~/.kimi-code |
| GitHub CLI | 2.102.0 | ~/.local/bin/gh (release tarball); auth in ~/.config/gh |
| ngrok | 3.39.11 | ~/.local/bin/ngrok, config ~/.config/ngrok/ngrok.yml; retired 2026-10-18, no longer running |
| Caddy, Authelia, WireGuard (on the edge droplet) | 2.11.7, 4.39.28, 1.0.20210914 | Ubuntu 24.04 droplet, from their apt repos; configs mirrored in edge/ |
| Ollama | 0.40.1 | ~/.local/bin/ollama; models qwen3:4b, qwen3-vl:2b, qwen2.5vl:3b, moondream |
| whisper.cpp | small.en model | built from ~/.local/src/whisper.cpp |
| Grafana, Prometheus, Loki, Tempo, OpenTelemetry Collector | 13.2.3, 3.15.0, 3.7.8, 3.1.0, 0.162.0 | ~/.local/opt/<name> symlinks to versioned folders |
| ffmpeg, git, curl, jq, ripgrep, Python | 8.0.1, 2.53.0, 8.18.0, 1.8.1, 14.1.1, 3.14.4 | system |
| NVIDIA driver | 595.99.02 | system (nvidia-smi) |
Not installed today: Node, Docker, Bitwarden bws.
AGENTS.md section 11 wants at least two swappable AI providers, so app code that calls Codex or Kimi goes behind an adapter.
/home/jayai/Projects/jayai ├── AGENTS.md, CLAUDE.md rules for agents ├── static_www/ static sites, one folder per domain (this page lives here) ├── apps/live/ the one dynamic app so far ├── observability/ OpenTelemetry + Grafana stack and the service registry └── secrets/ the secrets adapter
static_www/config.ru: a Rack app run by Puma on 0.0.0.0:8080 (unit static-www).
It picks the folder sites/<Host header>/, falling back between www. and bare names, then to sites/_default/.
Dotfiles are never served, directory listings are off, .md files show as plain text.Cache-Control: no-cache, so edits show on the next load without any cache busting.lib/network_footer.rb appends a "More sites" line to every HTML page at serve time, built from the sites/ folders. Pages don't include it themselves. Below it goes a one-line privacy note and the visit script /_jayai/v.js.lib/access_log.rb writes one JSON line per outside request (with probe paths and scanner tools named by lib/scan_probe.rb),
and lib/visitor_beacon.rb serves the visit script and logs its browser report at POST /_jayai/v. Both go to journald and Loki.
X-Forwarded-For is trusted only from 127.0.0.1 and the edge (10.88.0.1). Visits through the edge have no country yet; ngrok used to add it.
See Visitor and scan logging.edge/Caddyfile, installed as /etc/caddy/Caddyfile on the droplet) sends every request to port 8080 over the tunnel.
Certificates come from Let's Encrypt on demand, but only for names under the domains in its allow-list./radio* to radio (8100) and /jellyfin* to Jellyfin (8096);
www.ruby.im /grafana* and /live*, and bada.im (opencode, all but its top-level articles) behind the Authelia passkey sign-in at auth.ruby.im and auth.bada.im.run_ngrok.sh, .ngrok-policy.yml, extra-endpoints.yml, edge-rules.yml) are still here but unused.| Site | What it is |
|---|---|
| jaycorp.us | JayCorp's home: how we work, research pages on AI harnesses and graph engineering, and this handoff page. |
| www.ruby.im | Engineering notes for people and coding agents: why Ruby, testing, identity, services, secrets, observability, hardware. Publishes AGENTS.md. Hosts the /live app. |
| aol.pm | Security research notes: how systems fail and how to defend what we run. |
| bada.im | Adversarial research on ML models and agents, including a "hacking with AI" page. |
| www.michigan.im | Michigan events calendar, rendered from events.yml by static_www/bin/michigan-calendar. |
_default | "No site here yet" page for hosts with no folder, such as yohohoho.com today. |
Phone monitoring with local AI, at www.ruby.im/live (access token required).
A phone streams camera and mic over a WebSocket to a Falcon server on 10.88.0.2:8090 (unit live);
ffmpeg splits the stream into clips and frames, and scripts in analyzers/ transcribe audio with whisper.cpp
(unit whisper, port 8178) and describe frames with Ollama (unit ollama, port 11434).
Sessions are saved under apps/live/sessions/, and bin/summarize writes an AI summary per session
through lib/llm.rb (LIVE_LLM=ollama by default, anthropic as the second provider).
Details: apps/live/README.md.
New apps go in apps/<name>/ with their own Gemfile, README and user systemd unit, bound to 127.0.0.1 on a port from config. To publish one, bind it to 10.88.0.2 and add a route in edge/Caddyfile.
OpenTelemetry into a local Grafana stack, all on 127.0.0.1 (observability/README.md):
apps, agents ─OTLP :4317/:4318─▶ otelcol ─▶ Prometheus :9090 (metrics, 30 days)
host + GPU metrics, journald ──▶ ─▶ Loki :3100 (logs, 30 days)
health checks ─────────────────▶ ─▶ Tempo :3200 (traces, 7 days)
Grafana :3000 reads all three
observability/services.yml is the registry of every host and service (name, unit, health URL, otel, source, host).
Add an entry in the same change that adds a service or site, then systemctl --user restart otelcol.opentelemetry-sdk, opentelemetry-exporter-otlp and opentelemetry-instrumentation-rack,
with OTEL_SERVICE_NAME set in the unit (see static_www/config.ru).observability/bin/check is the end-to-end smoke test.http://10.88.0.2:3000/grafana/, dashboards "JayAI overview" and "Site visitors and scans". From elsewhere, www.ruby.im/grafana behind the passkey sign-in.secrets/lib/secrets.rb (Secrets.fetch("NAME")) or secrets/bin/secret get NAME.
secrets/bin/secret names lists names, never values..env (mode 600, git-ignored). Bitwarden Secrets Manager is the second provider, picked with JAYAI_SECRETS=bitwarden..env.example with names and empty values. Never print, log, commit or paste a secret, and never type a person's password or token yourself; ask them to put it in .env.~/.config/ngrok/ngrok.yml, the live app's .token); see secrets/README.md.static_www/sites/<domain>/. Plain HTML with inline CSS, matching the site's existing pages
(copy the <style> block, light and dark colors, from the home page). No build step.static_www/bin/changelog <domain> "What changed"It inserts a dated line at the top of
sites/<domain>/changelog.html, creating the page if needed.
Every www.ruby.im change needs one; every other site keeps one too.curl -sI https://<domain>/<page>.html
@ and * A records at 174.138.65.239 (Name.com; edge/bin/dns add <domain> '*' with the token from ~/.config/environment.d/namecom.conf).
For a new domain, also add it to the allow-list in edge/Caddyfile and copy that to the droplet.static_www/sites/<domain>/index.html. It's live as soon as DNS resolves; Caddy gets the certificate on the first visit.
Use the www. name for the folder if that's the canonical host.static_www/bin/changelog <domain> "Launched the first version of <domain>.", and link /changelog.html from its footer.observability/services.yml (unit static-www.service, health https://<domain>/), then restart otelcol.curl -sI https://<domain>/ returns 200 with a valid certificate.systemctl --user status static-www live whisper ollama systemctl --user status otelcol prometheus loki tempo grafana gpu-metrics ssh edge 'systemctl status caddy authelia wg-quick@wg0' ssh edge 'journalctl -u caddy -f' scp edge/Caddyfile edge:/etc/caddy/ && ssh edge 'caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy' observability/bin/check ruby static_www/test/network_footer_test.rb
Provided as is, for information only, with no warranty and no liability. Not legal or professional advice. Security and radio material is for authorized, lawful use only. Full disclaimer.
Visits are logged on our own server: IP address, browser and device details, and pages viewed. We use them to understand traffic and spot attacks, and never share or sell them.