| name | nixos-config |
| description | nixos-config 오퍼레이터의 운영 면 — oracle/nuc/laptop/thinkpad 멀티 디바이스 NixOS + oracle에 사는 OpenClaw 봇 런타임을 실제로 손볼 때. AGENTS.md가 '현재 상태', NEXT.md가 '할 일', ROADMAP.md가 '이력'을 담는다면 이 스킬은 그 문서들이 front-load 못 하는 운영 반사신경을 담는다: 자리(디바이스) 인식 먼저, run.sh 엔트리, oracle/openclaw 분리 원칙, rebuild/rollback, OpenClaw 업그레이드(Dockerfile FROM 한 줄 + doctor read-only), 공개면 변경 시 서빙 URL 전수 검수(caddy 8-세트 + CF Tunnel + Tailscale Serve), 봇 스킬 심볼릭 배포, 커밋/스탬프/git-hooks 규율. 트리거: 'nixos-config', 'nixos-rebuild', 'rebuild', 'switch', 'rollback', 'flake update', 'oracle', 'openclaw', '봇 업그레이드', 'caddy', 'claw', 'control ui', 'geworfen', 'authelia', 'agenda 안돼', '디바이스', 'run.sh', '롤백', 'nixos 정리', 'tailscale', '터널', '안드로이드 앱', '앱이 안 붙어', 'trustedProxies', '403', '페어링'. |
| user_invocable | true |
nixos-config — 오퍼레이터의 운영 면
Repo: ~/repos/gh/nixos-config (심볼릭 → /home/junghan/nixos-config, 같은 real dir).
멀티 디바이스 NixOS(oracle/nuc/laptop/thinkpad) + oracle에 사는 OpenClaw 봇 런타임을
운영하는 자리. AGENTS.md(현재 상태)·NEXT.md(할 일)·ROADMAP.md(이력)이 무엇/언제를
담는다면, 이 스킬은 그 문서들이 못 담는 매번 다시 당하는 운영 반사신경을 담는다.
⚠️ 이건 generic NixOS 문서가 아니다. 이 repo 오퍼레이터의 핸드북이다.
실사용자(가족 포함)가 oracle 봇에 의존한다 — oracle 작업은 service reliability 작업이다.
0. 자리 인식 먼저 (correctness starts here)
어느 디바이스인지 모르면 아무것도 하지 마라. SessionStart hook이 device=/time_kst=를 준다.
안 보이면:
cat ~/.current-device
TZ='Asia/Seoul' date '+%Y%m%dT%H%M%S'
run.sh normalization: oracle-nixos → oracle (첫 - 앞 토큰 = flake profile 이름).
핵심 분리 원칙: oracle이 아니면 OpenClaw를 볼 필요가 없다. oracle/openclaw 작업이 아니면
ORACLE.md를 열지 마라 — AGENTS.md만으로 충분하다.
1. 문서 라우팅 — 뭘 열까
| 작업 맥락 | 펼칠 문서 |
|---|
| oracle 디바이스 또는 OpenClaw 관련 | ORACLE.md (ownership·model routing·env/secret·업그레이드·함정) |
| thinkpad 로컬 AI (Ollama) | THINKPAD.md |
| nuc/laptop 일반 NixOS | AGENTS.md + 표준 NixOS 흐름 |
| OpenClaw 함정 카탈로그 | docs/openclaw-gotchas.md |
| 지금 무슨 상태인가 | AGENTS.md (날짜 박히면 → ROADMAP로 이관 신호) |
| 앞으로 할 일·후속 검증 | NEXT.md (끝난 항목 지우고 새 후속 추가) |
| 어떻게 여기까지 왔나(버전·결정 이력) | ROADMAP.md |
| 라이브 봇 런타임 SSOT | ~/openclaw/ (config/auth/workspaces — 커밋 금지) |
2. run.sh — 오퍼레이터 엔트리
사람+에이전트 공용 인터페이스. 이미 run.sh 경로가 있는 작업이면 중복 만들지 말고 확장하라.
범위: flake update, rebuild/switch/rollback, cleanup, Oracle service helper, OpenClaw
tunnel/restart/status/pairing, skill deploy(k)). 상세 oracle helper는 ORACLE.md §7.
br 쓰지 마라. rigid tracker 대신 agenda 스탬프를 쓴다(이 repo 선호).
3. 표준 rebuild / rollback (nuc/laptop/thinkpad)
sudo nixos-rebuild switch --flake .#<profile>
문제 시 rollback: run.sh 또는 sudo nixos-rebuild switch --rollback. NixOS는 generation이
롤백 표면이다. 디스크 여유 부족하면(oracle 특히) 정리부터 — run.sh C) prune.
4. oracle / openclaw 작업 (safety-critical)
라이브 truth = ~/openclaw/. 공개 백업/레퍼런스 = docker/openclaw/, docker/*. 절대
secret/auth를 공개 repo로 새게 하지 마라. 자세한 건 ORACLE.md. 자주 쓰는 반사신경만:
- 업그레이드는 두 종류다 — 먼저 어느 쪽인지 판정하라.
- 패치/마이너 (
X.Y-1 → X.Y-2 등) = Dockerfile FROM 한 줄 bump: ~/openclaw/Dockerfile +
docker/openclaw/Dockerfile 둘 다 ghcr.io/openclaw/openclaw:X → :Y + 업글 로그 주석 →
재빌드 → up -d --force-recreate. 플러그인은 stock(번들)이라 자동으로 따라옴.
- 메이저 hop (7.1 → 8.1 같은 한 달치) = 한 줄 bump가 아니다. 새 이미지는 옛 state를 거부한다
(agent DB v1 → v19). 게이트웨이를 끄고 오프라인으로 마이그레이션한 뒤 올린다:
백업 → 정지 →
doctor --fix 직렬 반복(lease를 잃으면 남은 DB를 건너뛴다) → 기동 → 검수.
실측 39분. 절차·함정 전문 = docs/openclaw-gotchas.md "8.1 컷오버" (2026-08-31).
업글 후 --fix 금지 → 2026-08-31 폐기. 그 금지는 gemini가 google-gemini-cli/였을 때의
방어였고, 8/27 Copilot 이관으로 겨눌 대상이 사라졌다. 8.1부터 doctor --fix는 필수다 —
agent DB 스키마를 올리는 건 doctor뿐이다. 단 반드시 게이트웨이를 끈 상태에서(라이브 writer가
있으면 "stopped-writer maintenance" 로 전부 skip), 그리고 끝난 뒤 6봇 prefix를 재확인한다.
- 6봇 모델 prefix 전수 재확인 (gemini는
github-copilot/gemini-3.7-flash — google* 아님),
claude-cli 봇 Anthropic auth GREEN, memory 4096d, fallbacks 빈 상태,
catch-all 1번(8.1부터 modelPolicy.allow 배열 첫 항목 = openai/gpt-5.6-terra).
- 모델은 claude-cli 봇이면 카탈로그 등록만으로 태운다 —
agentRuntime claude-cli는 구독
API에서 모델을 동적 해결(예: Sonnet 5 = anthropic/claude-sonnet-5 + primary, 재빌드 불필요).
서빙 미보장 모델을 primary로 박지 말 것(auto-fallback catch-all이 정체성 훼손).
- gemini는 DOWN 아니다 (2026-08-27~) — 서빙 레일 =
github-copilot/gemini-3.7-flash(Copilot 구독).
옛 "403 → agy 이관 대기 → DOWN 유지" 반사신경은 화석이다. Google 구독·gemini-cli·agy 안 쫓는다.
api-key(google/) 폴백은 여전히 금지 — 그 키는 나노바나나 이미지 전용.
5. 반사신경 — 다시 당하지 말 것
-
공개면 변경 = 서빙 URL 전수 검수: docker/caddy/Caddyfile 이든 gateway 설정이든
공개면을 건드렸으면 caddy 8-세트 + 밖의 3개를 한 번에 확인한다. 하나만 보고 넘기지 마라.
(docs/openclaw-gotchas.md "caddy 변경 = 8-세트 검수" 참조)
for h in agenda analytics ax claw comments forge ha map; do
printf '%-28s %s\n' "$h" "$(curl -sS -m 12 -o /dev/null -w '%{http_code}' https://$h.junghanacs.com/)"
done
curl -sS -m 12 -o /dev/null -w 'aionsclubs %{http_code}\n' https://aionsclubs.org/
curl -sS -m 10 -o /dev/null -w 'tailnet %{http_code}\n' https://oracle.tailb0e905.ts.net/
기대값을 알고 봐야 한다 — 200 이 아닌 게 셋이고 전부 정상이다 (2026-09-08 실측):
| 기대 | 호스트 | 왜 |
|---|
| 200 | agenda · analytics · ax · forge · ha · aionsclubs · tailnet | |
| 302 | claw · map | authelia forward_auth 게이트. 설계대로 |
| 404 | comments | remark42 는 루트에 페이지가 없다. 장애 아님 — /api/v1/ping 이 pong 이면 정상 |
caddy 밖의 두 경로(aionsclubs.org CF Tunnel, oracle.tailb0e905.ts.net Tailscale Serve)는
caddy 를 안 타므로 Caddyfile 검수에서 빠지기 쉽다. 세트에 넣어라.
-
/health 200 은 게이트웨이가 멀쩡하다는 증거가 아니다: /health 는 무인증 라우트라
proxy attribution·토큰 검사를 건너뛴다. 그래서 /health 는 200 인데 / 는 403 인
비대칭이 나오고, 이 비대칭 자체가 "네트워크·프로세스는 살아있고 인증 계층에서 막혔다"는
진단이다. 앱/UI 가 못 붙을 때 /health 만 보고 "게이트웨이 정상"이라 결론내지 마라 —
인증이 걸리는 / 를 같이 찍고, 403 이면 본문(JSON type)을 읽어라. 본문이 원인을
이름으로 말해준다(proxy_attribution_required 등).
-
도커 네트워크가 갈리면 gateway.trustedProxies 가 조용히 화석이 된다: gateway 는 두
네트워크에 붙어 있다 — proxy(172.18, caddy 경로) 와 openclaw_default(172.19, 호스트
루프백 포워딩 = tailscale serve·SSH 터널). 네트워크가 재생성되면 서브넷이 밀리는데
trustedProxies 는 안 따라간다. 설정 파일은 그대로인데 그 아래 땅이 움직인 화석이다.
추측하지 말고 컨테이너가 보는 소스 IP 를 실측한다(/proc/net/tcp 를 폴링하며 요청을 흘린다 —
전문은 gotchas). 넓히지 말고 /32 로 좁게 더하고, config 변경이므로 restart 로 충분.
⚠️ 대가를 알고 하라: 도커 NAT 는 tailscale serve 와 호스트 루프백을 같은 IP 로 뭉갠다.
그 IP 를 신뢰하는 순간 run.sh t) SSH 터널로 Control UI 를 보던 길이 403 이 된다. /32 로도
분리되지 않는다. 대체는 tailnet URL 직행(thinkpad 도 tailnet 에 있다).
-
claw.junghanacs.com = 인증 뒤에 원격 셸이 있는 유일한 vhost: OpenClaw Control UI 공개면.
자물쇠 3겹(Authelia forward_auth → gateway token → device pairing)을 전부 유지한다.
gateway.auth.mode를 trusted-proxy로 바꾸지 마라 — gateway가 proxy 도커 네트에 붙어
있어 같은 네트 컨테이너가 18789로 직결(Authelia 우회)하므로, 그 순간 우회가 곧 인증 우회가
된다. Authelia claw 규칙은 bypass → operator one_factor → deny catch-all 3단(default가
one_factor라 catch-all 없으면 family가 통과). WS 재연결이 조용히 죽으면 F5.
(docs/openclaw-gotchas.md claw 항목)
-
ax.junghanacs.com = 첫 static vhost(관리 대상): 기존 6개는 reverse_proxy지만 ax는
백엔드 없이 caddy file_server가 /srv/ax(호스트 docker-data/ax ro 마운트)를 직접 서빙.
web root는 junghan0611 repo apply/ax make publish가 채운다(재시작 불요, 즉시 라이브·leak
gate 유일 관문). compose 볼륨 추가/제거는 restart 아니라 up -d --force-recreate.
향후 umami/remark는 정본에 스니펫으로(caddy 주입 금지). ax 요청은 이 레인이 대응.
-
agenda 000 ≠ caddy: agenda.junghanacs.com(geworfen)은 호스트 emacs server 데몬에
의존한다. 데몬 hang이면 caddy와 무관하게 agenda 000. emacsclient -s server --eval '(+ 1 1)'
타임아웃이면 데몬이 근인. 복구는 geworfen 담당자에게 entwurf 핸드오프 — 호스트 데몬을
nixos-config에서 직접 만지지 마라.
-
Caddyfile 편집 후 reload 아니라 docker restart caddy: single-file bind-mount inode
함정 — atomic rename이 새 inode를 만들어 caddy reload가 "unchanged"로 무시한다. 재시작 후
docker exec caddy grep <domain> /etc/caddy/Caddyfile로 컨테이너가 새 내용을 보는지 확인.
-
봇 스킬은 심볼릭 배포 — workspace 스킬을 sibling repo SSOT(예: butlercli)로 절대 심볼릭.
봇이 고친 게 SSOT로 흐르고 SSOT 수정이 봇에 즉시 반영(redeploy 불필요). 이중 마운트 덕에
/home/junghan/... 절대경로가 host·container 양쪽 resolve. (NEXT.md "스킬 심볼릭 배포")
-
디스크 보수적으로 — oracle storage 빠듯. 업글 사이클마다 dangling image + build cache
누적 → run.sh C) 정기 prune (docker system df 명목치 ≠ 실 회수량, builder prune이 본 회수원).
-
restart vs recreate — "무엇을 바꿨느냐"로 가른다 (매번 까먹는 지점):
- env/mount 변경 →
up -d --force-recreate. docker compose restart는 기존 env 재사용, recreate만 새 env·볼륨 픽업.
- config 파일(
~/openclaw/config/openclaw.json) 변경 → docker compose restart openclaw-gateway로 충분. restart도 gateway 프로세스·manager cache·Node compile state를 콜드 리셋한다. config-only에 recreate는 과하다(느리고 불필요하게 더 깊은 콜드). recreate는 env/mount일 때만.
-
restart/recreate 후 memory prewarm — 콜드 첫 memory_search는 세션 전수 스캔(glg 808 files/211MB, ARM)으로 15s 하드타임아웃(memory-core tools.ts 하드코딩, config로 못 늘림)에 걸릴 수 있다. gateway healthy 뒤 각 봇에 openclaw agent --agent <id> --session-key "agent:<id>:prewarm-$(date +%s)" --message '기억 검색' --timeout 60을 (텔레그램 발송 없이) 한 번 돌려 봇이 콜드를 대신 맞게 하면 실제 유저는 웜만 만난다. 1차 방어는 인덱스 정비(dirty:no 유지 — family-turns 스킬 memory 섹션): 정비돼 있으면 콜드도 15s를 버틴다(2026-07-16 실측, 정비 후 콜드 3연속 성공). upstream PR로 timeout 늘리는 건 안 한다 — 운영으로 커버(GLG 결정).
6. 커밋 / 릴리즈 규율
- 커밋 전
commit 스킬, 릴리즈 전 tag-release 스킬. 로그 깨끗하게(“Generated with Claude”·
Co-Authored-By 금지). 에이전트는 commit workflow에서만 커밋, push는 GLG.
- git-hooks 안전벽 (global
core.hooksPath): 공개 repo(junghan0611/*·junghanacs/*)
added line의 정체성 용어 + 모든 repo의 secret를 차단. nixos-config는 공개 repo다 —
실제 이메일/비번/토큰을 커밋 다이어그램에 넣지 마라(실파일은 gitignore, 템플릿은 플레이스홀더).
AGENT_ALLOW_UNSAFE_COMMIT=1·--no-verify·core.hooksPath 변경 금지(GLG 명시 예외만).
- 막히면: hook 출력 읽고 → 다이어그램에서 용어/secret 제거(private은
PRIVATE.md/.env.local
또는 플레이스홀더) → 재스테이지 → 재시도. false positive 같으면 멈추고 hook 출력 그대로 보고.
- push 후 agenda 스탬프(commit/release 스킬 규약).
7. 영속 사실이 사는 곳 (썩는 문서 말고)
| 사실 | 집 |
|---|
| 현재 운영 상태 | AGENTS.md |
| 다음 할 일·후속 검증 | NEXT.md (완료분은 지우고 ROADMAP으로 흘림) |
| 버전·업그레이드·운영 결정 이력 | ROADMAP.md |
| oracle/openclaw 핸드북 | ORACLE.md |
| 반복되는 운영 함정 | docs/openclaw-gotchas.md |
| 라이브 봇 런타임 change history | ~/openclaw/README.md (커밋 안 되는 SSOT) |
이 스킬 자체(.claude/skills/nixos-config/SKILL.md)는 양쪽 하네스가 읽는다: Claude Code는
.claude/skills/를 네이티브로, pi는 .pi/settings.json의 ["../.claude/skills"]로. 스킬을
고치면 두 하네스에 동시에 반영된다 — 커밋해서 다른 기기로도 펼친다.