agentleFS
ryanmacleanryanmaclean/skills/elixir-gastown-ops/SKILL.md

elixir-gastown-ops

Skill1 starsChanged 50 days agoMITView on GitHub

Use when operating, debugging, or deploying the elixir-gastown Elixir/BEAM fleet — cluster federation, release builds, worker deploys, seccomp fixes, sname/name distribution issues, rate limiter, or any gastown service on Pi workers or coordinator nodes.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 1 is distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    All nodes use sname. Check:

    line 77
  2. Very specific:

    Verify with: journalctl -k | grep "syscall=92" — should stop after fix.

    line 92
  3. Very specific:

    Must be short name only — no @host suffix:

    line 105
  4. Very specific:

    Check /etc/gastown-worker.env — RELEASE_NODE=egt, RELEASE_DISTRIBUTION=sname

    line 116
  5. Very specific:

    Check bidirectional /etc/hosts — Pi must resolve coordinator hostname AND coordinator must resolve Pi hostname

    line 117
  6. Very specific:

    Add missing entry: ansible -i inventory i9-zfs-pop,pop4090,i7-zfs-pop -b -m lineinfile -a 'path=/etc/hosts regexp=".*\s<hostname>$" …

    line 118

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Gastown Ops

Fleet Topology

RoleHostsUserService
Coordinatori9-zfs-pop, i7-zfs-pop, 7950x4090pop<user>gastown.service
Pi workersubrpi401/405/407/408/409/410/411/501/502ubuntu (ubrpi501: string)gastown-worker.service
  • BEAM cookie: GASTOWNBEAM2026
  • Node naming: egt@<hostname> — sname on all nodes
  • Coordinator Phoenix port: 4000 (i9 only, PHX_SERVER=true)
  • Workers: PHX_SERVER=false, GASTOWN_ROLE=polecat_worker
  • Repo: http://<coordinator-ip>:3001/<user>/elixir-gastown.git

Check Cluster

# Node count from i9
ansible -i ansible/inventory i9-zfs-pop -m shell \
  -a '/opt/gastown/_build/prod/rel/elixir_gastown/bin/elixir_gastown rpc "Node.list() |> inspect() |> IO.puts"'

# From a Pi worker (avoids i9 sname/name issues)
ansible -i ansible/inventory ubrpi407 -m shell \
  -a '/opt/gastown/_build/prod/rel/elixir_gastown/bin/elixir_gastown rpc "Node.list() |> inspect() |> IO.puts"'

Expected: 10 nodes (3 coordinators + 7 Pi workers, excluding self).

Build + Deploy

Full rebuild on ubrpi502 + distribute to all Pis:

ansible-playbook -i ansible/inventory ansible/22-gastown-pis.yml -v

Distribute only (skip build):

ansible-playbook -i ansible/inventory ansible/22-gastown-pis.yml \
  --limit ubrpi401,ubrpi407,... -v
# Note: no --skip-tags build — Play 1 targets ubrpi502 only; --limit to pis skips it

Rebuild coordinator on i9:

ansible -i ansible/inventory i9-zfs-pop -m shell -a '
  cd /opt/gastown && sudo -u $DEPLOY_USER git pull origin main
  # If local changes block pull:
  sudo -u $DEPLOY_USER git stash && sudo -u $DEPLOY_USER git pull origin main
  sudo -u $DEPLOY_USER bash -c "export HOME=/home/$DEPLOY_USER MIX_ENV=prod PATH=/usr/local/bin:/usr/bin:/bin; mix release --overwrite 2>&1 | tail -5"
  systemctl restart gastown
' -b

Rustler NIF missing from coordinator release:

# Build NIF manually, then rebuild release
ansible -i ansible/inventory i9-zfs-pop -m shell -a '
  cd /opt/gastown/native/gastown_nif
  sudo -u $DEPLOY_USER bash -c "PATH=/usr/local/bin:/usr/bin:/bin cargo build --release"
  NIF=target/release/libgastown_nif.so
  DEST=/opt/gastown/_build/prod/rel/elixir_gastown/lib/elixir_gastown-0.3.0/priv/native/gastown_nif.so
  mkdir -p $(dirname $DEST) && cp $NIF $DEST && chown $DEPLOY_USER:$DEPLOY_USER $DEST
' -b
# Then re-run mix release --overwrite and restart

Common Fixes

sname/name mismatch (i9 not federating)

i9 /etc/gastown.env must have RELEASE_DISTRIBUTION=sname — not name. All nodes use sname. Check:

ansible -i ansible/inventory i9-zfs-pop,pis -m shell \
  -a 'grep RELEASE_DISTRIBUTION /etc/gastown.env /etc/gastown-worker.env 2>/dev/null'

seccomp SIGSYS on coordinator (syscall 92 = chown)

ERTS dirty I/O scheduler calls chown — blocked by SystemCallFilter=~@privileged:

ansible -i ansible/inventory i9-zfs-pop -m shell -a '
  sed -i "/SystemCallFilter=~@privileged/a SystemCallFilter=chown fchown lchown fchownat" \
    /etc/systemd/system/gastown.service
  systemctl daemon-reload && systemctl restart gastown
' -b

Verify with: journalctl -k | grep "syscall=92" — should stop after fix.

:global sync storm (nodes ejecting each other)

Symptom: nodes connect then immediately disconnect, "overlapping partitions" in logs. Cause: old binary with :global.register_name in check_singleton!. Fix: distribute new tarball (commit da9fca2+), restart workers with 15s stagger:

for h in <worker-ip-1> <worker-ip-2> <worker-ip-3>; do
  ssh ubuntu@$h "sudo systemctl restart gastown-worker"; sleep 15
done

RELEASE_NODE wrong format

Must be short name only — no @host suffix:

RELEASE_NODE=egt          ✅
RELEASE_NODE=egt@ubrpi407 ❌  (invalid for sname)

ubrpi501 chown failure in playbook

gastown_user must be {{ ansible_user }}, not hardcoded ubuntu. ubrpi501 uses ansible_user=string.

Worker not joining cluster (node not in Node.list)

  1. Check /etc/gastown-worker.env — RELEASE_NODE=egt, RELEASE_DISTRIBUTION=sname
  2. Check bidirectional /etc/hosts — Pi must resolve coordinator hostname AND coordinator must resolve Pi hostname
  3. Add missing entry: ansible -i inventory i9-zfs-pop,pop4090,i7-zfs-pop -b -m lineinfile -a 'path=/etc/hosts regexp=".*\s<hostname>$" line="<ip> <hostname>" state=present'
  4. Restart worker: sudo systemctl restart gastown-worker

Rate Limiter

HTTP endpoint on i9: GET http://<i9-ip>:4000/api/llm-permit → {"ok":true,"wait_ms":N} Release endpoint: POST http://<i9-ip>:4000/api/llm-release Zeroclaw config on Pis: /etc/gastown-worker.env → irclaw config.toml has pre_request_url. Deploy irclaw rate limit config: ansible-playbook -i inventory ansible/23-irclaw-rate-limit.yml

Key Playbooks

PlaybookPurpose
22-gastown-pis.ymlBuild on ubrpi502 + deploy to all Pis
23-irclaw-rate-limit.ymlSet pre/post_request_url on Pi irclaw configs
24-dd-fix-gastown-pis.ymlFix DD tags, process check, DogStatsD on Pis

Hostnames vs Node Names

Pi hostnames drop leading zeros: ubrpi401 → hostname ubrpi41, node :egt@ubrpi41. Exception: ubrpi410, ubrpi411, ubrpi501, ubrpi502 keep full name.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.