# 중단·복귀·로그 반출

검토: @DATE@ KST. 원칙은 **실패 상태 보존 → 원인/현재 단계 확인 → 명시 선택**이다.
무조건 재시도/자동 rollback/자동 Start하지 않는다. SSH와 네트워크를 유지한다.

## 먼저 남길 것

화면의 오류 code, 마지막 단계, 로컬 기록 경로, 실행 시각을 메모한다.
비밀번호·token·인증 URI·전체 shell history는 공유하지 않는다.

- 명령마다 `/var/lib/contextra-maintenance/offline-attempts/attempt-...`에 기록한다.
- 적용 원장: `/var/lib/contextra-maintenance/contextra/offline/test-demo-20261103-r02`.
- 원장의 `private`에는 민감 설정 복귀본이 있다. 이 폴더를 통째로 반출하지 않는다.
- 기록 쓰기도 실패하면 `LOG_WRITE_FAILED`가 표시된다. 화면의 code와 시각부터 보존한다.

## 오류별 다음 행동

| 표시/상황 | 다음 행동 |
| --- | --- |
| HASH/FILE/ARCHIVE/SOURCE 관련 검사 실패 | 실행 중단. 원본과 복사본을 보존하고 누락/손상 경로를 공유 |
| WRONG_TARGET / LEGACY_INVENTORY_REQUIRED / BACKEND_UNSUPPORTED | 다른 현장에 억지 적용하지 말고 inventory 결과를 반출 |
| ROOT_RESERVE / IMAGE_LOAD_SPACE / STAGE_SPACE | 더 진행하지 않음. 자동 정리/daemon 변경 금지 |
| OUTPUT_EXISTS / EXISTING_RUN | 기존 자료를 삭제하지 말고 해당 작업 상태 확인 |
| BASE_CHANGED / FILE_CONFLICT / PROTECTED_DRIFT | 이후 사용자/다른 세션 변경을 보존. 보호 검사 우회 금지 |
| MAINTENANCE_REQUIRED | 일부 서비스가 남아 있음. 기존ct 정상 Stop을 확인, 강제 종료하지 않음 |
| READINESS_TIMEOUT / FINAL_IMAGE_STATE | 현재 컨테이너 상태와 로컬 기록을 보존. 재개 또는 복귀를 명시 선택 |
| NO_RUNTIME_CHANGE | Runtime 설정을 바꾸기 전이다. 기존 실행의 재개 필요 여부를 확인하고 이전ct 절차를 사용 |
| USB 쓰기 실패/분리/용량 부족 | 로컬 원장은 유지됨. 정상 반출 폴더를 다시 선택하며 운영 작업은 반복하지 않음 |

상태 조회도 패키지 전체를 검증하므로 즉시 끝나지 않을 수 있다. 기록 상태와 실시간 health는 다르다.

```bash
STAGE=/data/contextra-offline/test-demo-20261103-r02
DIGEST=@PACKAGE_SHA@
sudo env PYTHONDONTWRITEBYTECODE=1 python3 "$STAGE/ct-offline" status \
  --package "$STAGE" --sha256 "$DIGEST"
```

## 명시적 재개

실제 현재 상태와 기록이 일치하고 원인이 해소된 경우에만 `resume`을 선택한다.
서비스가 정상 정지돼 있어야 한다. 입력/설정 충돌을 정상으로 가정하지 않는다.

```bash
sudo env PYTHONDONTWRITEBYTECODE=1 python3 "$STAGE/ct-offline" resume \
  --package "$STAGE" --sha256 "$DIGEST"
```

확인 문구는 `APPLY`다. 완료된 작업을 재적용하거나 복귀한 작업을 다시 사용하는 기능이 아니다.

## 이전 정상 버전 복귀

이 패키지는 기존ct launcher를 교체하지 않는다. 새ct-offline에 문제가 생겨도 기존 정상 정지 진입점은 남는다.
복귀 전에 **실패 근거를 반출하거나 서버에 보존**한다.

```bash
sudo /usr/local/bin/ct stop
```

정지가 정상 완료됐을 때만:

```bash
sudo env PYTHONDONTWRITEBYTECODE=1 python3 "$STAGE/ct-offline" rollback \
  --package "$STAGE" --sha256 "$DIGEST"
```

확인 문구는 `RESTORE`다. 정상 기대는 `ROLLED_BACK`, `rollback_verified: true`와 process readiness다.
이전 image·이번에 변경한 설정·실행 기준을 복원하고 실제 서비스 준비까지 확인한다.
DB·Event·영상·인증자료를 과거로 일괄 덮지 않는다. 이후 사용자 설정과 충돌하면 중단한다.
root15GiB와 이전 image 보존이 필요하다. 복귀에도 전체 패키지 검사가 있어 즉시 완료를 보장하지 않는다.

`apply-pending.json`을 손으로 지우거나 `ct config restore`에 넘기지 않는다.
old/new 상태를 모른 채 `docker compose up`, 강제 image tag 변경으로 우회하지 않는다.

### staging/새 도구가 손상됐을 때

보관한 **동일 지문의 USB 원본**으로3단계 bootstrap 검사를 다시 수행한다.
새 로컬 경로에 stage하고 그 복사본의 `ct-offline`로 동일 release 원장을 사용해 rollback한다.
손상된 staging과 로컬 원장은 지우지 않는다. 이때도 기존ct Stop을 먼저 확인한다.
USB 자체에서 apply/rollback하지 않는다. 원격 복귀가 불가능하면 반복 조작 대신 화면과 code를 공유한다.

Runtime 변경 전 실패로 `NO_RUNTIME_CHANGE`가 확인됐고 기존 Contextra만 정지한 상태라면,
사용자가 이전 정상 서비스 재개를 선택해 `sudo /usr/local/bin/ct start`를1회 실행할 수 있다.
이 명령도 보호 검사에 막히면 중단한다. pending 파일 삭제나 반복 Start로 우회하지 않는다.

## 기록 반출

서버의 별도 기존 폴더 또는 사용자가 명시적으로 쓰기를 허용한 USB 반출 폴더를 선택한다.
읽기 전용 USB를 도구가 자동 remount하지 않는다. USB가 없으면 로컬 폴더로 먼저 반출해도 된다.
다음 명령은 목적지 절대경로를 질문한다. `EXPORT` 확인 후 고유한 하위 폴더가 생성된다.

```bash
sudo env PYTHONDONTWRITEBYTECODE=1 python3 "$STAGE/ct-offline" export \
  --record /var/lib/contextra-maintenance/contextra/offline/test-demo-20261103-r02
```

준비 단계 이전 오류는 `--record`에 화면에 표시된 해당 `offline-attempts/attempt-...`를 넣는다.
반출은 package manifest 검사 실패와 독립적으로 사용할 수 있지만, 실행 코드 자체는 신뢰할 복사본이어야 한다.
결과는 `SUMMARY.ko.md`, `result.json`, `SHA256.json`이다. 로컬 민감 복귀본은 포함하지 않는다.
반출본과 사용자 메모를 먼저 분석하고, 이후 test 직접 조회와 대조해 부족한 정보를 다음 도구에 반영한다.
