# ct 재사용·오프라인 후속 개발 인계

기록: 2026-09-30 16:36:24 KST / ltb1021 / test / work-session.
기준 source: `6c621870ad17d3775685165afb9237755b79d55a`, `task/runtime-lifecycle-refactor`.
범위: 현장 도구와 확인 사실 보존. **test 설치·범용화 구현·네트워크 전환은 하지 않았다.**

## 1. 다음 작업자가 먼저 읽을 것

1. 이 문서: 현재 경계, 구현 위치, test 이식 차이와 다음 행동.
2. [운영 가이드](ICERTI_OFFLINE_MAINTENANCE.ko.md): 실제 ct 명령과 보호 계약.
3. [소스 보관 안내](../../tools/icerti_offline/README.ko.md): 설치 recipe와 source-only export 구분.
4. [최근 구현 검증](../profiles/ltb1021/topics/runtime-lifecycle/validation/20260930T153901+0900-validation-field-recovery-tools.md).

영구 evidence root(test 서버, Git 밖):

```text
/data/codex-artifacts/contextra/field-maintenance/2026/2026-09-30_ct-reuse-preservation_r01/
```

`manifest.json`이 실제 파일·SHA256·보관 범위를 소유한다. `installed-inventory.json`은
설치 source hash, active 식별자, profile 구조, 서비스·mount·ACL·VSCode 설정의 허용 항목만 담는다.
`service-templates.redacted.json`은 launcher와 unit의 재구성 참고본이다. IP는 비식별화했으므로
그대로 설치하는 설정 파일이 아니다. 비밀번호/token/private key/DB/원본 미디어는 포함하지 않는다.
원본 임시 evidence는 삭제하지 않았으며 이 문서는 전체 대화 원문이나 서버 전체 복구 image가 아니다.

보존 결과: 설치 Python32파일/대응 source32파일 일치, source-manifest66파일 PASS.
영구 묶음에는 `source-6c621870ad17.tar.gz`, 현재 인계/운영 문서, 비식별화 진단3개,
설치·서비스 inventory와 기존 USB r03을 포함한다. USB r03의 SHA256을 기존 기록과 재대조했다.
USB는 과거 실행 asset 보관본이고 최신 ct source snapshot이 아니다. 후속 패치는 위 HEAD를 기준으로 한다.

## 2. 현재 상태와 중단 위치

| 항목 | 상태와 의미 |
| --- | --- |
| Git source | ct와 보조 도구·설치 recipe·표적 테스트가 위 HEAD에 보존됨 |
| 현장 설치 | `/usr/local/lib/contextra-maintenance/20260928-r01`; 패치일이 바뀌어도 prefix 이름은 그대로 |
| 설치 지문 | 설치 Python과 repository 대응 파일을 이번 수집에서 대조. 상세 개수·결과는 inventory 참조 |
| Runtime | test·현장 Contextra는 마지막 확인에서 모두 stopped. 자동 기동하지 않음 |
| 현장 active | `offline-source-20260928T165350`; 군중 패치를 포함한 후속 APP 적용 기준. 이전 transaction으로 덮어쓰지 않음 |
| 현장 selected | `office`, 회사망 DHCP. `field`는 폐쇄 현장용 고정주소 준비본 |
| 현장 pending | `field-20260930T155631-91276`; 연결 버전2, 로그인 session 있음, 발급 leaf 없음 |
| 현재 실패 | Web 인증서 발급 HTTP500 / `CERTIFICATE_SIGNING_NOT_CONFIGURED` |
| systemd | runtime unit은 enabled/failed 상태로 관측,5개 컨테이너는 stopped. 실패 상태를 초기화하거나 재기동하지 않음 |
| mount | 데이터 bind view 유지. 실제 디스크 이름을 test에 그대로 복사하지 않음 |

이번 수집은 현재 상태를 보존한 것이며 systemd 실패 원인 재조사나 Web 수정 작업이 아니다.
현재 NIC와 예전 active 앱 주소가 다른 상태이므로 `ct start`를 무조건 실행하지 않는다.
SSH 키와 기존 임시 sudo 파일은 사용자 삭제 지시 전까지 보존한다. Git/보존 묶음에 비밀값은 넣지 않는다.

## 3. 설치 구조와 소스 연결

```text
si 또는 root 터미널
  /usr/local/bin/ct
    -> /usr/local/lib/contextra-maintenance/20260928-r01/ct.py
       -> field_cli / field_profiles / field_runtime
       -> connection / network_prepare / network_apply
       -> bridge_tools의 Auth/TLS 코드
       -> Runtime deployment_kit + guarded active transaction
```

| 역할 | 구현 source |
| --- | --- |
| 명령 dispatcher, start/stop, 메뉴 | `tools/icerti_offline/maintenance/ct.py` |
| 프로필 형식·원자적 저장·si 권한 | `field_profiles.py` |
| 설정 준비·적용·지원 수동 변경 수용·복구 | `field_runtime.py`, `field_cli.py`, `maintenance_guard.py` |
| 주소/CA 계획·enroll·Compose/방화벽 반영 | `connection.py`, `network_prepare.py`, `network_apply.py` |
| 연결 개정 번호 | `connection_versions.py` |
| 계정 변경, 기존 인증 작업의 복구 | `auth_control.py` |
| 로그인·CSR 발급 통신 | repository `tools/secure_bridge_enroll.py`, `control_sidecar/auth.py`, `control_sidecar/config.py` |
| CA 탐색, USB 선택·마운트·해제 | `field_ca.py`, `field_usb.py` |
| OS 네트워크 프로필·ARP 진단 | `field_network.py` |
| 언어, 오류 안내 | `field_ui.py`, `field_diagnostics.py` |
| 자원 진단·PID 보호·timeout 회수 | `field_resources.py`, `maintenance_process.py` |
| VSCode/Python 보호, 복구 | `vscode_safety.py` |
| ACL | `workspace_access.py`, `tools/icerti_offline/install_access.py` |
| APP image, source, 현장 작업 기록 | `apply_source.py`, `source_prepare.py`, `work_record.py` |
| 현장 설치 | `install_field.py` 및 `install_*.py`; 기존 설치본 hash와 대조 후 exact 파일만 반영 |

표에서 생략한 디렉터리는 `tools/icerti_offline/maintenance/`다.
Runtime deployment kit는 repository root의 `deployment_kit/`이며 현장에 versioned copy로 배치돼 있다.
`ct`만 복사해서 동작하는 단일 파일 도구가 아니다. vendor Auth/TLS, 설치 wrapper, Runtime state,
Compose/env, mount guard와 기존 배포 도구가 함께 필요하다.

## 4. 설정 원본·보호 상태의 구분

| 경로 | 의미 / 직접 편집 경계 |
| --- | --- |
| `/workspace/contextra/setup/profiles/office.json`, `field.json` | 사람이 편집하는 일반 원본. 메뉴와 VSCode가 같은 파일 사용 |
| `profiles/<profile>.private/<web>.credentials.json` | ID/비밀번호. owner 전용, `ct profile credentials` 권장 |
| `profiles/<profile>.private/<web>.ca.crt` | 선택 Web의 공개 CA, `ct ca`로 반입 |
| `/var/lib/contextra-maintenance/field/selected.json` | 선택 프로필, 관리 도구가 소유 |
| `field/pending.json`, `apply-pending.json`, `network-pending.json` | 중단·재개 상태. 수동 삭제나 SHA 수정으로 우회하지 않음 |
| `field/connections/` | Web origin·serverId별 로컬 준비/성공 버전 이력. 원격 최신값의 보증 아님 |
| `field/language.json`, `field/vscode-safety/` | 언어 선택·VSCode 관리 키 백업 |
| `/workspace/contextra/runtime/state/site.json`, `active.json` | 실행 기준. 지원 항목 수용은 `ct emergency`; 코드/image 보호까지 해제하지 않음 |
| `runtime/state/transactions/<transaction>/compose.json` | 고정된 실행 Compose. 직접 변경 후 hash만 맞추지 않음 |
| `/workspace/contextra/data/site/runtime.env`, `secure-bridge.env` | 실행 env. 전체 내용을 출력·Git 보관하지 않음 |
| `/workspace/contextra/setup/outbox/field-change-*/` | SUMMARY.ko.md, changes.redacted.json, verification.json과 반출 ZIP |
| `/etc/netplan/*.yaml` | OS NIC 설정. 앱 주소 설정과 별개이며 SSH 단절 가능 |

현재 프로필 root keys: `version,id,label,network,webs,selected_web,redis_allow,control_port,redis_port,artifact_lan_cidr,media`.
`network`: `interface,mode,address,gateway,dns,ssh_port`.
`webs[]`: `id,label,host,manager,event_url,server_id`, 선택적 `connection_version`.
프로필의 `version=1`은 파일 형식이다. 요청의 `connectionConfigVersion`과 혼동하지 않는다.
빈 Enter는 현재값 유지/추천값 사용 중 무엇인지 안내에 따른다. 지우기는 별도 선택이다.

## 5. 주소·계정·인증 적용 흐름

1. 프로필·공개 CA·AI 계정 준비. 계정 저장만으로 로그인하지 않는다.
2. `ct config status` / `ct config version status`: 현재값·pending·주소/지문 비교.
3. 필요 시 명시적 `ct config version 번호`. 준비본이 있으면 먼저 결과와 발급 상태를 확인한다.
4. `ct config prepare`: 현재 profile와 보호 기준으로 별도 stage/키/CSR 생성. 아직 Runtime 적용 없음.
5. `ct config connect`: 명시 CONNECT 뒤 로그인·접속정보 등록, 이어 인증서 발급 요청.
6. `ct config apply`: 발급·identity·IP SAN·지문 검사 후 로컬 실행 설정 반영. 서비스는 stopped 유지.
7. `ct start`: 실제 mount/NIC/transaction/source 검증 후 서비스 기동. 카메라 자동 Start 없음.

동일 Web/AI 계정의 더 큰 유효 연결 버전은 변경된 접속정보를 갱신한다. 같은 버전/같은 내용은
동일 요청으로 취급하고, 같은 버전/다른 내용은 충돌, 작은 버전은 stale이다. 허용 범위는
`1..9007199254740991`의 정수. 매 재부팅/실패마다 무조건 올리거나 원격값을 추측하지 않는다.
`auto`는 로컬 이력 기반이다. 다른 장비가 같은 ID를 갱신한 원격 상태는 알 수 없다고 표시한다.

실패한 stage·intent를 보존한다. `cancel`은 발급/session 없는 명확한 미발급 준비본의 선택 해제다.
현재처럼 session이 생긴500 단계는 무조건 cancel하지 않는다. 원인 해결 뒤 같은 stage의 명시적
`retry`를 사용하며 자동 반복하지 않는다. 로그인 성공과 인증서 성공, WSS 성공은 서로 다르다.

## 6. 오늘 확인한 연결 실패와 재개

| 순서 | 확인 사실 | 조치/현재 경계 |
| --- | --- | --- |
| 회사망 TCP/TLS | Web 주소가 잘못된 neighbor MAC으로 해석됨. 검증 MAC 임시 매핑 후 TCP/TLS 통과 | 회사망 커널 예외일 뿐 부팅 영속 설정 아님. 현장으로 복사 금지 |
| 로그인400 | 이전 Web 등록은 test 주소/version1, 현장 요청은 다른 주소/version1 | VERSION_CONFLICT 확인; 도구 버전 관리 추가 |
| 사용자 재시험 | 실패 준비본 보존·선택 해제, version2 readback, prepare 성공 | 새 stage 보존 |
| version2 connect | 로그인 session 생성, Web DB에 새 주소와 version2 반영 | 주소 갱신 계약의 실제 성공 |
| 인증서500 | CERTIFICATE_SIGNING_NOT_CONFIGURED | Web signer 설정 누락. AI의 CA 부족이나 새 계정 문제 아님 |
| 추가 진단 | 기존 session으로 발급 불가능한 invalid CSR1건; 동일한 상세 reason 반환 | 새 로그인/인증서 발급 없음 |
| test 비교 | 기존 leaf가 Gateway에 mount, 유효기간2026-09-18~2027-09-18, 같은 CA로 chain/IP 검증 통과 | 기존 인증서 사용과 신규 발급은 별개. test는 계속 stopped |

Web에 필요한 설정은 `PLATFORM_MANAGER_CA_CERT_FILE`과 `PLATFORM_MANAGER_CA_KEY_FILE`이다.
CA 공개본은 AI에도 배포하지만 CA 서명키는 Web에만 둔다. AI는 자체 key/CSR를 만들며 Web이 leaf를 발급한다.
이번 Web에는 기존 CA·키가 존재하고 일치·읽기 가능하며 실행 env/.env에 경로가 빠져 있다.
Web 담당자가 경로를 설정하고 실제 Web 앱에 반영해야 한다. DB 변경/새 CA/새 계정/버전3은 해결책이 아니다.
Windows Docker에서는 CA·키 mount와 컨테이너 내부 경로의 환경변수가 필요하다. Compose 환경변수/mount
변경은 단순 restart가 아니라 해당 Web 컨테이너 재생성이 필요하다. 현장 Web의 설정·image는 미검증이다.
Web 수정은 이번 작업에서 하지 않았다. 과거401 원인과9/18 최초 발급 경로는 확정하지 않았다.

## 7. test에 그대로 복사하면 안 되는 부분

| 구분 | 현장 현재 전제 | test 재현 시 결정할 것 |
| --- | --- | --- |
| 계정 | `si`, `/home/si`, sudo 일부 승격 | 실제 operator 계정/UID/GID/home. root·다른 계정 진입도 검증 |
| project | `contextra-icerti`, 고정5서비스·container명 | 기존 test의 `contextra`와 절대 혼동하지 않는 대상 지정 |
| Runtime root | `/workspace/contextra/runtime` + guarded state/unit | test의 repo/Compose 기반 실행을 인식할 adapter 또는 별도 격리 fixture |
| 설정 root | `/workspace/contextra/data/site` | test `/workspace/contextra-runtime-config`와 기존 파일별 실제 mount |
| physical 데이터 | `/training/contextra/models`, `/backup/contextra/{artifacts,training,...}` | test의 `/data/contextra/...`; 디스크 UUID와 컨테이너 내부 경로 구분 |
| NIC/SSH | 설치 때 선택한 유선 NIC, 현장 SSH1023 | test actual NIC·포트·DHCP를 조회. NIC 변경은 기본 미적용 |
| 방화벽 | 현장 Redis allowlist/unit | test의 기존 규칙 소유권과 다른 서비스 보존 |
| VSCode/USB | si 원격 설정·media 경로·PID UID | operator에 맞게 변환. `/backup`·`/training` 보호 유지 |
| LLM/관측/GUI | 현장 설치 binary/image/cache와 선택적 실행 | ct core와 분리. test에 없다는 이유로 자동 설치하지 않음 |

현장 `install_field.py`, `ct.py`, `field_profiles.py`, `connection.py`, `field_runtime.py`,
`network_prepare.py`, `network_apply.py`, `apply_source.py`, `work_record.py`, `workspace_access.py`,
`vscode_safety.py`, `field_resources.py`, `field_usb.py` 등에 위 가정이 분산돼 있다.
문자열을 전역 치환하거나 test 경로를 현장 경로로 symlink해서 보호 검사를 통과시키지 않는다.

범용화 추천안(아직 미구현): 경로·operator·Compose project/service·runtime backend·NIC를
하나의 검증된 서버 설정에서 공급하고 기존 기능별 로직은 재사용한다. 환경 프로필(회사/폐쇄망)과
서버 설치 구조(test/현장)는 다른 축이다. 하나의 office 프로필로 두 서버의 경로 차이를 숨기지 않는다.
기존 test Runtime에 적용하기 전 읽기 전용 discovery와 격리 fixture로 검증하고 실반영은 별도 승인받는다.
같은 Web AI 계정으로 test·현장을 동시에 등록하지 않는다. 현재 원격 등록은 현장으로 바뀌었다.

## 8. 의존성·보존물·오프라인 패치

- 핵심 도구는 Python 표준 라이브러리와 시스템 명령을 사용한다. 현장 Python, bash, sudo,
  Docker/Compose, OpenSSL, systemd, iproute2, Netplan, util-linux, acl 명령이 전제다.
- 표적 테스트는 Python3.12로 실행했다. 설치 전 `pwd.getpwnam(operator)`와 실제 도구 존재를 확인한다.
- source-manifest는 현재66파일을 검증한다. 초기 README의51파일은 초기 snapshot 설명이며 최신 count가 아니다.
- source-only export는 설치/다운로드/서비스 조작을 하지 않는다. 단독으로 서버 전체 복구본은 아니다.
- `install_*.py`는 기존 현장 단계 recipe다. test에 순서대로 실행하거나 설치 완료 서버에 일괄 재실행하지 않는다.
- 설치 변경은 현재 installed hash, candidate hash, root 전용 이전 파일 백업, readback을 유지한다.
- `/workspace/contextra/source/...`는 편집본이고 Runtime에 host app bind하지 않는다. `ct apply`는
  기존 SDK/native/model 위에 Python APP만 담은 작은 offline image를 만들며 snapshot/hash로 적용한다.
- source snapshot, old image/config, 현장 `ct work` 기록과 사용자 수정을 대조해 다음 USB 패치를 만든다.
  dependency/SDK/model 변경은 APP-only patch로 위장하지 않는다.
- 기존 USB r03에는 실행기·일부 GUI package·관측 image가 포함된다. 이후 ct 변경은 최신 Git source가 우선이다.
  GGUF, 전체 Runtime image, 모델 engine, DB, 원본자료, 인증 비밀은 별도이며 이번 문서 보존에 포함하지 않는다.

## 9. 보호 계약

- Runtime stop/reap·source/image hash·데이터 mount 누락 보호는 범용화에서도 유지한다.
- 긴급모드는 지원 설정의 사용자 변경을 검사·수용하는 기능이지 모든 SHA/권한을 무시하는 스위치가 아니다.
- 인증/session/private env는600/700 등 기동 필수 보호를 유지한다. 일반 경로는 named/default ACL로 접근한다.
- USB는 명시 선택, `ro,nosuid,nodev,noexec`. 해제는 device identity와 mount를 재검증한 일반 umount만 사용한다.
  `/backup`, `/training`, OS·데이터·하위 mount 보호. force/lazy/unmount-all 금지.
- wait-online은 현재 NIC 기준60초 bounded 대기. OS 전체 부팅시간60초 보장을 의미하지 않는다.
- 회사망 ARP 임시 예외는 부팅 뒤 없어질 수 있다. MAC 자동 추정/다른 현장 전파 금지.
- VSCode `/` 탐색·파일 접근 유지, npm 자동탐색 Off, 가상FS/데이터 검색 제한, Pylance light/openFilesOnly/indexing Off.
  extensionHost만 사용자 저장 확인 뒤 pidfd/start-time/UID 검증으로 종료. SSH·터미널 전체 kill 금지.
- `ct` 소유 subprocess는 timeout/cancel에 process group 회수. 외부 process는 제한된 명시 선택만 가능하다.
- 영구sudo NOPASSWD, chmod777, 무차별 chown, cache drop, 전역 pkill, 과거 Event 재전송은 도입하지 않는다.

## 10. 검증·사용자 수용·미완료

완료 근거: 구현 때 Python3.12 maintenance72건 + manifest4건 PASS, 설치18파일/CLI10항목 검증,
사용자 extensionHost 재시작·편집 화면 정상. 이번에는 source66파일 확인과 설치 지문 수집을 재사용했다.
Runtime 실연동을 문서화 작업에서 다시 수행하지 않았다.

| 남은 항목 | 상태 |
| --- | --- |
| Web signer 설정 후 leaf 발급·apply·WSS·Redis | 대기. 현재500 원인을 먼저 해결 |
| test용 ct 설치 구조 adapter | 미구현. 위7절이 다음 구현 범위 |
| 현재 profile의 직접 편집·긴급 수용·재부팅 실수용 | 사용자 동반 시험으로 남음 |
| USB 선택 해제·SSH CA 반입·물리 콘솔 언어 | 코드/표적 보호 검증과 실제 사람 조작을 구분 |
| UX-VERSION-01 | version 지정 시 저장 전 auto를 먼저 출력. 저장 후 실제값 표시 개선 후보 |
| UX-CERT-01 | safe_error가 certificate details.reason을 생략. 허용 코드 전달 개선 후보 |
| 한국어 화면의 영어 안내 혼재 | 사용자 feedback 기반 개선 후보 |
| Gateway 종료137 | 별도 경고 추가. 실제 graceful 종료 원인/개선 검증은 미완료 |
| VSCode 장기 재발·모든 Python 기능 | 미검증. workspace override/JSONC 수동 merge 경계 유지 |
| 현장 고정망 전체 시험 | 상대 PC/격리망 준비 어려워 SKIP. 회사망 성공을 현장망 실검증으로 표현하지 않음 |

초기 수동시험 MT-01~14와 UX 항목을 이어간다. 한 번에1~2명령, 예상 결과와 영향 설명,
사용자 출력 확인 뒤 다음 단계. 게임북형 상세 가이드/PDF는 수용 결과를 반영해 후속 확정한다.

## 11. 재개 요청문

```text
profile: ltb1021
server: test
codex_docs_repo: /workspace/users/root/codex-ops-docs

Contextra docs/deployment/CT_REUSE_HANDOFF.ko.md와 영구 evidence manifest부터 읽어줘.
actual HEAD/status/ownership을 확인하고 기존 사용자 변경을 보존해줘.
현장 설치 전제를 test 경로/operator/Compose 구조로 분리하는 최소 설계를 먼저 제시해줘.
현장 installer 재실행·test Runtime 기동·Web 로그인·네트워크/권한 변경은 자동으로 하지 마.
source-only/fixture 검증과 실제 설치/실연동을 구분하고, 다음 승인 범위를 잠근 뒤 구현해줘.
```

현재 네트워크가 끊겨도 위 source와 허용 항목 inventory로 분석·후속 구현을 시작할 수 있다.
실제 서버 전체 복구나 모든 현장 상태의 재현이 검증됐다는 뜻은 아니다.
