You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
11 KiB
204 lines
11 KiB
|
1 month ago
|
# 배포 및 버전 관리 운영 절차서
|
||
|
|
|
||
|
|
이 문서는 NEW Web Configurator의 버전 관리 정책, 릴리즈 절차, 배포 워크플로우, 검증, 롤백, 디바이스 아키텍처, 트러블슈팅을 다룹니다. 신규 버전을 디바이스에서 실행하는 방법에 대한 단일 참조 문서입니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. 개요
|
||
|
|
|
||
|
|
NEW Web Configurator는 여러 현장의 IoT 디바이스에 배포되는 Python stdlib HTTP 서버입니다. 배포는 `scripts/deploy.ps1` — PowerShell 스크립트를 사용합니다. 이 스크립트는 커밋된 `src/` 트리를 아카이브로 묶어 SSH로 전송하고, 디바이스 상의 기존 트리를 백업 및 교체한 뒤, 배포된 버전을 기록하고 systemd 서비스를 재시작합니다. 모든 프로덕션 배포는 반드시 `vMAJOR.MINOR.PATCH` 릴리즈 태그와 연결되어야 하므로, 어떤 디바이스에서든 실행 중인 정확한 버전을 언제나 식별할 수 있습니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 버전 관리 정책
|
||
|
|
|
||
|
|
### 2.1 버전 체계
|
||
|
|
|
||
|
|
세 부분으로 구성된 시맨틱 버전 관리: **`vMAJOR.MINOR.PATCH`**.
|
||
|
|
|
||
|
|
| 구분 | 올려야 하는 경우 |
|
||
|
|
|---|---|
|
||
|
|
| **MAJOR** | 하위 호환이 깨지는 변경 — 예: 기존 디바이스를 망가뜨리는 `board_config` 스키마 변경, 또는 API 삭제/이름 변경. |
|
||
|
|
| **MINOR** | 새로운 하위 호환 기능 추가 — 예: 새 설정 페이지, 랜딩 대시보드, WiFi 국가 코드 드롭다운. |
|
||
|
|
| **PATCH** | 신규 기능 없는 버그 수정 또는 소규모 수정 — 예: `db_manager` 재시도 수정, 표시 버그 수정. |
|
||
|
|
|
||
|
|
### 2.2 단일 진실 소스(Single Source of Truth)
|
||
|
|
|
||
|
|
`src/static/js/constants.js`의 `APP_VERSION`이 애플리케이션 버전의 단일 진실 소스입니다. 사이드바에 표시되며, 릴리즈 시점에 git 태그와 반드시 일치해야 합니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 릴리즈 절차
|
||
|
|
|
||
|
|
새 릴리즈를 만들려면:
|
||
|
|
|
||
|
|
1. **버전 올림 결정** — 변경 내용이 MAJOR, MINOR, PATCH 중 어느 것에 해당하는지 결정합니다(§2.1 표 참조).
|
||
|
|
2. **`APP_VERSION` 업데이트** — `src/static/js/constants.js`에서 `APP_VERSION`을 새 버전 문자열로 설정합니다(예: `'v1.0.1'`).
|
||
|
|
3. **`CHANGELOG.md` 항목 추가** — `CHANGELOG.md` 상단에 `## [vX.Y.Z] — YYYY-MM-DD` 형식으로 변경 내용을 기술하는 섹션을 추가합니다.
|
||
|
|
4. **커밋** — `constants.js`와 `CHANGELOG.md`를 함께 커밋합니다.
|
||
|
|
5. **태그** — annotated 태그를 생성합니다:
|
||
|
|
```
|
||
|
|
git tag -a vX.Y.Z -m "Release vX.Y.Z — <one-line summary>"
|
||
|
|
```
|
||
|
|
|
||
|
|
태그는 릴리즈 시점에만 생성합니다. 개발 중간에 태그를 붙이지 마십시오. 태그는 배포되는 커밋을 정확히 표시합니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 배포
|
||
|
|
|
||
|
|
### 4.1 사전 요구사항
|
||
|
|
|
||
|
|
- 다음이 갖춰진 **Windows 개발 호스트**:
|
||
|
|
- OpenSSH `ssh` 및 `scp` (Windows 10/11 기본 포함 또는 Git for Windows).
|
||
|
|
- `PATH`에 등록된 `git`.
|
||
|
|
- `root@<device>`에 인증된 SSH 키.
|
||
|
|
- **클린(clean)** 상태의 로컬 워킹 트리 (`git status`에 변경 사항 없음).
|
||
|
|
- **`vX.Y.Z` 태그에 위치한 HEAD** (플래그 처리된 개발 빌드에는 `-AllowUntagged` 사용).
|
||
|
|
|
||
|
|
### 4.2 사용법
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
.\scripts\deploy.ps1 <device-ip>
|
||
|
|
.\scripts\deploy.ps1 <device-ip> -AllowUntagged
|
||
|
|
.\scripts\deploy.ps1 <device-ip> -Rollback
|
||
|
|
```
|
||
|
|
|
||
|
|
예시:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
.\scripts\deploy.ps1 192.168.55.56
|
||
|
|
.\scripts\deploy.ps1 192.168.55.56 -AllowUntagged
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.3 배포 흐름 (스크립트 동작 상세)
|
||
|
|
|
||
|
|
1. **사전 점검(Pre-flight)** — SSH 도달 가능 여부 확인(`ssh … echo ok`), 로컬 워킹 트리가 클린 상태인지 확인, HEAD가 버전 태그에 위치하는지 확인(버전 태그가 아니면 중단, `-AllowUntagged`가 전달된 경우 제외).
|
||
|
|
2. **클린 아카이브 빌드** — `git archive --output=<temp>.tar HEAD src`를 실행하여 `src/` 하위의 커밋된 파일만 캡처 — `__pycache__`, `*.bak.*`, 미추적 파일은 제외.
|
||
|
|
3. **전송** — `scp`로 임시 tar 파일을 디바이스로 복사하고, 디바이스에서 스테이징 디렉터리에 압축 해제.
|
||
|
|
4. **백업** — 현재 디바이스의 `src/`를 `backups/src-<timestamp>/`로 복사하고, 최근 3개 백업만 유지하도록 정리.
|
||
|
|
5. **systemd 유닛 및 헬퍼 스크립트 설치** (멱등성 보장 — 해시 비교 후 변경된 경우에만 재복사; src 교체 이전에 수행하므로 실패 시 기존 src가 활성 상태를 유지). 모든 유닛은 영구 경로 `/lib/systemd/system/`에 설치됩니다(`/etc/systemd/system/` 오버레이는 tmpfs라 재부팅 시 초기화됩니다). 설치 항목:
|
||
|
|
- `web-configurator.service` (메인 유닛; 첫 배포 시 `systemctl enable`도 수행).
|
||
|
|
- `dpworld-net-recover.service` — 네트워크 적용 엔진이 사용하는 `modprobe` 전용 복구 유닛.
|
||
|
|
- `dpworld-network-apply.service.d/10-ondemand.conf` — 펌웨어 적용 유닛의 부팅 전용 `Requires`를 온디맨드로 재정의하는 드롭인.
|
||
|
|
- **펌웨어 부팅 하드닝(v1.7.1+)**: 하드닝된 부팅 스크립트 `deploy/dpworld-network-apply-hardened.sh` → `/usr/bin/`으로 복사, 그리고 두 개의 드롭인(`dpworld-network-apply.service.d/20-hardened.conf` 및 `dpworld-network-seed.service.d/20-hardened.conf`)이 펌웨어 seed/apply 유닛을 하드닝된 스크립트로 연결(라이브 `modprobe -r` 없음 — 국가 코드는 재부팅 시 적용됨). 펌웨어 원본 `/usr/bin/dpworld-network-apply.sh`는 수정하지 않습니다. [firmware-boot-hardening.md](firmware-boot-hardening.md) 참조.
|
||
|
|
- **Wi-Fi AP 유닛**: `deploy/dpworld-ap-apply.sh` → `/usr/bin/`으로 복사, 그리고 네 개의 유닛(`dpworld-ap-seed`, `dpworld-ap-apply`, `dpworld-hostapd-ap0`, `dpworld-udhcpd-ap0`). `dpworld-ap-seed.service`만 enable하며, 나머지는 apply 스크립트에서 온디맨드로 시작됩니다.
|
||
|
|
- 엔진/OTA 디렉터리도 사전 생성합니다(`/opt/fw_staging`, `/opt/fw_upload`, `/opt/config_backups`, `/home/root/network`, `/opt/config_backups/network`).
|
||
|
|
6. **교체(Swap)** — 디바이스의 `src/`를 새로 압축 해제된 트리로 교체(`rm -rf src && mv <staging>/src src`); 스테이징 tar와 디렉터리를 삭제.
|
||
|
|
7. **버전 기록** — `DEPLOYED_VERSION`을 기록하고 `deploy-history.log`에 한 줄을 추가합니다(§5 참조).
|
||
|
|
8. **재시작** — `systemctl restart web-configurator`를 실행합니다.
|
||
|
|
9. **검증** — 최대 약 15초 동안 폴링: `systemctl is-active web-configurator`가 `active`인지, `:9090`으로 HTTP 요청 시 200이 반환되는지 확인합니다. 성공 또는 실패를 보고합니다. 재시작/검증이 실패하면 스크립트가 4단계에서 생성한 `backups/src-<timestamp>/`로 자동 롤백합니다.
|
||
|
|
|
||
|
|
> **참고:** HEAD가 정확히 `vX.Y.Z` 태그에 있지 않으면 배포가 거부됩니다. 미태그 커밋을 플래그 처리된 개발 빌드로 배포하려면 `-AllowUntagged`를 전달하십시오(버전은 `vX.Y.Z-dev+<short-sha>`로 기록됩니다).
|
||
|
|
|
||
|
|
이 스크립트는 nginx, Java app-runner, SQLite 데이터베이스, dpworldapp 바이너리를 **절대 건드리지 않습니다**. 단, 5단계에 나열된 systemd 유닛과 헬퍼 스크립트를 설치/갱신하며(멱등성 보장, 해시가 다를 경우에만), 펌웨어 소유의 `/usr/bin/dpworld-network-apply.sh`는 수정하지 않습니다 — 하드닝은 원본을 수정하지 않고 드롭인을 통해 적용됩니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. 배포 검증
|
||
|
|
|
||
|
|
배포 성공 후, 디바이스에서 실행 중인 내용을 확인합니다:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
ssh root@<ip> cat /opt/web-configurator/DEPLOYED_VERSION
|
||
|
|
```
|
||
|
|
|
||
|
|
이 파일은 매 배포 시 덮어쓰이며, 다음 내용을 포함합니다:
|
||
|
|
|
||
|
|
```
|
||
|
|
version=v1.0.0
|
||
|
|
tag=v1.0.0
|
||
|
|
commit=<full-sha>
|
||
|
|
deployed_at=<ISO-8601 timestamp>
|
||
|
|
deployed_by=<user>@<host>
|
||
|
|
```
|
||
|
|
|
||
|
|
배포 이력을 확인하려면:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
ssh root@<ip> cat /opt/web-configurator/deploy-history.log
|
||
|
|
```
|
||
|
|
|
||
|
|
각 줄의 형식: `<timestamp> <version> <commit-short> <deployed_by>`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. 롤백
|
||
|
|
|
||
|
|
디바이스의 가장 최근 백업으로 복원하려면:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
.\scripts\deploy.ps1 <ip> -Rollback
|
||
|
|
```
|
||
|
|
|
||
|
|
이 명령은 가장 최근의 `backups/src-*` 스냅샷을 `src/`에 복원하고, 서비스를 재시작하여 정상 여부를 검증한 뒤, 롤백이 발생했음을 기록하도록 `DEPLOYED_VERSION`을 다시 기록합니다(복원된 백업 타임스탬프 포함). 백업 스냅샷은 롤백 후에도 삭제되지 않습니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. 디바이스 아키텍처
|
||
|
|
|
||
|
|
| 항목 | 값 |
|
||
|
|
|---|---|
|
||
|
|
| 앱 디렉터리 | `/opt/web-configurator/` |
|
||
|
|
| systemd 서비스 | `web-configurator.service`가 `src/server.py` 실행 |
|
||
|
|
| Python 설정 서버 포트 | **9090** |
|
||
|
|
| Nginx (리버스 프록시) | 포트 **80** |
|
||
|
|
| Java app-runner | 포트 **8080** |
|
||
|
|
| 디바이스 — 프로덕션(메인) | `192.168.55.56` (유선 `eth1`) |
|
||
|
|
| 디바이스 — 개발/검증(보조) | `192.168.55.54` |
|
||
|
|
| Python 런타임 | Python 3.10 |
|
||
|
|
| SSH 사용자 | `root` |
|
||
|
|
|
||
|
|
디바이스 주요 경로:
|
||
|
|
|
||
|
|
| 경로 | 용도 |
|
||
|
|
|---|---|
|
||
|
|
| `/opt/web-configurator/src/` | 실행 중인 애플리케이션 소스 |
|
||
|
|
| `/opt/web-configurator/backups/` | 디바이스 내 백업 스냅샷(최근 3개) |
|
||
|
|
| `/opt/web-configurator/DEPLOYED_VERSION` | 현재 버전 기록 파일 |
|
||
|
|
| `/opt/web-configurator/deploy-history.log` | 배포별 감사 로그 |
|
||
|
|
| `/home/root/db/dynamic_data.db` | SQLite 설정 데이터베이스(`board_config` 테이블) |
|
||
|
|
| `/opt/log/dpworldapp/` | 애플리케이션 로그 디렉터리 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. 트러블슈팅
|
||
|
|
|
||
|
|
### SSH 접속 불가
|
||
|
|
|
||
|
|
**증상:** `Cannot reach <ip> over SSH.`
|
||
|
|
|
||
|
|
**조치:**
|
||
|
|
- 디바이스 전원이 켜져 있고 네트워크에 연결되어 있는지 확인합니다: `ping <ip>`.
|
||
|
|
- SSH 키가 `root@<ip>:~/.ssh/authorized_keys`에 등록되어 있는지 확인합니다.
|
||
|
|
- 수동으로 테스트합니다: `ssh -o StrictHostKeyChecking=no root@<ip> echo ok`.
|
||
|
|
|
||
|
|
### 배포 거부 — HEAD에 태그 없음
|
||
|
|
|
||
|
|
**증상:** `HEAD is not at a version tag. Tag a release, or pass -AllowUntagged for a dev build.`
|
||
|
|
|
||
|
|
**조치:** 커밋에 태그를 붙이거나(`git tag -a vX.Y.Z -m "..."`) 다시 실행하거나, `-AllowUntagged`를 전달하여 플래그 처리된 개발 빌드를 배포합니다. 프로덕션 배포에 `-AllowUntagged`를 절대 사용하지 마십시오.
|
||
|
|
|
||
|
|
### 배포 거부 — 더티(dirty) 워킹 트리
|
||
|
|
|
||
|
|
**증상:** `Local working tree is not clean.`
|
||
|
|
|
||
|
|
**조치:** 배포 전에 모든 로컬 변경 사항을 커밋하거나 stash합니다. `git status`로 미처리 항목을 확인하십시오.
|
||
|
|
|
||
|
|
### 배포 후 서비스 비정상
|
||
|
|
|
||
|
|
**증상:** 스크립트가 `Verification failed: web-configurator not healthy on <ip> after restart`를 보고합니다.
|
||
|
|
|
||
|
|
**조치:**
|
||
|
|
```bash
|
||
|
|
ssh root@<ip> journalctl -u web-configurator -n 50 --no-pager
|
||
|
|
```
|
||
|
|
Python import 오류, 파일 누락, 포트 충돌 등을 확인합니다. 새 버전에 문제가 있으면 즉시 롤백합니다:
|
||
|
|
```powershell
|
||
|
|
.\scripts\deploy.ps1 <ip> -Rollback
|
||
|
|
```
|
||
|
|
|
||
|
|
### 롤백 방법
|
||
|
|
|
||
|
|
§6을 참조합니다. 롤백 자체가 실패한 경우(백업 없음), 알려진 정상 상태의 `.tar` 파일에서 수동 복원하거나 이전 릴리즈 태그를 다시 배포합니다.
|