move_detect/cloud/deploy/README-UPLOAD.md
유창욱 2ea0420de7 fix(delivery): resolve codex + workflow review findings across edge/cloud/deploy
Two independent adversarial reviews (codex CLI + a 6-dimension verification
workflow) of the delivery-hardening branch surfaced ~40 findings; this closes
the confirmed correctness/security/deploy blockers plus a codex re-review pass.

edge:
- geometry: track_displacement per-second threshold recalibrated (0.02->0.25,
  env-tunable via MOTION_THR, validated finite/positive); confidence-filter
  keypoints with a bbox-center fallback so hallucinated points don't fake motion
- fall: low-confidence frames no longer cancel an active FALLING/FALLEN incident
  (freeze + reset recovery timer so recovery needs a fresh confident streak);
  baseline height only seeded from trusted frames
- queue: PostClient.send() returns a tri-state SendResult; only definite 4xx
  count toward the poison drop cap, so a network outage (incl. 408/425/429/5xx)
  never deletes queued safety events; legacy rows persist their minted uuid
- config: reject placeholder / http / short DEVICE_SECRET at startup
- zones: warn+skip legacy pixel-space polygons that would silently match nothing

cloud:
- zone endpoints wired into P3-2b site-scope (cross-site read/write/delete
  closed) + role gate on list; catch narrowed so DB faults surface as 500
- snapshot purge keeps the DB row when unlink() fails (no untrackable PIPA orphan)
- ingest dedup path re-stores missing snapshots (+ short-write guard); UNIQUE
  (event_id, content_hash) makes recovery idempotent under retry/race
- event list cursor carries full-precision occurred_at (no float rounding at the
  page boundary); string keyset end-to-end
- SiteScope '' fails closed (only NULL is global); dashboard fetches full page cap
- runnable migration (site/event_uuid + backfill ordering + indexes); rate-limit
  reap fails open

deploy:
- offline bundle hashes EVERY file (fatal on missing/failed/injected; models in
  SBOM; arch in tarball name; REQUIRE_SIGNATURE option)
- installer verifies before any mutation, Hailo arch preflight before install,
  atomic src swap, backend reconcile preserves a working operator choice
- systemd DeviceAllow=char-dma_heap (the /dev/dma_heap/ dir form was inert)

Verified: edge pytest 165 passed / 2 skipped; cloud phpunit 109 passed / 4
skipped; deploy scripts bash -n clean. Reviewed by codex twice (findings +
fixes) with the second-pass findings folded in.
2026-07-17 19:24:42 +09:00

5.7 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. 표 10개가 생겼는지 확인
  5. 서버에서 schema.sql 삭제 — 남겨둘 이유가 없습니다

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

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

  1. phpMyAdmin → 해당 DB → SQL 탭
  2. migrations/2026-07-17-p1-9-p3-2b.sql 내용을 붙여넣고 실행 (site / event_uuid 컬럼 + 인덱스 추가, 기존 이벤트의 site 백필 포함)
  3. 파일 맨 아래 검증 쿼리로 컬럼·인덱스가 생겼는지 확인
  4. 그 다음에 새 코드를 배포합니다 — 컬럼이 없으면 모든 요청이 500 이 됩니다

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 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 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. 마지막 확인

  • https://내도메인/move_detect/login 이 열린다
  • admin 으로 로그인되고 대시보드가 뜬다
  • schema.sql 을 서버에서 지웠다
  • www/move_detect/_preflight.php 를 서버에서 지웠다
  • https://내도메인/move_detect_app/config.php 가 안 열린다 (열리면 위치가 잘못된 것)