move_detect/server/deploy/README-UPLOAD.md
유창욱 93f896af87 fix(audit): 1.2.0 4차 재감사 — 확정 23건 전량 해소
4개 관점 병렬 감사 후 각 발견을 반증 시도해 확정. 반증 5건은 문서에 남겼다.

이번 감사의 요지: **구현도 단위테스트도 초록인데 납품 경로에서 동작하지 않는**
결함이 셋 있었다. 전부 같은 모양이다 — 테스트가 기본값이 아닌 설정을 썼거나,
값이 존재해도 되는지 결정하는 상류 허용 목록을 보지 않았다.

CRITICAL 4건:

R4-1 추락 승격이 기본 설정에서 한 번도 발화하지 않았다. link_s(3.0)를 now
기준으로 쟀는데 넘어짐은 prone_confirm_s(3.0) 지속 후 확정되므로 확정 시점에는
항상 3초 넘게 지나 있다. 다른 테스트가 전부 prone_confirm_s=0.5 를 써서
초록이었다. 착지(prone 시작) 시각과 비교하도록 고치고, 기본값 그대로 도는
회귀 테스트를 넣었다.

R4-2 max_workers:0 이 상류 두 곳에서 막혀 있었다 — 서버 규칙 경계가 [1,50]
이라 저장이 400 이고, 엣지 파서가 v<=0 으로 규칙 전체를 무효화해 같이 넣은
2인1조까지 사라졌다. 앞 커밋에서 고친 네 곳은 전부 이 둘보다 하류라 도달
불가였다.

R4-3 고소작업 프리셋(elevated)이 완전히 무동작이었다. 서버 경계에 키가 없어
400, 통과해도 엣지 파서가 침묵 삭제. 프리셋 안내문과 커밋 메시지가 시스템이
하지 않는 일을 약속하고 있었다.

R4-4 동시 다발이 한 사람을 두 명으로 셀 수 있었다. 트래커는 1초 가려짐에
트랙을 버리고 새 id 를 주고(max_age=1.0) 누운 사람은 새 트랙에서도 즉시
prone 이라 두 번째 fall 이 난다. 그러면 다친 한 사람에게 "공기호흡기 없이
진입 금지"가 나가 구조가 늦어진다 — 기능의 가치가 정반대로 뒤집힌다.
ghost 를 인원 집계에서 빼고, 거의 같은 자리의 사건은 같은 사람으로 본다.

HIGH 5건: 구역 그룹핑 사문화(엣지가 zone_id 를 안 실어 카메라가 다른 두
희생자가 절대 안 묶였다 — 밀폐공간이 정확히 그 상황), 창 미끄러짐으로 한
사고가 매 분 새 위급 건으로 쏟아지고 인원이 줄면 앞선 알림을 스스로 뒤집던
문제, 무장 미만료·forget 누락·이중 발보, 업그레이드 안내의 마이그레이션 2개
누락(신규 유형 INSERT 가 전부 DB CHECK 에 걸린다), BUILD-INFO 커밋 스탬프.

BUILD-INFO 는 1.1.0 에서 고친 R3-6 의 재발이다. 절차로 막으려다 두 번
실패했으므로 HEAD 일치를 검사하는 테스트로 막았다. README 마이그레이션
목록도 같은 이유로 테스트를 붙였다.

MEDIUM 14건 중 눈에 띄는 것: 정비 억제가 SCBA 경고를 삼켰고(허가가 발급된
그 구역·그 시간이 질식이 일어나는 자리다) 위급 3종을 억제 예외로 뺐다.
데모 시더 심각도가 제품과 7종 어긋나 fall 이 위급으로 찍혀 1.2.0 의 요점인
넘어짐/추락 구분이 데모에서 지워져 있었다 — 라벨과 달리 화면이 안 깨져
눈으로는 못 잡으므로 엣지를 단일 출처로 강제하는 테스트를 붙였다.
문서의 "17종"은 실제 20종(엣지 16 + 서버 4)으로 정정하고 신규 2종을
소개서 감지 표에 넣었다.

검증: pytest 335 passed / phpunit 269 passed. 신규 테스트 30건.
dist 는 이 커밋 이후 재생성한다(BUILD-INFO 가 HEAD 를 가리켜야 한다).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBjuR7DW6PNNMows4KnbYU
2026-09-05 23:11:57 +09:00

10 KiB

카페24 업로드 안내

이 폴더의 내용물을 카페24 FTP 최상위에 그대로 올리면 됩니다. 고칠 파일은 config.php 하나뿐입니다.


1. 올리는 위치 (제일 중요)

폴더 2개만 올리면 됩니다. move_detect_app 과 www.

/                          <- FTP 최상위
├── (기존 사이트 파일)        <- 건드리지 않습니다
├── move_detect_app/       <- [올리기 1] 통째로. 웹에서 안 보입니다
│   ├── config.php            여기만 고칩니다
│   ├── src/
│   ├── vendor/
│   ├── storage/snapshots/    사진 저장
│   └── schema.sql            DB 넣고 나면 지웁니다
└── www/                   <- 원래 있던 폴더 (다른 사이트가 씀)
    ├── (기존 사이트 파일)     <- 건드리지 않습니다
    └── move_detect/       <- [올리기 2] 이 폴더만 새로 생깁니다
        ├── index.php
        ├── .htaccess
        ├── _preflight.php
        └── assets/

접속 주소는 https://내도메인/move_detect/ 입니다.

README-UPLOAD.md(이 파일)는 올릴 필요 없습니다.

왜 move_detect_app 을 www 밖에 두나: config.php 에 DB 비밀번호가, storage/snapshots/ 에 작업자 사진이 들어갑니다. www 안에 있으면 같은 www 를 쓰는 다른 사이트 주소로 접근될 수 있습니다. 한 폴더로 묶은 건 최상위에 있는 남의 파일(vendor/, config.php 같은 흔한 이름)과 겹쳐 덮어쓰는 사고를 막기 위해서입니다.

업로드가 끝나면 FTP 클라이언트에서 권한을 지정하세요. zip 과 FTP 는 유닉스 권한을 옮기지 못하므로, 패키지에 넣어둔 권한은 올리는 순간 사라집니다.

대상 권한
move_detect_app/config.php 600 (DB 비밀번호가 들어있습니다)
move_detect_app/storage/snapshots/ 750

_preflight.php 가 실제 권한과 노출 여부를 확인해줍니다.


2. config.php 고치기

DB 세 줄만 카페24 관리자 페이지의 MySQL 정보로 바꿉니다.

'db_dsn'  => 'mysql:host=localhost;dbname=여기에_DB이름;charset=utf8mb4',
'db_user' => '여기에_MySQL아이디',
'db_pass' => '여기에_MySQL비밀번호',

나머지 값은 그대로 두면 됩니다. 비밀번호는 메일/메신저/깃에 올리지 마세요.


3. DB 만들기

처음 설치(빈 DB)

  1. 카페24 관리자 → phpMyAdmin 열기
  2. 이 앱용 DB 선택
  3. Import(가져오기) 탭 → schema.sql 선택 → 실행
  4. 표 11개가 생겼는지 확인
  5. 서버에서 schema.sql 삭제 — 남겨둘 이유가 없습니다

schema.sql 은 항상 최신 상태입니다. 처음 설치라면 아래 마이그레이션은 실행하지 마세요 (이미 반영되어 있어 충돌합니다).

이미 쓰던 DB 업그레이드(기존 데이터 있음)

⚠️ 기존 DB에 schema.sql 을 다시 Import 하면 CREATE TABLE 충돌로 실패합니다. 대신 마이그레이션을 실행하세요.

반드시 아래 순서대로, 아직 적용하지 않은 것부터 전부 실행합니다. 하나라도 건너뛰면 그 컬럼·표를 쓰는 요청이 전부 500 이 됩니다. 순서가 중요한 이유는 뒤 파일이 앞 파일이 만든 표 이름(move_ 접두사)을 전제하기 때문입니다.

순서 파일 무엇이 바뀌나
1 2026-07-17-p1-9-p3-2b.sql site / event_uuid 컬럼 + 인덱스, 기존 이벤트 site 백필
2 2026-07-20-move-table-prefix.sql 모든 표 이름에 move_ 접두사 부여 (이 단계를 건너뛰면 이후 전부 실패)
3 2026-08-06-zone-rules.sql 존 점유 규칙(rules_json)
4 2026-08-06-heatmap-spatial.sql 히트맵 좌표(spatial_x, spatial_y)
5 2026-08-06-false-positive-tag.sql 오탐 태그 컬럼
6 2026-08-07-improvement-actions.sql 개선 조치 표(move_actions)
7 2026-08-08-actions-site.sql 개선 조치의 현장(site) 스코프
8 2026-08-09-safety-platform.sql 통보 채널·억제·위험성평가·인수인계·성능 시계열 표, 감사로그 해시체인, 신규 감지 유형
9 2026-09-04-fall-from-height.sql 추락(fall_from_height)·동시 다발 쓰러짐(multi_down) 유형 허용
  1. phpMyAdmin → 해당 DB → SQL 탭
  2. 위 표의 순서대로 파일 내용을 하나씩 붙여넣고 실행합니다 (각 파일 첫머리에 적용 조건이 주석으로 적혀 있습니다)
  3. 각 파일 맨 아래 검증 쿼리로 컬럼·표가 생겼는지 확인
  4. 전부 끝난 뒤에 새 코드를 배포합니다

이미 적용한 마이그레이션을 다시 실행하면 "Duplicate column" 같은 오류가 납니다 — 재실행은 안전하지 않으니, 어디까지 적용했는지 기록해 두세요. 어디까지 했는지 모르겠으면 phpMyAdmin 의 Structure(구조) 탭에서 위 표의 컬럼·표가 있는지 하나씩 확인하면 됩니다.


4. HTTPS 켜기

카페24 관리자에서 무료 SSL 인증서를 켭니다.

선택이 아니라 필수입니다. 로그인 세션 쿠키가 cookie_secure=true 라서, HTTP 로는 브라우저가 쿠키를 아예 안 보냅니다 → 로그인이 안 됩니다.


5. 점검하기

브라우저에서 엽니다:

https://내도메인/move_detect/_preflight.php

전부 OK 여야 합니다. 실패한 줄에 무엇을 하면 되는지 적혀 있습니다.

특히 이 둘은 카페24 요금제에 따라 없을 수 있고, 없으면 카페24에 문의해야 합니다:

항목 없으면 생기는 일
Argon2id 로그인 페이지가 즉시 죽습니다. 제일 흔한 실패 원인입니다.
gd 이벤트는 저장되는데 사진만 조용히 사라집니다. 에러도 안 납니다.

확인이 끝나면 www/move_detect/_preflight.php 를 지우세요.


6. 관리자 계정 만들기

앱에 회원가입 기능이 없습니다. 첫 계정은 직접 넣습니다.

비밀번호 해시를 만듭니다(로컬 PC 에서 실행 — 평문이 phpMyAdmin 기록에 남지 않게):

php -r "echo password_hash('여기에_진짜_비밀번호', PASSWORD_ARGON2ID), \"\n\";"

phpMyAdmin → SQL 탭:

INSERT INTO move_users (username, password_hash, role, site_scope, failed_attempts, locked_until)
VALUES ('admin', '여기에_해시_붙여넣기', 'admin', NULL, 0, 0);

7. 라즈베리파이(장비) 등록

장비마다 비밀키를 하나씩 만들어 DB 에 넣습니다.

php -r "echo bin2hex(random_bytes(32)), \"\n\";"
INSERT INTO move_devices (device_id, name, secret, config_version)
VALUES ('pi-01', '3라인 낙상 카메라', '여기에_비밀키_붙여넣기', 1);

같은 값을 파이 쪽 /etc/move-detect/edge.env 의 DEVICE_SECRET 에 넣습니다. CAFE24_URL 에는 https://내도메인/move_detect 를 넣습니다. (앱이 서브폴더에 있으므로 뒤의 /move_detect 까지 반드시 포함해야 합니다.)

비밀키는 메일/메신저로 보내지 마세요.


8. 알림 켜기 (자동 실행 등록) — 이걸 안 하면 알림이 안 갑니다

여기까지만 하면 시스템은 감지는 하지만 아무에게도 알리지 않습니다. 화면을 보고 있는 사람에게만 보입니다. 새벽에 넘어진 작업자를 아무도 모른 채 아침을 맞게 됩니다.

카페24 관리자 페이지 → 자동실행관리(크론) 에서 아래 두 개를 등록하세요.

주기 실행할 명령 하는 일
1분마다 /usr/bin/php /home/<계정>/move_detect_app/tools/dispatch_alerts.php 장치 이상·감시 성능 저하 판정 · 알림 발송 · 미확인 경보 상위 통보 · 실패 재시도
하루 1회 (새벽) /usr/bin/php /home/<계정>/move_detect_app/tools/maintain.php 사진 보존기간 정리 · 기록 용량 관리

<계정> 자리에는 카페24 FTP 계정명을 넣습니다. 경로가 헷갈리면 FTP 로 접속했을 때 move_detect_app 폴더가 보이는 그 위치입니다.

처음 켤 때는 반드시 시험 발송부터

잘못된 수신자 목록으로 첫 발송을 하면 그 현장에서는 두 번 다시 이 기능을 켜지 않게 됩니다. 크론에 넣기 전에 아래를 한 번 실행해 누구에게 무엇이 갈지만 확인하세요 (아무것도 보내지 않습니다).

php /home/<계정>/move_detect_app/tools/dispatch_alerts.php --dry-run

알림 경로 설정

로그인 후 상단 [시스템 설정] → 통보 경로 에서 등록합니다.

  • 현장 경광등을 가장 먼저 설정하세요. 라즈베리파이가 직접 울리므로 인터넷이 끊겨도 동작하는 유일한 경로입니다. 정전·회선 장애 때 오히려 위험이 높아지는 시간대에 유일하게 살아 있는 알림입니다.
  • 메일·웹훅은 그 위에 얹는 보조 수단이고, 위 크론이 돌아야 나갑니다.
  • config.php 의 public_base_url 이 비어 있으면 디스패처는 실행을 거부합니다 — 열리지 않는 링크가 담긴 알림을 보내는 것보다 안 보내는 편이 낫기 때문입니다.

9. 설치 점검 화면

로그인 후 https://내도메인/move_detect/setup 을 엽니다.

각 단계는 실제 상태를 조회해서 표시하므로, 체크가 켜졌다면 그 단계는 정말로 동작하고 있는 것입니다. 장치 등록 · 장치 통신 · 카메라 감지 · 위험구역 · 통보 경로 순서대로 확인하세요.


10. 마지막 확인

  • https://내도메인/move_detect/login 이 열린다
  • admin 으로 로그인되고 대시보드가 뜬다
  • schema.sql 을 서버에서 지웠다
  • www/move_detect/_preflight.php 를 서버에서 지웠다
  • https://내도메인/move_detect_app/config.php 가 안 열린다 (열리면 위치가 잘못된 것)
  • 자동실행(크론) 2개가 등록되어 있다 — 없으면 알림이 나가지 않습니다 (8번)
  • [시스템 설정] → 통보 경로 에 최소 1개가 '사용' 상태다 (현장 경광등 우선)
  • /setup 화면의 모든 단계에 체크가 켜져 있다 (9번)