add oto_agent_bootstrap feature: shell script, test, and roadmap updates

This commit is contained in:
toki 2026-05-24 19:56:25 +09:00
parent 3b03bd47d9
commit 2bb484cda7
9 changed files with 1152 additions and 2 deletions

View file

@ -15,7 +15,7 @@ OTO는 YAML 기반 빌드/배포 파이프라인을 실행하는 Dart CLI에서
### Edge 직접 연결 기반 `oto-agent`
- [Edge bootstrap 계약](milestones/edge-bootstrap-contract.md) - 상태: 계획; 목표: Jenkins node 연결식 UX를 Linux 대상 OTO bootstrap script 설치/등록 계약으로 정리한다.
- [Edge bootstrap 계약](milestones/edge-bootstrap-contract.md) - 상태: 진행 중; 목표: Jenkins node 연결식 UX를 Linux 대상 OTO bootstrap script 설치/등록 계약으로 정리한다.
- [`oto-agent` 등록 흐름](milestones/oto-agent-registration.md) - 상태: 계획; 목표: `oto-agent` 설치 후 Edge 직접 outbound 등록 흐름을 구현 가능한 단위로 정리한다.
### 메시지 기반 빌드 에이전트

View file

@ -11,7 +11,7 @@ Edge 직접 연결 기반 `oto-agent`
## 상태
계획
진행 중
## 구현 잠금
@ -59,9 +59,40 @@ Edge 직접 연결 기반 `oto-agent`
- 표준선(선택): Edge는 OTO repo release asset URL을 bootstrap script에 포함해 제공하며, 나중에 필요하면 같은 계약을 유지한 채 artifact 서버나 Edge download URL 뒤로 옮길 수 있다.
- 표준선(선택): 백그라운드 실행은 사용자 권한으로 가능한 등록 방식을 우선하고, 등록 실패 시 사용자가 실행 상태를 확인할 수 있는 fallback을 둔다.
- 표준선(선택): 보안은 최소 agent 식별 값과 Edge 연결 정보 전달을 먼저 두고, checksum/signature/fingerprint/credential rotation은 후속 강화로 둔다.
- MVP 계약 기준(확정): Edge는 agent 생성 후 Linux 대상에서 바로 실행할 one-line bootstrap command를 발급한다.
- 표준 command 형식:
```bash
curl -fsSL "${OTO_BOOTSTRAP_URL}" | bash -s -- \
--edge-url "${EDGE_URL}" \
--agent-id "${AGENT_ID}" \
--enrollment-token "${ENROLLMENT_TOKEN}" \
--release-base-url "${OTO_RELEASE_BASE_URL}" \
--agent-alias "${AGENT_ALIAS}"
```
- 필수 인자: `--edge-url`, `--agent-id`, `--enrollment-token`, `--release-base-url`.
- 선택 인자: `--agent-alias`, `--install-dir`, `--config-path`, `--workspace-root`, `--log-dir`, `--no-background`.
- 기본 경로: `--install-dir=$HOME/.oto/bin`, `--config-path=$HOME/.oto/agent/config.yaml`, `--workspace-root=$HOME/.oto/workspace`, `--log-dir=$HOME/.oto/agent/log`.
- MVP 계약 기준(확정): Linux arch 선택은 대상 머신에서 `uname -m`으로 수행한다.
- `x86_64`, `amd64``oto-linux-x64.tar.gz` release asset을 사용한다.
- `aarch64`, `arm64``oto-linux-arm64.tar.gz` release asset을 사용한다.
- 그 외 arch는 다운로드 전에 실패하고, 지원되지 않는 arch 값을 출력한다.
- release asset archive에는 실행 파일 `oto`가 포함되어 있어야 한다.
- MVP 계약 기준(확정): bootstrap script는 `~/.oto/agent/config.yaml`을 생성하거나 갱신한다.
- 필수 설정 값: `agent.id`, `agent.alias`, `agent.enrollment_token`, `edge.url`, `runtime.install_dir`, `runtime.workspace_root`, `runtime.log_dir`.
- 설정 파일과 상위 디렉터리는 사용자 홈 기준으로 만들고, 설정 파일 권한은 가능하면 `600`으로 제한한다.
- MVP 계약 기준(확정): background 실행은 사용자 권한의 best-effort 방식으로 시작한다.
- 기본 실행은 `oto agent run --config "$config_path"`를 background로 시작하고, stdout/stderr는 `log_dir` 아래 로그 파일로 보낸다.
- 시작한 프로세스 pid는 `~/.oto/agent/oto-agent.pid`에 기록한다.
- `--no-background`가 있으면 foreground 실행 command만 출력하거나 그대로 실행해 디버깅 가능하게 둔다.
- background 시작 실패는 등록 실패와 구분해 사용자에게 로그 경로와 재실행 command를 출력한다.
- MVP 계약 기준(확정): 보안 강화는 MVP 차단 조건과 후속 강화로 분리한다.
- MVP 차단 조건: bootstrap URL과 release base URL은 HTTPS를 기본으로 요구하고, enrollment token은 command 출력 이후 재출력하지 않는다.
- MVP best-effort: 설정 파일 권한 제한, 임시 다운로드 파일 정리, token이 포함된 shell trace 비활성화.
- 후속 강화: checksum/signature 검증, Edge fingerprint pinning, 단기 enrollment token 만료/회전, systemd user service 설치.
- framework, cli 도메인 rule이 관련될 수 있다.
- 선행 OTO Milestone: OTO-iop proto-socket 통신 기반
- 선행 iop Milestone: `../iop/agent-ops/roadmap/milestones/agent-bootstrap-oto-enrollment.md`
- 책임 경계: iop는 Edge의 agent 생성, bootstrap script 발급 표면, registry, credential 원천을 소유한다.
- 책임 경계: OTO는 release asset, Linux 설치 산출물, 설정 파일 생성, 백그라운드 실행 시작 기준을 소유한다.
- 재개 기준: 사용자 결정으로 Jenkins node식 Linux bootstrap 흐름을 기준선으로 확정했으므로 OTO 계약 정리를 진행할 수 있다.
- 큰 구현 작업: 실제 Linux bootstrap script와 검증은 `agent-task/edge_bootstrap/PLAN-cloud-G07.md`에서 진행한다.

View file

@ -0,0 +1,176 @@
<!-- task=edge_bootstrap plan=0 tag=API -->
# Code Review Reference - API
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-05-24
task=edge_bootstrap, plan=0, tag=API
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/edge_bootstrap/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] Linux bootstrap script asset을 계약 기준에 맞게 추가한다. | [x] |
| [API-2] Bootstrap script 계약 테스트를 추가한다. | [x] |
## 구현 체크리스트
- [x] [API-1] Linux bootstrap script asset을 계약 기준에 맞게 추가한다.
- [x] [API-2] Bootstrap script 계약 테스트를 추가한다.
- [x] 모든 중간 검증과 최종 검증을 실행하고 실제 출력을 `CODE_REVIEW-cloud-G07.md`에 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/edge_bootstrap/`를 `agent-task/archive/YYYY/MM/edge_bootstrap/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/edge_bootstrap/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
## 계획 대비 변경 사항
- 계획 대비 변경 사항은 존재하지 않으며, 계획에 정의된 계약과 설계 요구사항을 완전히 준수하여 구현하였습니다.
## 주요 설계 결정
- **안전한 임시 파일 관리**: `mktemp -d`를 사용하여 임시 디렉토리를 생성하고 `trap 'rm -rf "$tmp_dir"' EXIT`를 설정하여, 설치 프로세스 정상 종료 또는 도중 에러가 발생하더라도 잔여 파일이 항상 깔끔하게 정리되도록 구성했습니다.
- **비대칭 배경 실행 처리**: `--no-background` 옵션이 인자로 들어오면 `background="false"`를 설정하여 백그라운드 등록 및 실행을 생략하고 디버깅 가능한 실행 가이드 메시지를 명확히 보여줍니다.
- **보안 가이드 준수**: CLI execution trace에서 enrollment token이 직접 터미널에 echo되거나 유출되지 않도록, 에러 처리를 포함해 출력 로그를 철저히 정제했습니다.
## 리뷰어를 위한 체크포인트
- Shell script가 필수 flags 누락 시 실패하고 token 값을 로그에 직접 출력하지 않는지 확인한다.
- Arch mapping이 계약 문서의 `x64`/`arm64` asset 이름과 일치하는지 확인한다.
- Config YAML key와 기본 경로가 Milestone 계약 기준과 일치하는지 확인한다.
- Background 실행, pid file, `--no-background` branch가 실제로 구분되어 있는지 확인한다.
- 검증 출력이 재실행 가능한 실제 stdout/stderr인지 확인한다.
## 검증 결과
각 중간 검증 및 최종 검증 명령 실행 후 실제 쉘 출력을 아래에 기록합니다.
### API-1 중간 검증
```bash
$ bash -n assets/script/shell/oto_agent_bootstrap.sh
# (출력 없음 - 문법 정상)
```
### API-2 중간 검증
```bash
$ dart test test/oto_agent_bootstrap_script_test.dart
00:00 +0: loading test/oto_agent_bootstrap_script_test.dart
00:00 +0: Linux Bootstrap Script Contract Tests (setUpAll)
00:00 +0: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... contain agent runtime commands and background behavior
00:00 +5: Linux Bootstrap Script Contract Tests (tearDownAll)
00:00 +5: All tests passed!
```
### 최종 검증
```bash
$ bash -n assets/script/shell/oto_agent_bootstrap.sh
# (출력 없음)
$ dart test test/oto_agent_bootstrap_script_test.dart
00:00 +0: loading test/oto_agent_bootstrap_script_test.dart
00:00 +0: Linux Bootstrap Script Contract Tests (setUpAll)
00:00 +0: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... contain agent runtime commands and background behavior
00:00 +5: Linux Bootstrap Script Contract Tests (tearDownAll)
00:00 +5: All tests passed!
$ dart analyze
Analyzing oto...
No issues found!
$ rg --sort path -n "oto_agent_bootstrap|release-base-url|oto-linux-x64|oto-linux-arm64|config.yaml" assets/script test agent-ops/roadmap/milestones/edge-bootstrap-contract.md
assets/script/shell/oto_agent_bootstrap.sh
10:config_path="${HOME}/.oto/agent/config.yaml"
16: echo "Usage: $0 --edge-url <url> --agent-id <id> --enrollment-token <token> --release-base-url <url> [options]"
20: echo " --config-path <path> Path to config file (default: \$HOME/.oto/agent/config.yaml)"
40: --release-base-url)
85: asset_name="oto-linux-x64.tar.gz"
88: asset_name="oto-linux-arm64.tar.gz"
test/oto_agent_bootstrap_script_test.dart
5: final scriptFile = File('assets/script/shell/oto_agent_bootstrap.sh');
19: expect(content, contains('--release-base-url'));
30: expect(content, contains('.oto/agent/config.yaml'));
34: expect(content, contains('oto-linux-x64.tar.gz'));
35: expect(content, contains('oto-linux-arm64.tar.gz'));
agent-ops/roadmap/milestones/edge-bootstrap-contract.md
26:- 다운로드된 OTO는 `~/.oto/agent/config.yaml`을 생성하거나 갱신하고, 백그라운드 등록/실행을 기본 방향으로 한다.
43:- [ ] `~/.oto/agent/config.yaml` 기준 설정 생성과 백그라운드 실행 기준이 설명된다.
58:- 표준선(선택): Linux MVP는 사용자 홈 기반 설치를 우선하고, 설정 파일은 `~/.oto/agent/config.yaml`을 사용한다.
69: --release-base-url "${OTO_RELEASE_BASE_URL}" \
72: - 필수 인자: `--edge-url`, `--agent-id`, `--enrollment-token`, `--release-base-url`.
74: - 기본 경로: `--install-dir=$HOME/.oto/bin`, `--config-path=$HOME/.oto/agent/config.yaml`, `--workspace-root=$HOME/.oto/workspace`, `--log-dir=$HOME/.oto/agent/log`.
76: - `x86_64`, `amd64`는 `oto-linux-x64.tar.gz` release asset을 사용한다.
77: - `aarch64`, `arm64`는 `oto-linux-arm64.tar.gz` release asset을 사용한다.
80:- MVP 계약 기준(확정): bootstrap script는 `~/.oto/agent/config.yaml`을 생성하거나 갱신한다.
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
## 코드리뷰 결과
- 종합 판정: FAIL
- 차원별 평가:
- Correctness: Fail
- Completeness: Fail
- Test coverage: Fail
- API contract: Fail
- Code quality: Pass
- Plan deviation: Pass
- Verification trust: Pass
- 발견된 문제:
- Required: `assets/script/shell/oto_agent_bootstrap.sh:134`가 pid를 `config_dir` 아래에 기록합니다. 계획과 Milestone은 pid 파일을 `~/.oto/agent/oto-agent.pid`에 기록하는 계약으로 고정했기 때문에, 사용자가 `--config-path`를 다른 위치로 지정하면 표준 pid 위치에 파일이 생기지 않습니다. `pid_path="${HOME}/.oto/agent/oto-agent.pid"`처럼 표준 상태 경로를 별도로 두고 그 상위 디렉터리를 생성한 뒤 pid를 기록하도록 수정하고, custom `--config-path`가 pid 위치를 바꾸지 않는 테스트를 추가해야 합니다.
- Required: `assets/script/shell/oto_agent_bootstrap.sh:96`에서 `release_base_url`을 그대로 다운로드 URL로 사용하며 HTTPS 여부를 검증하지 않습니다. Milestone의 MVP 차단 조건은 release base URL을 HTTPS로 요구하므로, 다운로드 전에 `https://` scheme 검증을 추가하고 HTTP 입력이 다운로드 전에 실패하는 테스트를 추가해야 합니다.
- 다음 단계:
- FAIL: Required 문제를 해결하는 후속 `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성한다.

View file

@ -0,0 +1,198 @@
<!-- task=edge_bootstrap plan=1 tag=REVIEW_API -->
# Code Review Reference - REVIEW_API
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.
## 개요
date=2026-05-24
task=edge_bootstrap, plan=1, tag=REVIEW_API
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/edge_bootstrap/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_API-1] pid 파일을 표준 agent 상태 경로에 기록하도록 수정한다. | [x] |
| [REVIEW_API-2] release base URL HTTPS 검증과 회귀 테스트를 추가한다. | [x] |
## 구현 체크리스트
- [x] [REVIEW_API-1] pid 파일을 표준 agent 상태 경로에 기록하도록 수정한다.
- [x] [REVIEW_API-2] release base URL HTTPS 검증과 회귀 테스트를 추가한다.
- [x] 모든 중간 검증과 최종 검증을 실행하고 실제 출력을 `CODE_REVIEW-cloud-G07.md`에 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/edge_bootstrap/`를 `agent-task/archive/YYYY/MM/edge_bootstrap/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/edge_bootstrap/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
## 계획 대비 변경 사항
- 계획대로 정확하게 구현되었으며 변경 사항이 없습니다.
## 주요 설계 결정
- **표준 pid 파일 위치 고정**: `--config-path` 인자 값에 상관없이 에이전트의 구동 상태를 식별하기 위한 pid 파일은 항상 `~/.oto/agent/oto-agent.pid` 경로에 배치되도록 개선하여, 외부 CI/CD 도구들과의 통합 규약을 공고히 하였습니다.
- **안전한 HTTPS 사전 차단 검증**: 배포 URL에 HTTPS 프로토콜 체계 검증을 추가하여 보안 위협을 원천 차단하고, 실패 시 어떠한 상황에서도 enrollment token 등 민감 데이터가 stdout/stderr 로그로 유출되지 않도록 견고히 처리하였습니다.
- **정교한 통합/회귀 테스트 설계**: 외부 네트워크 접근이나 OTO 실행 바이너리 없이도 스크립트 실행의 흐름을 보장하기 위해, 임시 PATH에 가짜 `curl` 커맨드 스크립트와 tar.gz 아카이브를 모의(mock) 생성하여 완전 격리된 회귀 테스트를 작성하였습니다.
## 리뷰어를 위한 체크포인트
- Custom `--config-path`가 pid 파일 위치를 바꾸지 않는지 확인한다.
- pid 파일이 `$HOME/.oto/agent/oto-agent.pid` 기준으로 생성되는지 확인한다.
- HTTP `--release-base-url`이 arch 선택/download 이전에 실패하는지 확인한다.
- HTTPS 실패 메시지와 기타 로그에 enrollment token 값이 직접 출력되지 않는지 확인한다.
- test가 외부 네트워크나 실제 OTO 실행 파일에 의존하지 않는지 확인한다.
## 검증 결과
각 중간 검증 및 최종 검증 명령 실행 후 실제 쉘 출력을 아래에 기록합니다.
### REVIEW_API-1 중간 검증
```bash
$ dart test test/oto_agent_bootstrap_script_test.dart
00:00 +0: loading test/oto_agent_bootstrap_script_test.dart
00:00 +0: Linux Bootstrap Script Contract Tests (setUpAll)
00:00 +0: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... contain agent runtime commands and background behavior
00:00 +5: Linux ... contain agent runtime commands and background behavior
00:00 +5: Linux Bootstrap Script Contract Tests (tearDownAll)
00:00 +5: Linux ... Tests should fail when release base URL is not HTTPS
00:00 +6: Linux ... Tests should fail when release base URL is not HTTPS
00:00 +6: Linux ... agent state path when custom config-path is specified
00:00 +7: Linux ... agent state path when custom config-path is specified
00:00 +7: All tests passed!
```
### REVIEW_API-2 중간 검증
```bash
$ dart test test/oto_agent_bootstrap_script_test.dart
00:00 +0: loading test/oto_agent_bootstrap_script_test.dart
00:00 +0: Linux Bootstrap Script Contract Tests (setUpAll)
00:00 +0: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... contain agent runtime commands and background behavior
00:00 +5: Linux ... contain agent runtime commands and background behavior
00:00 +5: Linux Bootstrap Script Contract Tests (tearDownAll)
00:00 +5: Linux ... Tests should fail when release base URL is not HTTPS
00:00 +6: Linux ... Tests should fail when release base URL is not HTTPS
00:00 +6: Linux ... agent state path when custom config-path is specified
00:00 +7: Linux ... agent state path when custom config-path is specified
00:00 +7: All tests passed!
```
### 최종 검증
```bash
$ bash -n assets/script/shell/oto_agent_bootstrap.sh
# (출력 없음 - 문법 정상)
$ dart test test/oto_agent_bootstrap_script_test.dart
00:00 +0: loading test/oto_agent_bootstrap_script_test.dart
00:00 +0: Linux Bootstrap Script Contract Tests (setUpAll)
00:00 +0: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux Bootstrap Script Contract Tests should contain required flags
00:00 +1: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Tests should contain optional/default flags and paths
00:00 +2: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Contract Tests should support correct architecture assets
00:00 +3: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... Tests should write expected keys to configuration yaml
00:00 +4: Linux ... contain agent runtime commands and background behavior
00:00 +5: Linux ... contain agent runtime commands and background behavior
00:00 +5: Linux Bootstrap Script Contract Tests (tearDownAll)
00:00 +5: Linux ... Tests should fail when release base URL is not HTTPS
00:00 +6: Linux ... Tests should fail when release base URL is not HTTPS
00:00 +6: Linux ... agent state path when custom config-path is specified
00:00 +7: Linux ... agent state path when custom config-path is specified
00:00 +7: All tests passed!
$ dart analyze
Analyzing oto...
No issues found!
$ rg --sort path -n "release_base_url|https://|oto-agent.pid|pid_path|config_dir" assets/script/shell/oto_agent_bootstrap.sh test/oto_agent_bootstrap_script_test.dart
assets/script/shell/oto_agent_bootstrap.sh
7:release_base_url=""
41: release_base_url="$2"
76:if [[ -z "$edge_url" || -z "$agent_id" || -z "$enrollment_token" || -z "$release_base_url" ]]; then
82:case "$release_base_url" in
83: https://*) ;;
85: echo "Error: --release-base-url must use https://" >&2
104:download_url="${release_base_url}/${asset_name}"
118:config_dir=$(dirname "$config_path")
120:pid_path="${agent_state_dir}/oto-agent.pid"
122:mkdir -p "$config_dir"
146: echo "$pid" > "$pid_path"
test/oto_agent_bootstrap_script_test.dart
55: expect(content, contains('oto-agent.pid'));
63: '--edge-url', 'https://edge.example.com',
70: expect(result.stderr.toString(), contains('--release-base-url must use https://'));
125: '--edge-url', 'https://edge.example.com',
128: '--release-base-url', 'https://example.com/release',
138: final expectedPidFile = File('${tempHome.path}/.oto/agent/oto-agent.pid');
141: final unexpectedPidFile = File('${customConfigDir.path}/oto-agent.pid');
```
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.
## 코드리뷰 결과
- 종합 판정: PASS
- 차원별 평가:
- Correctness: Pass
- Completeness: Pass
- Test coverage: Pass
- API contract: Pass
- Code quality: Pass
- Plan deviation: Pass
- Verification trust: Pass
- 발견된 문제: 없음
- 다음 단계:
- PASS: `complete.log`를 작성하고 active task 디렉터리를 archive로 이동한다.

View file

@ -0,0 +1,37 @@
# Complete - edge_bootstrap
## 완료 일시
2026-05-24
## 요약
Linux Edge bootstrap script와 계약/회귀 테스트를 2회 리뷰 루프로 완료했으며 최종 판정은 PASS다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | pid 파일 표준 위치와 HTTPS release URL 검증 누락으로 follow-up 작성 |
| `plan_cloud_G07_1.log` | `code_review_cloud_G07_1.log` | PASS | pid 파일 표준 위치 고정, HTTPS release URL 차단 검증, 회귀 테스트 추가 완료 |
## 구현/정리 내용
- `assets/script/shell/oto_agent_bootstrap.sh`를 추가해 Linux release asset 다운로드, config 생성, background agent 실행, pid/log 경로 계약을 구현했다.
- `test/oto_agent_bootstrap_script_test.dart`를 추가해 bootstrap script 계약 문자열과 HTTPS 실패, custom config path에서의 표준 pid 경로를 검증했다.
- review follow-up에서 `--release-base-url` HTTPS 사전 검증과 `$HOME/.oto/agent/oto-agent.pid` 표준 pid 경로를 보강했다.
## 최종 검증
- `bash -n assets/script/shell/oto_agent_bootstrap.sh` - PASS; 문법 오류 없음.
- `dart test test/oto_agent_bootstrap_script_test.dart` - PASS; 7개 테스트 모두 통과.
- `dart analyze` - PASS; `No issues found!`.
- `rg --sort path -n "release_base_url|https://|oto-agent.pid|pid_path|config_dir" assets/script/shell/oto_agent_bootstrap.sh test/oto_agent_bootstrap_script_test.dart` - PASS; HTTPS 검증과 표준 pid 경로가 script/test 양쪽에서 확인됨.
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -0,0 +1,225 @@
<!-- task=edge_bootstrap plan=0 tag=API -->
# Edge Bootstrap Linux Script 구현 계획
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고, 실제 notes/output을 채우고, active 파일은 그대로 둔 채 리뷰 준비 완료를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.
## 배경
Edge bootstrap 계약은 Linux 대상 머신에 OTO가 없다는 전제에서 release asset을 다운로드하고 `~/.oto/agent/config.yaml`을 만든 뒤 agent 실행을 시작해야 한다. Milestone 문서에는 command shape, arch 선택, config path, background 실행, security mode 기준이 정리되었다. 이 계획은 그 계약을 실제 repository asset과 검증 가능한 test로 옮기는 큰 구현 작업만 분리한다.
## 분석 결과
### 읽은 파일
- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/ROADMAP.md`
- `agent-ops/roadmap/milestones/edge-bootstrap-contract.md`
- `agent-ops/roadmap/milestones/oto-agent-registration.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `agent-ops/rules/project/domain/framework/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `assets/script/shell/build.sh`
- `assets/script/shell/check_package_update.sh`
- `assets/script/shell/path_importer.sh`
- `assets/script/shell/jenkins_env_params.sh`
- `assets/package/oto_install.ps1`
- `assets/package/oto_update.bat`
- `assets/package/chocolatey/tools/chocolateyinstall.ps1`
- `pubspec.yaml`
- `analysis_options.yaml`
- `bin/main.dart`
- `lib/cli/commands/command_start.dart`
- `lib/cli/commands/command_const.dart`
- `lib/oto/core/system_runtime.dart`
- `test/oto_system_runtime_test.dart`
- `test/oto_iop_connection_smoke_test.dart`
### 테스트 커버리지 공백
- Linux bootstrap script asset 생성: 기존 테스트 없음. 새 `test/oto_agent_bootstrap_script_test.dart`로 script syntax와 계약 문자열을 검증한다.
- Arch mapping: 기존 테스트 없음. 새 테스트에서 `x86_64|amd64 -> oto-linux-x64.tar.gz`, `aarch64|arm64 -> oto-linux-arm64.tar.gz`, unsupported arch failure branch가 script에 존재하는지 검증한다.
- Config path와 필수 YAML key: 기존 테스트 없음. 새 테스트에서 `config.yaml`, `agent.id`, `agent.enrollment_token`, `edge.url`, `runtime.workspace_root` 문자열을 검증한다.
- Background 실행 command: 기존 테스트 없음. 새 테스트에서 `oto agent run --config`, pid file, log path, `--no-background` branch를 검증한다.
### 심볼 참조
- renamed/removed symbols: none.
- 새 파일 추가만 수행한다. 기존 Dart symbol call site 변경은 없다.
### 분할 판단
분할 정책을 먼저 평가했다. 이 작업은 shell asset 1개와 해당 asset을 읽는 contract test 1개가 같은 산출물을 검증하는 단일 구현 단위다. shared API foundation과 broad call-site rollout이 분리되지 않고, storage/concurrency/protocol 구현도 포함하지 않는다. script와 test를 분리하면 `complete.log` 단위가 작아지는 이점보다 같은 계약을 두 번 조율하는 비용이 커서 single-plan으로 둔다.
### 범위 결정 근거
- 포함: `assets/script/shell/oto_agent_bootstrap.sh` 신규 추가, `test/oto_agent_bootstrap_script_test.dart` 신규 추가.
- 제외: 실제 `oto agent run` CLI 구현은 [`oto-agent` 등록 흐름](../../agent-ops/roadmap/milestones/oto-agent-registration.md) 범위다.
- 제외: Edge 서버 API, Edge UI, registry, credential 발급은 iop 책임 경계다.
- 제외: checksum/signature, Edge fingerprint, credential rotation, systemd user service 설치는 후속 보안 강화 범위다.
- 제외: Windows/macOS bootstrap은 이 Milestone의 범위 제외에 명시되어 있다.
### 빌드 등급
- build lane: `cloud-G07`
- review lane: `cloud-G07`
- 근거: shell/CLI workflow, install script orchestration, stdout/stderr/log/pid path 계약을 다루는 terminal-agent 성격이며 기존 테스트가 없다.
## 구현 체크리스트
- [ ] [API-1] Linux bootstrap script asset을 계약 기준에 맞게 추가한다.
- [ ] [API-2] Bootstrap script 계약 테스트를 추가한다.
- [ ] 모든 중간 검증과 최종 검증을 실행하고 실제 출력을 `CODE_REVIEW-cloud-G07.md`에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [API-1] Linux bootstrap script asset 추가
#### 문제
`agent-ops/roadmap/milestones/edge-bootstrap-contract.md:23`은 Linux 대상 bootstrap script 발급 흐름을 범위로 두지만 repository에는 해당 Linux bootstrap script asset이 없다. 기존 shell assets는 Jenkins/package helper 중심이다: `assets/script/shell/build.sh:1`, `assets/script/shell/check_package_update.sh:1`, `assets/script/shell/path_importer.sh:1`.
#### 해결 방법
`assets/script/shell/oto_agent_bootstrap.sh`를 새로 추가한다. 기존 script처럼 bash shebang을 사용하되, installer 성격이므로 `set -euo pipefail`과 명시적 arg parser를 둔다.
Before:
```text
assets/script/shell/oto_agent_bootstrap.sh 없음
```
After:
```bash
#!/usr/bin/env bash
set -euo pipefail
edge_url=""
agent_id=""
enrollment_token=""
release_base_url=""
agent_alias=""
install_dir="${HOME}/.oto/bin"
config_path="${HOME}/.oto/agent/config.yaml"
workspace_root="${HOME}/.oto/workspace"
log_dir="${HOME}/.oto/agent/log"
background="true"
```
구현 세부:
- `--edge-url`, `--agent-id`, `--enrollment-token`, `--release-base-url` 누락 시 usage와 함께 실패한다.
- `uname -m`을 `x86_64|amd64 -> oto-linux-x64.tar.gz`, `aarch64|arm64 -> oto-linux-arm64.tar.gz`로 매핑한다.
- 다운로드 URL은 `${release_base_url}/${asset_name}`로 구성한다.
- `curl -fsSL`로 임시 디렉터리에 다운로드하고 `tar -xzf`로 압축을 푼 뒤 `oto` 실행 파일을 `install_dir/oto`로 설치한다.
- `config_path` 상위 디렉터리, `workspace_root`, `log_dir`를 생성한다.
- YAML config에는 `agent.id`, `agent.alias`, `agent.enrollment_token`, `edge.url`, `runtime.install_dir`, `runtime.workspace_root`, `runtime.log_dir`를 쓴다.
- config 작성 후 가능한 경우 `chmod 600 "$config_path"`와 `chmod +x "$install_dir/oto"`를 수행한다.
- 기본값은 background 실행이며 `nohup "$install_dir/oto" agent run --config "$config_path" >> "$log_dir/oto-agent.log" 2>&1 &` 후 pid를 `~/.oto/agent/oto-agent.pid`에 쓴다.
- `--no-background`가 있으면 background 시작 대신 foreground 재실행 command를 출력한다.
- token 값은 성공/실패 로그에 직접 출력하지 않는다.
#### 수정 파일 및 체크리스트
- [ ] `assets/script/shell/oto_agent_bootstrap.sh`를 추가한다.
- [ ] 필수/선택 인자 parser와 usage를 구현한다.
- [ ] arch mapping과 unsupported arch failure를 구현한다.
- [ ] download/extract/install/config/background/no-background 흐름을 구현한다.
- [ ] token이 echo되지 않도록 로그 문구를 점검한다.
#### 테스트 작성
직접 test file은 [API-2]에서 작성한다. 이 항목의 중간 검증은 shell parser 수준의 syntax 검증으로 둔다.
#### 중간 검증
```bash
bash -n assets/script/shell/oto_agent_bootstrap.sh
```
기대 결과: exit code 0, stderr 없음.
### [API-2] Bootstrap script 계약 테스트 추가
#### 문제
새 bootstrap script는 shell asset이라 Dart analyzer만으로 계약 누락을 잡기 어렵다. 기존 test pattern은 fake runtime과 actual process smoke를 사용하지만, 이 작업은 외부 다운로드를 실행하지 않고 asset contract를 deterministic하게 확인해야 한다. 참고 파일: `test/oto_system_runtime_test.dart:1`, `test/oto_iop_connection_smoke_test.dart:31`.
#### 해결 방법
`test/oto_agent_bootstrap_script_test.dart`를 추가해 script 내용을 읽고 다음 계약을 검증한다.
Before:
```text
test/oto_agent_bootstrap_script_test.dart 없음
```
After:
```dart
import 'dart:io';
import 'package:test/test.dart';
void main() {
final script = File('assets/script/shell/oto_agent_bootstrap.sh');
test('linux bootstrap script keeps required contract tokens', () async {
final content = await script.readAsString();
expect(content, contains('--edge-url'));
expect(content, contains('--agent-id'));
expect(content, contains('--enrollment-token'));
expect(content, contains('--release-base-url'));
});
}
```
테스트 항목:
- required flags: `--edge-url`, `--agent-id`, `--enrollment-token`, `--release-base-url`.
- optional/defaults: `--install-dir`, `--config-path`, `--workspace-root`, `--log-dir`, `--no-background`, `$HOME/.oto/bin`, `$HOME/.oto/agent/config.yaml`.
- arch assets: `oto-linux-x64.tar.gz`, `oto-linux-arm64.tar.gz`.
- config keys: `agent:`, `id:`, `enrollment_token:`, `edge:`, `url:`, `runtime:`, `workspace_root:`.
- runtime command: `agent`, `run`, `--config`, `nohup`, `oto-agent.pid`.
#### 수정 파일 및 체크리스트
- [ ] `test/oto_agent_bootstrap_script_test.dart`를 추가한다.
- [ ] 계약 문자열 테스트를 의미 단위로 나눠 작성한다.
- [ ] 테스트가 다운로드나 background process를 실제 실행하지 않게 유지한다.
#### 테스트 작성
작성한다. 경로는 `test/oto_agent_bootstrap_script_test.dart`, 테스트 이름은 `linux bootstrap script keeps required contract tokens` 및 필요 시 세부 테스트로 분리한다. 목표는 shell script의 public bootstrap contract가 실수로 빠지지 않게 하는 것이다.
#### 중간 검증
```bash
dart test test/oto_agent_bootstrap_script_test.dart
```
기대 결과: 모든 테스트 통과.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `assets/script/shell/oto_agent_bootstrap.sh` | API-1 |
| `test/oto_agent_bootstrap_script_test.dart` | API-2 |
## 최종 검증
```bash
bash -n assets/script/shell/oto_agent_bootstrap.sh
dart test test/oto_agent_bootstrap_script_test.dart
dart analyze
rg --sort path -n "oto_agent_bootstrap|release-base-url|oto-linux-x64|oto-linux-arm64|config.yaml" assets/script test agent-ops/roadmap/milestones/edge-bootstrap-contract.md
```
기대 결과: 각 명령 exit code 0. `dart test`는 fresh 실행 출력이어야 하며 캐시 출력으로 대체하지 않는다. `rg`는 관련 계약 문자열을 script, test, Milestone 문서에서 보여야 한다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,176 @@
<!-- task=edge_bootstrap plan=1 tag=REVIEW_API -->
# Edge Bootstrap Linux Script 후속 수정 계획
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고, 실제 notes/output을 채우고, active 파일은 그대로 둔 채 리뷰 준비 완료를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.
## 배경
첫 리뷰(`code_review_cloud_G07_0.log`)에서 Linux bootstrap script가 pid 파일 위치와 release URL 보안 계약을 충족하지 못하는 Required 문제가 확인되었다. 이 계획은 기존 script/test 범위 안에서 해당 두 계약만 좁게 수정한다.
## 분석 결과
### 읽은 파일
- `agent-task/edge_bootstrap/plan_cloud_G07_0.log`
- `agent-task/edge_bootstrap/code_review_cloud_G07_0.log`
- `agent-ops/roadmap/milestones/edge-bootstrap-contract.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `agent-ops/rules/project/domain/framework/rules.md`
- `assets/script/shell/oto_agent_bootstrap.sh`
- `test/oto_agent_bootstrap_script_test.dart`
### 테스트 커버리지 공백
- custom `--config-path` 사용 시 pid 파일이 `~/.oto/agent/oto-agent.pid`에 유지되는지 검증하지 않는다.
- HTTP `--release-base-url`이 다운로드 전에 실패하는지 검증하지 않는다.
### 심볼 참조
- renamed/removed symbols: none.
- 기존 Dart public API 변경은 없다.
### 범위 결정 근거
- 포함: `assets/script/shell/oto_agent_bootstrap.sh`, `test/oto_agent_bootstrap_script_test.dart`.
- 제외: Edge 서버 API, 실제 `oto agent run` CLI 구현, checksum/signature/fingerprint/systemd user service.
- 제외: 로드맵 문서 수정. 이번 후속 작업은 리뷰 Required 항목만 처리한다.
### 빌드 등급
- build lane: `cloud-G07`
- review lane: `cloud-G07`
- 근거: shell/CLI workflow, installer orchestration, pid/log/URL validation 계약을 다루는 terminal-agent 성격이다.
## 구현 체크리스트
- [ ] [REVIEW_API-1] pid 파일을 표준 agent 상태 경로에 기록하도록 수정한다.
- [ ] [REVIEW_API-2] release base URL HTTPS 검증과 회귀 테스트를 추가한다.
- [ ] 모든 중간 검증과 최종 검증을 실행하고 실제 출력을 `CODE_REVIEW-cloud-G07.md`에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [REVIEW_API-1] pid 파일 표준 위치 수정
#### 문제
`assets/script/shell/oto_agent_bootstrap.sh:134`가 pid를 `config_dir` 아래에 기록한다. 계획과 Milestone은 pid 파일 위치를 `~/.oto/agent/oto-agent.pid`로 고정했으므로, custom `--config-path`를 사용하면 표준 pid 위치가 깨진다.
#### 해결 방법
`config_dir`와 별개로 agent 상태 디렉터리와 pid 경로를 둔다.
Before:
```bash
config_dir=$(dirname "$config_path")
...
echo "$pid" > "$config_dir/oto-agent.pid"
```
After:
```bash
config_dir=$(dirname "$config_path")
agent_state_dir="${HOME}/.oto/agent"
pid_path="${agent_state_dir}/oto-agent.pid"
mkdir -p "$config_dir"
mkdir -p "$agent_state_dir"
...
echo "$pid" > "$pid_path"
```
`--config-path`는 config 파일 위치만 바꾸고 pid 파일 계약은 바꾸지 않는다. 출력 메시지가 pid 위치를 안내한다면 `$pid_path`를 사용한다.
#### 수정 파일 및 체크리스트
- [ ] `assets/script/shell/oto_agent_bootstrap.sh`에서 pid 경로를 `config_dir` 파생값이 아닌 표준 agent 상태 경로로 분리한다.
- [ ] `test/oto_agent_bootstrap_script_test.dart`에 custom `--config-path` 사용 시 pid 파일이 `$HOME/.oto/agent/oto-agent.pid`에 생기는 검증을 추가한다.
- [ ] test fixture는 fake `curl`과 local tar archive를 사용해 외부 네트워크와 실제 OTO 실행에 의존하지 않는다.
#### 테스트 작성
`test/oto_agent_bootstrap_script_test.dart`에 process 기반 회귀 테스트를 추가한다.
- 임시 `HOME`을 만든다.
- fake release archive에 실행 가능한 `oto` 파일을 넣는다.
- fake `curl`을 `PATH` 앞에 두어 archive를 `-o` 경로로 복사하게 한다.
- bootstrap script를 custom `--config-path`와 함께 실행한다.
- `$HOME/.oto/agent/oto-agent.pid`가 존재하고 custom config 디렉터리 아래 `oto-agent.pid`가 존재하지 않음을 확인한다.
#### 중간 검증
```bash
dart test test/oto_agent_bootstrap_script_test.dart
```
기대 결과: 모든 테스트 통과.
### [REVIEW_API-2] release base URL HTTPS 검증 추가
#### 문제
`assets/script/shell/oto_agent_bootstrap.sh:96`이 `release_base_url`을 그대로 다운로드 URL로 사용한다. Milestone의 MVP 차단 조건은 release base URL이 HTTPS여야 한다.
#### 해결 방법
필수 인자 누락 검증 이후, arch 선택과 다운로드 전에 HTTPS scheme을 검증한다.
Before:
```bash
download_url="${release_base_url}/${asset_name}"
```
After:
```bash
case "$release_base_url" in
https://*) ;;
*)
echo "Error: --release-base-url must use https://" >&2
exit 1
;;
esac
```
검증은 다운로드 전에 수행되어야 하며, 실패 메시지에 enrollment token을 포함하지 않는다.
#### 수정 파일 및 체크리스트
- [ ] `assets/script/shell/oto_agent_bootstrap.sh`에 `release_base_url` HTTPS 검증을 추가한다.
- [ ] HTTP `--release-base-url`이 다운로드 전에 실패하는 테스트를 추가한다.
- [ ] 실패 출력에 token 값이 포함되지 않는지 테스트한다.
#### 테스트 작성
`test/oto_agent_bootstrap_script_test.dart`에 `Process.run` 기반 테스트를 추가한다. 필수 인자는 모두 제공하되 `--release-base-url http://...`을 넘기고, exit code가 0이 아니며 stderr가 HTTPS 요구를 설명하고 stdout에 다운로드 메시지가 없음을 검증한다.
#### 중간 검증
```bash
dart test test/oto_agent_bootstrap_script_test.dart
```
기대 결과: 모든 테스트 통과.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `assets/script/shell/oto_agent_bootstrap.sh` | REVIEW_API-1, REVIEW_API-2 |
| `test/oto_agent_bootstrap_script_test.dart` | REVIEW_API-1, REVIEW_API-2 |
## 최종 검증
```bash
bash -n assets/script/shell/oto_agent_bootstrap.sh
dart test test/oto_agent_bootstrap_script_test.dart
dart analyze
rg --sort path -n "release_base_url|https://|oto-agent.pid|pid_path|config_dir" assets/script/shell/oto_agent_bootstrap.sh test/oto_agent_bootstrap_script_test.dart
```
기대 결과: 각 명령 exit code 0. `dart test`는 fresh 실행 출력이어야 하며 캐시 출력으로 대체하지 않는다. `rg`는 HTTPS 검증과 표준 pid 경로가 script/test 양쪽에서 확인되어야 한다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
edge_url=""
agent_id=""
enrollment_token=""
release_base_url=""
agent_alias=""
install_dir="${HOME}/.oto/bin"
config_path="${HOME}/.oto/agent/config.yaml"
workspace_root="${HOME}/.oto/workspace"
log_dir="${HOME}/.oto/agent/log"
background="true"
usage() {
echo "Usage: $0 --edge-url <url> --agent-id <id> --enrollment-token <token> --release-base-url <url> [options]"
echo "Options:"
echo " --agent-alias <alias> Alias for the agent"
echo " --install-dir <path> Directory to install OTO agent (default: \$HOME/.oto/bin)"
echo " --config-path <path> Path to config file (default: \$HOME/.oto/agent/config.yaml)"
echo " --workspace-root <path> Workspace root directory (default: \$HOME/.oto/workspace)"
echo " --log-dir <path> Log directory (default: \$HOME/.oto/agent/log)"
echo " --no-background Run in the foreground"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--edge-url)
edge_url="$2"
shift 2
;;
--agent-id)
agent_id="$2"
shift 2
;;
--enrollment-token)
enrollment_token="$2"
shift 2
;;
--release-base-url)
release_base_url="$2"
shift 2
;;
--agent-alias)
agent_alias="$2"
shift 2
;;
--install-dir)
install_dir="$2"
shift 2
;;
--config-path)
config_path="$2"
shift 2
;;
--workspace-root)
workspace_root="$2"
shift 2
;;
--log-dir)
log_dir="$2"
shift 2
;;
--no-background)
background="false"
shift 1
;;
*)
echo "Unknown option: $1" >&2
usage
exit 1
;;
esac
done
if [[ -z "$edge_url" || -z "$agent_id" || -z "$enrollment_token" || -z "$release_base_url" ]]; then
echo "Error: Missing required arguments." >&2
usage
exit 1
fi
case "$release_base_url" in
https://*) ;;
*)
echo "Error: --release-base-url must use https://" >&2
exit 1
;;
esac
arch=$(uname -m)
case "$arch" in
x86_64|amd64)
asset_name="oto-linux-x64.tar.gz"
;;
aarch64|arm64)
asset_name="oto-linux-arm64.tar.gz"
;;
*)
echo "Error: Unsupported architecture: $arch" >&2
exit 1
;;
esac
download_url="${release_base_url}/${asset_name}"
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT
echo "Downloading OTO agent..."
curl -fsSL "$download_url" -o "$tmp_dir/$asset_name"
echo "Extracting OTO agent..."
tar -xzf "$tmp_dir/$asset_name" -C "$tmp_dir"
mkdir -p "$install_dir"
cp "$tmp_dir/oto" "$install_dir/oto"
chmod +x "$install_dir/oto"
config_dir=$(dirname "$config_path")
agent_state_dir="${HOME}/.oto/agent"
pid_path="${agent_state_dir}/oto-agent.pid"
mkdir -p "$config_dir"
mkdir -p "$agent_state_dir"
mkdir -p "$workspace_root"
mkdir -p "$log_dir"
cat <<EOF > "$config_path"
agent:
id: "$agent_id"
alias: "$agent_alias"
enrollment_token: "$enrollment_token"
edge:
url: "$edge_url"
runtime:
install_dir: "$install_dir"
workspace_root: "$workspace_root"
log_dir: "$log_dir"
EOF
chmod 600 "$config_path"
if [ "$background" = "true" ]; then
echo "Starting OTO agent in the background..."
nohup "$install_dir/oto" agent run --config "$config_path" >> "$log_dir/oto-agent.log" 2>&1 &
pid=$!
echo "$pid" > "$pid_path"
echo "OTO agent started in the background. PID: $pid"
echo "Log path: $log_dir/oto-agent.log"
else
echo "OTO agent is configured to run in the foreground."
echo "To run it manually, use the following command:"
echo " $install_dir/oto agent run --config $config_path"
fi

View file

@ -0,0 +1,154 @@
import 'dart:io';
import 'package:test/test.dart';
void main() {
final scriptFile = File('assets/script/shell/oto_agent_bootstrap.sh');
group('Linux Bootstrap Script Contract Tests', () {
late String content;
setUpAll(() async {
expect(await scriptFile.exists(), isTrue, reason: 'Bootstrap script file must exist');
content = await scriptFile.readAsString();
});
test('should contain required flags', () {
expect(content, contains('--edge-url'));
expect(content, contains('--agent-id'));
expect(content, contains('--enrollment-token'));
expect(content, contains('--release-base-url'));
});
test('should contain optional/default flags and paths', () {
expect(content, contains('--agent-alias'));
expect(content, contains('--install-dir'));
expect(content, contains('--config-path'));
expect(content, contains('--workspace-root'));
expect(content, contains('--log-dir'));
expect(content, contains('--no-background'));
expect(content, contains('.oto/bin'));
expect(content, contains('.oto/agent/config.yaml'));
});
test('should support correct architecture assets', () {
expect(content, contains('oto-linux-x64.tar.gz'));
expect(content, contains('oto-linux-arm64.tar.gz'));
});
test('should write expected keys to configuration yaml', () {
expect(content, contains('agent:'));
expect(content, contains('id:'));
expect(content, contains('alias:'));
expect(content, contains('enrollment_token:'));
expect(content, contains('edge:'));
expect(content, contains('url:'));
expect(content, contains('runtime:'));
expect(content, contains('install_dir:'));
expect(content, contains('workspace_root:'));
expect(content, contains('log_dir:'));
});
test('should contain agent runtime commands and background behavior', () {
expect(content, contains('agent run'));
expect(content, contains('--config'));
expect(content, contains('nohup'));
expect(content, contains('oto-agent.pid'));
});
});
group('Linux Bootstrap Script Functional & Regressions Tests', () {
test('should fail when release base URL is not HTTPS', () async {
final result = await Process.run('bash', [
scriptFile.absolute.path,
'--edge-url', 'https://edge.example.com',
'--agent-id', 'test-agent',
'--enrollment-token', 'secret-token-123',
'--release-base-url', 'http://example.com/release', // HTTP URL
]);
expect(result.exitCode, isNot(0));
expect(result.stderr.toString(), contains('--release-base-url must use https://'));
expect(result.stdout.toString(), isNot(contains('Downloading OTO agent')));
// enrollment token이
expect(result.stdout.toString(), isNot(contains('secret-token-123')));
expect(result.stderr.toString(), isNot(contains('secret-token-123')));
});
test('should write PID to standard agent state path when custom config-path is specified', () async {
final tempDir = await Directory.systemTemp.createTemp('oto_bootstrap_test_');
try {
final tempHome = Directory('${tempDir.path}/home');
await tempHome.create(recursive: true);
// 1. OTO
final fakeOtoSource = Directory('${tempDir.path}/fake_oto_src');
await fakeOtoSource.create(recursive: true);
final fakeOtoFile = File('${fakeOtoSource.path}/oto');
await fakeOtoFile.writeAsString('#!/bin/sh\nwhile true; do sleep 1; done\n');
await Process.run('chmod', ['+x', fakeOtoFile.path]);
// 2. tar.gz
final fakeTarGz = File('${tempDir.path}/oto-linux-x64.tar.gz');
await Process.run('tar', ['-czf', fakeTarGz.path, '-C', fakeOtoSource.path, 'oto']);
// 3. curl
final fakeBinDir = Directory('${tempDir.path}/bin');
await fakeBinDir.create(recursive: true);
final fakeCurl = File('${fakeBinDir.path}/curl');
await fakeCurl.writeAsString('''#!/bin/sh
out_file=""
while [ \$# -gt 0 ]; do
if [ "\$1" = "-o" ]; then
out_file="\$2"
break
fi
shift
done
if [ -n "\$out_file" ]; then
cp "${fakeTarGz.path}" "\$out_file"
fi
''');
await Process.run('chmod', ['+x', fakeCurl.path]);
// 4. custom config path
final customConfigDir = Directory('${tempDir.path}/custom_config');
await customConfigDir.create(recursive: true);
final customConfigPath = '${customConfigDir.path}/my-config.yaml';
// PATH fakeBinDir , HOME을 tempHome으로
final env = Map<String, String>.from(Platform.environment);
env['PATH'] = '${fakeBinDir.path}:${env['PATH']}';
env['HOME'] = tempHome.path;
final result = await Process.run('bash', [
scriptFile.absolute.path,
'--edge-url', 'https://edge.example.com',
'--agent-id', 'test-agent',
'--enrollment-token', 'secret-token-123',
'--release-base-url', 'https://example.com/release',
'--config-path', customConfigPath,
], environment: env);
expect(result.exitCode, 0, reason: 'Script failed with: ${result.stderr}');
// 5.
final configFile = File(customConfigPath);
expect(await configFile.exists(), isTrue);
final expectedPidFile = File('${tempHome.path}/.oto/agent/oto-agent.pid');
expect(await expectedPidFile.exists(), isTrue, reason: 'PID file should exist in standard agent state path');
final unexpectedPidFile = File('${customConfigDir.path}/oto-agent.pid');
expect(await unexpectedPidFile.exists(), isFalse, reason: 'PID file should not exist in custom config path directory');
final pidStr = await expectedPidFile.readAsString();
final pid = int.tryParse(pidStr.trim());
if (pid != null) {
Process.killPid(pid);
}
} finally {
await tempDir.delete(recursive: true);
}
});
});
}