<!-- canonical: https://docs.axelabs.ai/ops/host-setup -->
<!-- source: content/ops/host-setup.mdx -->

---
title: macmini host setup
description: axelabs.ai customer macmini 전체 setup. 13 layer step-by-step + 본 머신 (axe-macmini) 현재 inventory + 함정.
---

# macmini host setup

> **본 페이지의 위치**: 비전 = "macmini 10대 × customer 직원 1-2명/customer 시스템 개선" ([D-dev-platform-1](/ops/decisions)). 각 customer 머신 셋업의 SSOT. 신규 customer onboard 시 본 페이지 위에서 자동화 CLI ([B-axe-host-bootstrap-cli](/ops/backlog)) 가 진행할 task의 인간이 읽을 reference.
>
> **운영자 ritual**: 본 페이지 변경 시 = 운영 실패 / 함정 발견 / 신규 layer 추가의 trigger. 모든 macmini 변경의 SSOT.

13 layer + 본 머신 (axe-macmini) 의 현재 값을 짝지어 정리.

## Layer 0 — Hardware

- Mac mini (Apple silicon 권장 — M1/M2/M3/M4). 외장 SSD 1개 (backup tier B).
- 본 머신 = AXEs-Mac-mini (Soohun 운영).

## Layer 1 — macOS + system tunables

| 항목 | 값 (본 머신) | 신규 setup |
|---|---|---|
| macOS version | 26.3.1 (Build 25D771280a) | 26.0+ 권장 |
| Remote Login | `on` (sshd active port 22) | `sudo systemsetup -setremotelogin on` |
| `kern.tty.ptmx_max` | 511 (default) | leak 함정 — Claude Desktop app 의 PTY leak 으로 한도 도달 가능. `sudo sysctl -w kern.tty.ptmx_max=2047` + launchd plist 영구화 ([B-axe-pty-max-launchd](/ops/backlog)) |
| `launchctl limit maxfiles` | `256 / unlimited` | 낮음 — heavy MCP/Docker 운영 시 부족할 수 있음 |
| `launchctl limit maxproc` | `5333 / 8000` | 충분 |
| FileVault | (확인 필요) | 권장 ON (외장 SSD backup 도 암호화) |
| Time Machine | (선택) | restic 백업이 primary, TM 은 보조 |

## Layer 2 — macOS users + dev group ([D-dev-platform-1](/ops/decisions))

본 머신:
- `axe` (UID 501) — ai@ 봇 전용 (자동화/cron/MCP container 운영자)
- `soohun.kang` (UID 502) — 운영자 본인 dev 계정
- `taehun.kang` (UID 503) — axev 대표 dev 계정
- group `dev` (GID 501) — 위 3명 모두 가입

신규 setup:
```bash
sudo bash <<'OPSCRIPT'
set -e
dseditgroup -o create -r "AXE Developers" -n . dev 2>/dev/null || true
dseditgroup -o edit -a axe -t user dev
OPSCRIPT
# user 추가 (각 사람마다)
sudo sysadminctl -addUser <shortname> -fullName "<Full Name>" -password <random> -shell /bin/zsh -home /Users/<shortname> -GID 501
sudo createhomedir -c -u <shortname>
sudo dseditgroup -o edit -a <shortname> -t user dev
sudo install -d -o <shortname> -g dev -m 700 /Users/<shortname>/.ssh
sudo install -m 600 -o <shortname> -g dev /dev/null /Users/<shortname>/.ssh/authorized_keys
# password 는 Vaultwarden item 'axe-macmini local: <shortname>' 에 등재
```

repo 공유 — `/Users/axe/{blueprint,frame,hive,axelabs-docs}` = group `dev` + setgid + git `core.sharedRepository=group`:
```bash
for d in blueprint frame hive axelabs-docs; do
  sudo chgrp -R dev "/Users/axe/$d"
  sudo find "/Users/axe/$d" -type d -exec chmod g+rwsX {} +
  git -C "/Users/axe/$d" config core.sharedRepository group
done
```

## Layer 3 — sudoers (`/etc/sudoers.d/ai-axe`, D-dev-platform-1)

ai@ subprocess 가 NOPASSWD 로 실행 가능한 좁은 명령 set:

```
Cmnd_Alias AI_USER = /usr/sbin/dseditgroup, /usr/sbin/sysadminctl, /usr/bin/dscl ., /usr/sbin/createhomedir
Cmnd_Alias AI_SSH = /bin/launchctl kickstart -k system/com.openssh.sshd, /usr/sbin/sshd -t
Cmnd_Alias AI_FS = /usr/bin/install, /usr/sbin/chown, /usr/bin/tee /Users/*, /usr/bin/tee /etc/ssh/sshd_config*, /bin/mv /etc/ssh/sshd_config*
axe ALL=(ALL) NOPASSWD: AI_USER, AI_SSH, AI_FS
```

설치: `sudo install -m 440 /dev/stdin /etc/sudoers.d/ai-axe <<<'<content>'` + `sudo visudo -cf /etc/sudoers.d/ai-axe`.

**금지**: `tee` 의 wildcard 가 `/Users/*` 또는 `/etc/ssh/sshd_config*` 로 제한 — `tee` 자체 무제한 NOPASSWD = root takeover.

## Layer 4 — SSH 강화 ([D-dev-platform-1](/ops/decisions))

`/etc/ssh/sshd_config.d/200-axe-hardening.conf`:
```
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
```

검증: `sudo sshd -t` → reload: `sudo launchctl kickstart -k system/com.openssh.sshd`.

**Layer 1 보호** = Cloudflare Tunnel + Zero Trust (Microsoft Entra SSO 게이트). host SSH 는 layer 2 = key-only.

## Layer 5 — Tailscale + Cloudflare Tunnel

| 항목 | 본 머신 |
|---|---|
| Tailscale IP | 100.127.210.30 (host: axe-macmini) |
| Cloudflare tunnel UUID | `d8efecdd-2c3f-42de-9925-501433e21394` (container `axelabs-tunnel`) |
| 외부 hostname (axe.axelabs.ai) | `/blueprint/mcp` `/frame` `/hive` `/vault` `/index` (catch-all → blueprint-app) |
| 추가 hostname | `ssh.axe.axelabs.ai` (host SSH, Zero Trust App) [D-dev-platform-5] |
| Cloudflare Zero Trust App | `b903d8cd-...` (Self-hosted, Email policy `*@axellc.com`) |
| Zone | `axelabs.ai` (zone id `850053fec240de9d83f165cd3e6f10a5`) |
| Account | `6e6bbb05187c8bfea52d63fa1a06460b` |

신규 customer 머신:
- Tailscale install + tailnet 합류 (Microsoft Entra SSO)
- 새 cloudflared tunnel UUID 발급 (Cloudflare 대시보드)
- DNS CNAME `<customer>.axelabs.ai` → tunnel hostname
- Zero Trust App `ssh.<customer>.axelabs.ai` (선택 — 외부 dev access 시)

## Layer 6 — Microsoft Entra ID

| 앱 | 용도 |
|---|---|
| Frame MCP | 회계 backend 접근 (PKCE public client) |
| Hive MCP | HR backend 접근 (PKCE public client) |
| Blueprint MCP | platform MCP at apex (PKCE public client) |
| Vaultwarden | password vault SSO (OIDC confidential, secret 보관) |
| Blueprint Web | web UI NextAuth (OIDC) |

axe tenant ID = `122fb574-7efa-476a-95b6-bee81bce2cce` (axellc.com).

신규 customer = 자기 tenant 별도. 본 머신의 5 앱 패턴 미러. 자동 등록: [ops/runbook/customer-onboarding](/ops/runbook/customer-onboarding).

## Layer 7 — Vaultwarden ([D-ops-11](/ops/decisions) Timshel fork, [D-ops-24/26/27/29](/ops/decisions) SSO)

- 컨테이너: `axe-vaultwarden` (host `axe.axelabs.ai/vault`)
- bw CLI: 2025.7.0 (Timshel fork schema 호환 — [D-ops-28](/ops/decisions))
- SSO: Microsoft Entra (SSO_AUTHORITY single-tenant)
- 환경 vars: `SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION=true`, `SIGNUPS_ALLOWED=true`, `SSO_ONLY=false`, `ORGANIZATION_INVITE_AUTO_ACCEPT=true`
- org: AXE (`0c5d8bbd-ad85-42b4-8b8a-2849031981b1`)
- collections: Default · frame-jwt-axec · frame-jwt-axev · frame-jwt-operators

신규 customer = vault 인스턴스 별 (또는 same vault 다른 org). `axe customers vault-bootstrap <id>` ([D-dev-platform-3](/ops/decisions)) 가 3 collection 자동.

**Vault session 관리** ([D-dev-platform-6](/ops/decisions)): `axe vault unlock` 운영자 1회 → macOS Keychain (`axe.vault.session` / `ai@axellc.com`) 에 BW_SESSION push. ai@ subprocess 가 `_vault_env()` 로 자동 fetch. BW_SESSION 채팅 회신 패턴 폐기.

## Layer 8 — Docker stacks

| Container | 용도 |
|---|---|
| axe-tunnel · axelabs-tunnel · artemis-tunnel | Cloudflare cloudflared (외부 ingress) |
| axe-caddy · axe-blueprint-mcp-proxy · axe-frame-proxy · axe-hive-proxy · axe-vault-caddy | reverse proxy (Caddy 2) |
| axe-vaultwarden | Vaultwarden (password vault) |
| blueprint-app · blueprint-postgres · blueprint-mcp-blue · blueprint-mcp-green | Blueprint platform |
| frame-mcp-blue · frame-mcp-green · frame-postgres · frame-worker | frame 회계 |
| hive-mcp-blue · hive-mcp-green · hive-postgres | hive HR |
| axelabs-docs | docs.axelabs.ai (Nextra) |
| axelabs · axe-console-app | platform UIs |
| artemis-api · artemis-web · artemis-db · artemis-collector_* | artemis (filings/news) |
| mysrt-app · mysrt-nginx · mysrt-postgres | mysrt SRT 자동 예매 |

Docker networks: `artemis_default` · `blueprint_default` · `frame_default` · `hive_default` · `mysrt_default` · `vault_default` · `console_default`.

신규 customer = 동일 stack 일부 (frame + hive + blueprint + vault + cloudflared 가 핵심). `axe customer deploy <id>` ([B-dev-platform-customer-deploy](/ops/backlog)) 자동화 예정.

## Layer 9 — launchd jobs

본 머신 활성:
- `com.cortex.frontend` / `com.cortex.backend` / `com.cortex.cloudflared` (Cortex)
- `com.artemis.filings` / `collectors` / `index` / `index_intraday` / `reporters` / `logrotate` / `backup`
- `com.mysrt.app` (간접 — mysrt container)
- `com.axe.blueprint-usage-digest` (Teams 일일 사용량 09:00 KST)
- `com.axe.blueprint-skills-sync` (git webhook → ~/.claude/skills/)
- `com.axe.blueprint-rebuild-watcher` (rebuild trigger file polling)
- `com.axe.health-check` (매분 7 도메인 + bind-mount probe)
- `com.axe.secret-check` (09:00 KST vault drift)
- `com.axe.operator-alert-notify` (docker logs tail → osascript notification)
- `com.axe.backup.local` (외장 SSD restic)
- `com.axe.restore-drill` (분기별 restore 검증)
- `com.axe.ring.push` (frame data ring push)
- `com.axe.console.refresh` (axe-console-app refresh)

신규 customer = 본 머신 set 의 subset (customer 별 deploy script 가 깔아줌).

## Layer 10 — CLI tools

| 도구 | 설치 | 비고 |
|---|---|---|
| Homebrew | `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` | macOS 표준 |
| `axe` CLI | `/Users/axe/.axe/bin/axe` (local Python single file, git 미관리) | 별도 git repo 화 backlog |
| `bw` CLI | `brew install bitwarden-cli@2025.7.0` ([D-ops-28](/ops/decisions) 다운그레이드) | Timshel fork 호환 |
| `cloudflared` | `brew install cloudflared` | Tunnel + Access SSH |
| `gh` CLI | `brew install gh` + `gh auth login --with-token` | `axe-labs-ai` PAT (Vault) — [D-dev-platform-4](/ops/decisions) |
| `docker` | Docker Desktop for Mac | file sharing + memory 8GB+ |
| `python3` | macOS 기본 (또는 `brew install python@3.14`) | axe CLI venv 자동 |
| `pnpm` / `node` | `brew install pnpm` (node 자동) | Blueprint dev |

git config (global):
```bash
git config --global user.email ai@axellc.com
git config --global user.name "AXE Labs AI"
```

## Layer 11 — Git repos + GitHub

본 머신:
- `/Users/axe/blueprint, frame, hive, axelabs-docs` (4 core)
- `/Users/axe/artemis, cortex, axelabs, mysrt, kolon-discussion, distributa, magnet, stream` (별 service)
- `/Users/axe/.axe/` — local-only (git 미관리)

GitHub org = `axelabs-ai` (Free plan, [D-dev-platform-5](/ops/decisions)). 4 core repo 에 CODEOWNERS skeleton + PR template ([B-dev-platform-cf-zerotrust](/ops/backlog) 완료).

신규 customer = 자기 GitHub user 발급 + `axelabs-ai/<customer>-developers` team 가입 (`axe customers github-team <id>` — [D-dev-platform-3](/ops/decisions)).

## Layer 12 — OneDrive sync

운영자 Microsoft OneDrive client 설치 (Soohun 개인 + 공유). symlink 로 `~/OneDrive` → workspace 자료. Blueprint Workspace.drivePath = OneDrive 폴더 GUID.

신규 customer = 본인 OneDrive 또는 SharePoint site. Blueprint 의 trinity 모델 (Workspace × OneDrive × TeamsChat).

## Layer 13 — Backup ([D-config-14](/ops/decisions) DR cold storage)

- `axe-backup` launchd (daily) — restic snapshot
- Tier A: `blueprint-postgres` + `hive-postgres` + `customers.yaml` + Vaultwarden data dir
- Tier B: 외장 SSD rotation (사용자 manual swap)
- restore drill 분기별 (`com.axe.restore-drill`)

신규 customer = 자기 외장 SSD + 본 머신 patterns 미러.

## 신규 macmini setup 순서 (요약)

1. Layer 0-1 (Hardware + macOS) — 운영자 manual
2. Layer 2 (users + dev group) — `axe user add` ([B-dev-platform-user-add-cli](/ops/backlog)) 자동화 예정
3. Layer 3-4 (sudoers + SSH) — `axe host bootstrap` ([B-axe-host-bootstrap-cli](/ops/backlog)) 자동화 예정
4. Layer 5 (Tailscale + Cloudflare) — Tailscale CLI + `axe customers dns-placeholder <id>` ([B-dev-platform-dns-placeholder-cli](/ops/backlog))
5. Layer 6 (Entra ID) — 운영자 Azure CLI + 도메인 verify (수동)
6. Layer 7 (Vault) — `axe customers vault-bootstrap <id>` + `axe vault bootstrap`
7. Layer 8 (Docker) — `axe customer deploy <id>` ([B-dev-platform-customer-deploy](/ops/backlog)) 자동화 예정
8. Layer 9 (launchd) — `axe customer deploy` 가 install
9. Layer 10 (CLI) — `axe customer deploy` 가 brew install
10. Layer 11 (Git) — `axe customers github-team <id>` + 직원 별 collaborator
11. Layer 12 (OneDrive) — 운영자 수동
12. Layer 13 (Backup) — `axe customer deploy` 가 launchd 등재

## 함정 + 알려진 미해결

| 함정 | 결과 | 회피 / 결정 |
|---|---|---|
| `kern.tty.ptmx_max=511` 한도 + Claude Desktop PTY leak | `forkpty: Device not configured` → 새 terminal 못 열림 | Claude Desktop 재시작 또는 sysctl 늘림 + launchd plist 영구화 ([B-axe-pty-max-launchd](/ops/backlog)) |
| `launchctl limit maxfiles=256` | heavy MCP/Docker 시 fd 부족 | per-process 명령 또는 launchd plist 늘림 |
| sudoers wildcard 부정확 — `tee` 전체 NOPASSWD | root takeover | `tee` 경로 한정 (`/Users/*`, `/etc/ssh/sshd_config*`) |
| Cloudflare API token 의 scope 가 notes 와 실제 다름 | API 호출 불필요 fail | `curl /user/tokens/verify` 로 실 권한 검증 |
| Bitwarden CLI 2025.8.0+ 가 Timshel fork schema reject | bw 명령 모두 fail | 2025.7.0 pin ([D-ops-28](/ops/decisions)) |
| BW_SESSION 만료 + ai@ subprocess stdin tty 없음 | bw CLI 호출 fail | Keychain-backed session ([D-dev-platform-6](/ops/decisions)) |
| Docker bind-mount file source 손상 | container "not a directory" 거부 | file → dir 변환 + restic restore |
| Fine-grained PAT 의 Resource owner = 본인 (org 아님) | org repo 접근 시 "Could not resolve to a Repository" (404), org membership 와 무관 | 발급 시 Resource owner dropdown = **org** 선택 ([D-dev-platform-4](/ops/decisions)) |
| GitHub UI 에서 PAT regenerate → keyring stale | `gh auth status` ✓ 인데 모든 API 401 (gh CLI 가 옛 token cached) | `gh auth logout -h github.com -u <user>` + `gh auth login --with-token` 새 PAT 으로 재로그인 |
| bw CLI subprocess 호출 시 BW_SESSION env 누락 | bw 가 master password prompt → subprocess stdin pipe → hang 또는 JSON parse fail | python `subprocess.run(..., env={**os.environ, "BW_SESSION": ..., "NODE_EXTRA_CA_CERTS": ...})` 명시 전달 ([D-dev-platform-6](/ops/decisions)) |

## 관련 페이지

- [/ops/inventory](/ops/inventory) — 본 머신 자산 list (paths, ports)
- [/ops/runbook/customer-onboarding](/ops/runbook/customer-onboarding) — 신규 customer 절차
- [/architecture/auth](/architecture/auth) — Microsoft Entra + frame/hive/blueprint OAuth
- [/architecture/secrets](/architecture/secrets) — vault 정책
- [/architecture/backup](/architecture/backup) — restic + 외장 SSD
- [/ops/decisions](/ops/decisions) — 모든 D-... 결정
- [/ops/backlog](/ops/backlog) — `B-axe-host-bootstrap-cli` + `B-axe-pty-max-launchd` 등 후속
