# Last Signal 개인 NAS 사용자를 위한 초경량 생존 확인 웹앱입니다. - 현재 버전: `v1.0.0` - Git 저장소: `https://git.sori.studio/zenn/collection.sori.studio.git` - 상세 설치 문서: [`docs/DEPLOY.md`](docs/DEPLOY.md) 가끔 방문해서 중앙 버튼을 누르면 확인 시간이 갱신되고, 일정 기간 동안 아무도 누르지 않으면 사용자가 지정한 스크립트가 실행됩니다. 마지막 생존 확인 후 설정된 스크립트를 실행합니다. 기본 `scripts/cleanup.sh`는 `TARGET_PATH`로 지정한 폴더의 **내용 전체를 삭제**하며, 폴더 자체는 남깁니다. 기본 대상은 `/home/zenn/Collection`입니다. ## 주요 기능 - 중앙 버튼 한 개로 생존 확인 - SQLite 기반 통계 및 방문 흔적 저장 - 1초 쿨다운 스팸 방지 - 랜덤 이모지 클릭 효과 - 배경 이미지 교체 및 랜덤 선택 - threshold 초과 시 사용자 스크립트 실행 ## 로컬 실행 가이드 개발하거나 먼저 동작을 확인할 때는 로컬에서 실행합니다. 필요한 것: - Node.js 20 이상 - npm 설치: ```bash npm install ``` 실행: ```bash npm start ``` 브라우저에서 접속: ```text http://localhost:40100 ``` 개발 중 파일 변경을 감지해서 다시 실행하려면: ```bash npm run dev ``` 로컬 실행 시 SQLite 파일은 기본적으로 `data/last-signal.sqlite`에 생성됩니다. ## Docker 실행 가이드 Docker로 한 번에 빌드하고 실행합니다. 필요한 것: - Docker - Docker Compose 실행: ```bash docker compose up -d --build ``` 브라우저에서 접속: ```text http://localhost:44092 ``` 상태 확인: ```bash docker compose ps ``` 로그 확인: ```bash docker compose logs -f ``` 중지: ```bash docker compose down ``` 다시 시작: ```bash docker compose restart ``` 컨테이너와 네트워크를 중지해도 `last-signal-data` 볼륨에 SQLite 데이터가 남습니다. ## 상용/NAS 설치 가이드 NAS에서 오래 켜둘 때는 Docker Compose 실행을 권장합니다. 1. 프로젝트 폴더를 NAS에 둡니다. ```bash cd /volume1/docker/last-signal ``` 2. 설정을 확인합니다. ```bash vi config/default.json ``` 자주 바꾸는 항목: - `threshold_days`: 며칠 동안 버튼을 누르지 않으면 스크립트를 실행할지 - `cooldown_seconds`: 생존 확인으로 인정되는 최소 클릭 간격 - `trigger_script`: 실행할 사용자 스크립트 경로 - `background`: `none`, 배경 파일명, 또는 `random` 3. 사용자 스크립트를 수정합니다. ```bash vi scripts/cleanup.sh chmod +x scripts/*.sh ``` 4. `docker-compose.yml`의 NAS 경로를 확인합니다. 기본값은 Synology 스타일 경로입니다. ```yaml - /home/zenn/Collection:/home/zenn/Collection ``` 다른 경로를 쓴다면 왼쪽 경로를 실제 Collection 폴더로 바꿉니다. 5. 실행합니다. ```bash docker compose up -d --build ``` 6. 로그를 확인합니다. ```bash docker compose logs -f last-signal ``` 7. NAS 주소로 접속합니다. ```text http://NAS_IP:44092 ``` 예시: ```text http://192.168.0.10:44092 ``` ## 업데이트 가이드 코드를 새 버전으로 바꾼 뒤 다시 빌드합니다. ```bash docker compose down --remove-orphans docker compose up -d --build ``` `--remove-orphans`는 이전 서비스 이름으로 남은 컨테이너를 정리합니다. 데이터를 유지하려면 `docker compose down -v`는 사용하지 마세요. `-v` 옵션은 SQLite 데이터가 들어 있는 Docker 볼륨까지 삭제합니다. ## 백업 가이드 Docker Compose 기본 설정에서는 SQLite DB가 `last-signal-data` 볼륨에 저장됩니다. 볼륨 이름 확인: ```bash docker volume ls | grep last-signal ``` 로컬 실행에서는 `data/last-signal.sqlite` 파일을 백업하면 됩니다. ```bash cp data/last-signal.sqlite last-signal.backup.sqlite ``` ## 멀티 아키텍처 이미지 빌드 ARM64와 x86_64 이미지를 함께 빌드하려면 Docker Buildx를 사용합니다. ```bash docker buildx build --platform linux/amd64,linux/arm64 -t last-signal:latest . ``` ## 문제 해결 ### `EADDRINUSE: address already in use :::40100` 이미 `40100` 포트를 쓰는 서버가 떠 있을 때 발생합니다. 포트를 쓰는 프로세스 확인: ```bash lsof -nP -iTCP:40100 -sTCP:LISTEN ``` 예시 출력에서 `PID`가 `30383`이라면 종료: ```bash kill 30383 ``` 다시 실행: ```bash npm start ``` 기존 서버를 끄고 싶지 않다면 다른 포트로 실행할 수도 있습니다. ```bash PORT=40101 npm start ``` 이 경우 브라우저에서는 다음 주소로 접속합니다. ```text http://localhost:40101 ``` ## 설정 기본 설정은 [`config/default.json`](config/default.json) 에 있습니다. | 항목 | 기본값 | 설명 | | --- | --- | --- | | `threshold_days` | `90` | 생존 확인 허용 기간 | | `threshold_seconds` | `null` | 테스트용 초 단위 생존 확인 기간. 값이 있으면 `threshold_days`보다 우선합니다. | | `cooldown_seconds` | `1` | 생존 확인 인정 쿨다운 | | `check_interval_seconds` | `60` | 스크립트 검사 주기 | | `database_path` | `./data/last-signal.sqlite` | SQLite 경로 | | `trigger_script` | `./scripts/cleanup.sh` | 조건 충족 시 실행할 스크립트 | | `background` | `random` | `none`, 배경 파일명, 또는 `random` | | `target_path` | `/home/zenn/Collection` | 만료 시 스크립트가 처리할 대상 폴더 | | `recent_traces_limit` | `5` | 최근 흔적 저장 조회 개수 | | `reset_token` | `""` | 초기화 API 보호용 토큰. 비어 있으면 토큰 없이 초기화 가능합니다. | | `public_base_url` | `https://collection.sori.studio` | 메일 확인 링크에 사용할 공개 URL | | `mail_enabled` | `false` | Gmail 알림 메일 사용 여부 | | `mail_smtp_user` | `""` | Gmail SMTP 계정 | | `mail_smtp_pass` | `""` | Gmail 앱 비밀번호 | | `mail_to` | `zenn.message@gmail.com` | 알림 수신 주소 | | `mail_confirm_token` | `""` | 메일 확인 링크 보호 토큰 | 환경 변수로도 덮어쓸 수 있습니다. - `THRESHOLD_DAYS` - `COOLDOWN_SECONDS` - `CHECK_INTERVAL_SECONDS` - `DATABASE_PATH` - `TRIGGER_SCRIPT` - `TARGET_PATH` - `BACKGROUND` - `PORT` - `THRESHOLD_SECONDS` - `RESET_TOKEN` - `PUBLIC_BASE_URL` - `MAIL_ENABLED` - `MAIL_SMTP_USER` - `MAIL_SMTP_PASS` - `MAIL_TO` - `MAIL_CONFIRM_TOKEN` 이전 설정과의 호환을 위해 `COLLECTION_PATH`와 `collection_path`도 읽습니다. 새 설정에는 `TARGET_PATH`와 `target_path`를 사용하세요. ## 화면 문구 커스터마이징 화면에 보이는 글, 클릭 이모지, 클릭 메시지는 [`public/custom.js`](public/custom.js) 한 파일에서 바꿉니다. 코드를 몰라도 이 파일의 따옴표 안 문장과 이모지만 수정하면 됩니다. ```js window.LAST_SIGNAL_CUSTOM = { keeperTextLines: [ '가끔은 생각해.', '', '누군가 버튼을 누른다는 건', '이곳을 기억하고 있다는 뜻일까,', ], farewellTextLines: [ '안녕.', '', '고마웠어.', ], emojis: ['🐱', '🐰', '🌸', '✨'], clickMessages: [ '기록은 남겨둘게.', '아직 이곳을 기억하고 있어.', ], }; ``` - `keeperTextLines`: 오른쪽 아래에 표시되는 글입니다. 빈 줄은 `''`로 넣습니다. - `farewellTextLines`: 남은 기간이 0이 되었을 때 표시되는 작별 인사입니다. - `emojis`: 클릭할 때 기록되고 표시되는 이모지입니다. - `clickMessages`: 클릭할 때 이모지 뒤에 붙는 랜덤 문장입니다. ## 만료 테스트와 초기화 만료 화면을 빠르게 확인하려면 초 단위 타이머를 사용합니다. ```bash THRESHOLD_SECONDS=10 npm start ``` 10초가 지나면 화면이 작별 인사로 전환되고, 더 이상 클릭해도 확인 시간이 갱신되지 않습니다. 다시 처음 상태로 초기화: ```bash curl -X POST http://localhost:40100/api/reset ``` 초기화 API에 토큰을 걸고 싶으면 `RESET_TOKEN`을 설정합니다. Docker 운영 환경에서는 Git에 올리지 않는 `.env` 파일을 사용하세요. ```bash RESET_TOKEN=my-secret THRESHOLD_SECONDS=10 npm start ``` Docker Compose: ```bash cat > .env <<'EOF' RESET_TOKEN=my-secret EOF docker compose up -d --build ``` ## Gmail 알림 메일 마지막 확인 이후 60일, 75일, 89일이 지나면 Gmail SMTP로 알림 메일을 보낼 수 있습니다. `.env` 예시: ```bash PUBLIC_BASE_URL=https://collection.sori.studio MAIL_ENABLED=true MAIL_SMTP_USER=zenn.message@gmail.com MAIL_SMTP_PASS=구글-앱-비밀번호 MAIL_TO=zenn.message@gmail.com MAIL_CONFIRM_TOKEN=긴-랜덤-확인-토큰 ``` 메일의 `[ ❤️ 생존 확인하기 ]` 버튼은 사이트를 다시 열 필요 없이 `/confirm?token=...` 링크로 바로 생존 확인을 갱신합니다. Google 계정에서는 일반 비밀번호가 아니라 **앱 비밀번호**를 사용해야 합니다. 토큰이 있을 때 초기화: ```bash curl -X POST http://localhost:40100/api/reset \ -H 'Content-Type: application/json' \ -d '{"token":"my-secret"}' ``` ## 배경 이미지 `assets/backgrounds/` 폴더에 이미지를 넣고 설정에서 지정합니다. 기본값은 `random`입니다. 예시: ```json { "background": "bg01.jpg" } ``` 랜덤 사용: ```json { "background": "random" } ``` 배경 없이 사용: ```json { "background": "none" } ``` ## 사용자 스크립트 `scripts/` 폴더에 실행할 스크립트를 둡니다. 예시: - `scripts/warning.sh` - `scripts/encrypt.sh` - `scripts/cleanup.sh` 스크립트에는 다음 환경 변수가 전달됩니다. - `TARGET_PATH` - `TRIGGER_SCRIPT` 기존 스크립트 호환을 위해 `COLLECTION_PATH`와 `KEEPER_SCRIPT`도 같은 값으로 전달됩니다. 새 스크립트에는 새 이름을 사용하세요. ## 안전 원칙 Last Signal은 다음 역할을 수행합니다. 1. 마지막 확인 시간 관리 2. 통계 관리 3. 버튼 이벤트 처리 4. 조건 충족 여부 판단 5. 사용자 지정 스크립트 실행 파일 처리 동작은 실행 스크립트에 달려 있습니다. 기본 `scripts/cleanup.sh`는 `TARGET_PATH` 폴더의 내용을 전부 삭제합니다. 다른 작업을 원하면 해당 스크립트를 수정하고, 실제 데이터가 아닌 임시 폴더에서 먼저 검증하세요. ## API - `GET /api/status` - `POST /api/check` 버튼 클릭은 반드시 `POST /api/check` 로 처리됩니다. 단순 방문만으로는 생존 확인이 갱신되지 않습니다. ## NAS 볼륨 예시 ```yaml volumes: - last-signal-data:/data - ./scripts:/scripts:ro - /home/zenn/Collection:/home/zenn/Collection ```