feat: self-hosted 모드 (#41) — 내 NAS/PC + 공인 IP로 발행 - #42
Conversation
Storage backend abstraction (S3Backend/LocalFsBackend), self-hosted mode derivation via serveRoot, host resolution (config host > public-IP auto-detect + warning), _meta/index snippet (Caddy default, nginx opt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hange) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ppet) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…service) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route deprovision on resolveConfig({}).mode so HOSTDOC_SERVE_ROOT env config
is honored (was reading loadConfig().serveRoot, missing the env override).
Also: symmetric removeService darwin exists-guard, dedup security warning,
drop dead test imports.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2 완료 —
|
Windows arm of Phase 3: extend `setup --install` to register Caddy as a boot-surviving Windows service via WinSW (caddy has no native SCM stub — caddyserver/caddy#4670 PR unmerged, so sc.exe risks error 1053). Mirrors Phase 2's launchd/systemd installer seam; renames sudoCommands → privilegedCommands (platform-neutral). x64 first; arm64 TBD at impl. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WinSW-based Windows service integration for setup --install / deprovision. Verified constants: WinSW v2.12.0 WinSW-x64.exe (no arm64 asset), caddy windows download is a bare .exe. Tasks: platform detect, winswXml, sudo→ privileged rename, windows leaf helpers, windows service control, command routing + platform-aware CLI headers. All I/O behind injected seams; CI verifies windows paths on Linux via explicit plat injection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3 (Windows) 완료 —
|
On a Windows checkout (core.autocrlf=true, no .gitattributes) the shebang .mjs wrapper scripts get CRLF line endings. esbuild/vite's hashbang stripping then leaves an invalid token, so importing them from test/skill.test.ts fails the whole suite with "SyntaxError: Invalid or unexpected token" (CI on Linux is LF, so it never saw this). A CRLF shebang also breaks execution on Unix. Add `*.mjs text eol=lf` so these scripts are always LF regardless of the contributor's autocrlf. Once the suite collected, a second latent CRLF assumption surfaced: the SKILL.md frontmatter regex hard-coded `\n`, so it failed on the CRLF checkout. Make it line-ending agnostic (`\r?\n`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test/pack.test.ts spawned npm with execFileSync("npm", [...]), which throws
spawnSync ENOENT on Windows (the launcher is npm.cmd, and execFileSync does
not apply PATHEXT). Spawning npm.cmd directly is EINVAL since Node's
CVE-2024-27980 fix, and shell:true with an args array warns DEP0190. Use
execSync with a fixed literal command string: the shell resolves npm.cmd on
Windows and /bin/sh runs it on Linux/CI, with no deprecation noise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
부수: Windows 개발 환경 테스트 실패 2건 수정 (커밋
|
Real end-to-end testing on Windows surfaced a deprovision ordering bug the injected-fake unit tests could not: runDeprovisionSelfHosted removed the staged caddy-service.xml first, then called removeService (WinSW stop/uninstall). But WinSW loads caddy-service.[xml|yml] for EVERY command, so `caddy-service.exe stop` aborted with "Unable to locate caddy-service.[xml|yml]" and the service was left installed and running. On Windows, tear the service down before removing the xml/Caddyfile; when not elevated, leave the files in place so the stop/uninstall commands we hand the user still find the xml. Unix ordering is unchanged (the staged unit is a copy; the live service file lives at systemServicePath). New tests assert the invariant: the xml exists while WinSW stop/uninstall runs (admin), and is not pre-deleted when not admin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
실제 Windows 실행 테스트로 deprovision 버그 발견·수정 (커밋
|
buildPublicUrl returns http:// by default (cfg.scheme ?? "http"), but caddySnippet emitted a bare site address (host / host:port), which Caddy treats as a signal to enable Automatic HTTPS for any hostname or IP (per Caddy's docs). Real Windows testing showed the installed service serving HTTPS on a host hostdoc had advertised as http:// — an HTTP request got 400. Thread `scheme` into caddySnippet and prefix the site address with it (http://host or https://host). An explicit http:// disables Caddy's Automatic HTTPS so the served protocol matches the http:// link; --scheme https opts into TLS and yields an https:// link + auto-HTTPS. Verified end to end on Windows: http GET now 200 (was 400), no TLS/HTTP-3 listener, /_meta still 403. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
🎉 This PR is included in version 1.3.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Closes #41
내 NAS/PC + 공인 IP로
publish → 지속 링크UX를 제공하는 세 번째 호스팅 모드self-hosted. 이슈 #41 전체(Phase 1~3)를 관리하는 umbrella PR이며, 각 Phase를 이 브랜치(feat/41-self-hosted-mode)에 이어 쌓아 한 번에 머지한다.Spec:
docs/superpowers/specs/2026-07-03-self-hosted-mode-design.mdPlan:
docs/superpowers/plans/2026-07-03-self-hosted-mode.md진행 현황
✅ Phase 1 —
self-hosted모드 (MVP, 기존 웹서버 가정) — 완료src/lib/backend.ts):StorageBackend+S3Backend(aws.ts 무수정 위임) +LocalFsBackendresolveConfigself-hosted 파생(serveRoot판별자) +host/port/scheme+ 모호성 가드buildPublicUrlself-hosted 분기 + 설정 host 우선 / 공인 IP 자동감지(+경고) —ensureHost로 비동기 분리_meta차단 + index rewrite 스니펫(Caddy 기본 / nginx 옵션)hostdoc setup --serve-root(config 저장 + 스니펫 출력, 자동설치 없음)⬜ Phase 2 —
hostdoc setup이 웹서버 설치·구동 자동/_*차단 + index rewrite + 도메인 시 자동 HTTPS)deprovision으로 중지·제거🚧 Phase 3 — 플랫폼 확장 & 편의 (Windows 완료)
🔧 Phase 1 후속 정리 (최종 리뷰 Minor — non-blocking)
HOSTDOC_PORTNaN 가드 +HOSTDOC_SCHEME화이트리스트 (src/lib/config.ts)parsePort헬퍼로src/index.tsport 파싱 중복 +--port 0엣지 해소LocalFsBackend인접 prefix 테스트 보강 (test/backend.test.ts)LocalFsBackend.list/exists전체 walk → prefix 서브트리 최적화 (Phase 2 병행)Non-goals
포트포워딩/방화벽 자동화 · 베어 IP HTTPS 보장(호스트명 있을 때만).
테스트 (Phase 1 기준)
257/257 통과, typecheck(tsgo)·build 클린. CI는 AWS 크레덴셜·Terraform 없이 그대로 통과. 신규: backend/config/url/host/snippet/setup-selfhosted + self-hosted E2E(publish→list→rm).
리뷰 (Phase 1)
TDD 8 태스크, 태스크별 spec+quality 리뷰 통과 후 전체-브랜치 리뷰 Ready to merge: Yes (0 Critical/0 Important). Phase 2/3 완료 후 재리뷰하고 머지한다.
🤖 Generated with Claude Code