From aeaa1b37c20c2096fe2007741862d8563b08f7c5 Mon Sep 17 00:00:00 2001 From: zenn Date: Wed, 30 Sep 2026 11:55:58 +0900 Subject: [PATCH] feat: rename app to Last Signal Rename service, UI, storage, and target-path configuration; clarify expiration cleanup behavior. --- Dockerfile | 4 ++-- README.md | 54 ++++++++++++++++++++++++--------------------- config/default.json | 4 ++-- docker-compose.yml | 18 +++++++-------- docs/DEPLOY.md | 26 +++++++++++++--------- package-lock.json | 4 ++-- package.json | 2 +- public/app.js | 4 ++-- public/custom.js | 4 ++-- public/index.html | 2 +- scripts/cleanup.sh | 29 ++++++++++++++++++------ scripts/encrypt.sh | 6 ++--- scripts/warning.sh | 6 ++--- src/config.js | 9 ++++++-- src/confirmPage.js | 6 ++--- src/db.js | 2 +- src/mailer.js | 12 +++++----- src/server.js | 2 +- src/trigger.js | 6 +++-- 19 files changed, 115 insertions(+), 85 deletions(-) diff --git a/Dockerfile b/Dockerfile index 93be0a5..df685de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,9 +15,9 @@ RUN chmod +x /app/scripts/*.sh \ ENV NODE_ENV=production \ PORT=40100 \ CONFIG_PATH=/app/config/default.json \ - DATABASE_PATH=/data/keeper.sqlite \ + DATABASE_PATH=/data/last-signal.sqlite \ TRIGGER_SCRIPT=/scripts/cleanup.sh \ - COLLECTION_PATH=/home/zenn/Collection + TARGET_PATH=/home/zenn/Collection EXPOSE 40100 diff --git a/README.md b/README.md index 337e11f..ec8ef6c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Collection Keeper +# Last Signal 개인 NAS 사용자를 위한 초경량 생존 확인 웹앱입니다. @@ -8,7 +8,7 @@ 가끔 방문해서 중앙 버튼을 누르면 확인 시간이 갱신되고, 일정 기간 동안 아무도 누르지 않으면 사용자가 지정한 스크립트가 실행됩니다. -Collection Keeper는 Collection 폴더를 직접 삭제하지 않습니다. +마지막 생존 확인 후 설정된 스크립트를 실행합니다. 기본 `scripts/cleanup.sh`는 `TARGET_PATH`로 지정한 폴더의 **내용 전체를 삭제**하며, 폴더 자체는 남깁니다. 기본 대상은 `/home/zenn/Collection`입니다. ## 주요 기능 @@ -52,7 +52,7 @@ http://localhost:40100 npm run dev ``` -로컬 실행 시 SQLite 파일은 기본적으로 `data/keeper.sqlite`에 생성됩니다. +로컬 실행 시 SQLite 파일은 기본적으로 `data/last-signal.sqlite`에 생성됩니다. ## Docker 실행 가이드 @@ -72,7 +72,7 @@ docker compose up -d --build 브라우저에서 접속: ```text -http://localhost:40100 +http://localhost:44092 ``` 상태 확인: @@ -99,7 +99,7 @@ docker compose down docker compose restart ``` -컨테이너와 네트워크를 중지해도 `keeper-data` 볼륨에 SQLite 데이터가 남습니다. +컨테이너와 네트워크를 중지해도 `last-signal-data` 볼륨에 SQLite 데이터가 남습니다. ## 상용/NAS 설치 가이드 @@ -108,7 +108,7 @@ NAS에서 오래 켜둘 때는 Docker Compose 실행을 권장합니다. 1. 프로젝트 폴더를 NAS에 둡니다. ```bash -cd /volume1/docker/collection-keeper +cd /volume1/docker/last-signal ``` 2. 설정을 확인합니다. @@ -150,19 +150,19 @@ docker compose up -d --build 6. 로그를 확인합니다. ```bash -docker compose logs -f collection-keeper +docker compose logs -f last-signal ``` 7. NAS 주소로 접속합니다. ```text -http://NAS_IP:40100 +http://NAS_IP:44092 ``` 예시: ```text -http://192.168.0.10:40100 +http://192.168.0.10:44092 ``` ## 업데이트 가이드 @@ -170,26 +170,26 @@ http://192.168.0.10:40100 코드를 새 버전으로 바꾼 뒤 다시 빌드합니다. ```bash -docker compose down +docker compose down --remove-orphans docker compose up -d --build ``` -데이터를 유지하려면 `docker compose down -v`는 사용하지 마세요. `-v` 옵션은 SQLite 데이터가 들어 있는 Docker 볼륨까지 삭제합니다. +`--remove-orphans`는 이전 서비스 이름으로 남은 컨테이너를 정리합니다. 데이터를 유지하려면 `docker compose down -v`는 사용하지 마세요. `-v` 옵션은 SQLite 데이터가 들어 있는 Docker 볼륨까지 삭제합니다. ## 백업 가이드 -Docker Compose 기본 설정에서는 SQLite DB가 `keeper-data` 볼륨에 저장됩니다. +Docker Compose 기본 설정에서는 SQLite DB가 `last-signal-data` 볼륨에 저장됩니다. 볼륨 이름 확인: ```bash -docker volume ls | grep keeper +docker volume ls | grep last-signal ``` -로컬 실행에서는 `data/keeper.sqlite` 파일을 백업하면 됩니다. +로컬 실행에서는 `data/last-signal.sqlite` 파일을 백업하면 됩니다. ```bash -cp data/keeper.sqlite keeper.backup.sqlite +cp data/last-signal.sqlite last-signal.backup.sqlite ``` ## 멀티 아키텍처 이미지 빌드 @@ -197,7 +197,7 @@ cp data/keeper.sqlite keeper.backup.sqlite ARM64와 x86_64 이미지를 함께 빌드하려면 Docker Buildx를 사용합니다. ```bash -docker buildx build --platform linux/amd64,linux/arm64 -t collection-keeper:latest . +docker buildx build --platform linux/amd64,linux/arm64 -t last-signal:latest . ``` ## 문제 해결 @@ -246,10 +246,10 @@ http://localhost:40101 | `threshold_seconds` | `null` | 테스트용 초 단위 생존 확인 기간. 값이 있으면 `threshold_days`보다 우선합니다. | | `cooldown_seconds` | `1` | 생존 확인 인정 쿨다운 | | `check_interval_seconds` | `60` | 스크립트 검사 주기 | -| `database_path` | `./data/keeper.sqlite` | SQLite 경로 | +| `database_path` | `./data/last-signal.sqlite` | SQLite 경로 | | `trigger_script` | `./scripts/cleanup.sh` | 조건 충족 시 실행할 스크립트 | | `background` | `random` | `none`, 배경 파일명, 또는 `random` | -| `collection_path` | `/home/zenn/Collection` | Collection 폴더 경로 | +| `target_path` | `/home/zenn/Collection` | 만료 시 스크립트가 처리할 대상 폴더 | | `recent_traces_limit` | `5` | 최근 흔적 저장 조회 개수 | | `reset_token` | `""` | 초기화 API 보호용 토큰. 비어 있으면 토큰 없이 초기화 가능합니다. | | `public_base_url` | `https://collection.sori.studio` | 메일 확인 링크에 사용할 공개 URL | @@ -266,7 +266,7 @@ http://localhost:40101 - `CHECK_INTERVAL_SECONDS` - `DATABASE_PATH` - `TRIGGER_SCRIPT` -- `COLLECTION_PATH` +- `TARGET_PATH` - `BACKGROUND` - `PORT` - `THRESHOLD_SECONDS` @@ -278,6 +278,8 @@ http://localhost:40101 - `MAIL_TO` - `MAIL_CONFIRM_TOKEN` +이전 설정과의 호환을 위해 `COLLECTION_PATH`와 `collection_path`도 읽습니다. 새 설정에는 `TARGET_PATH`와 `target_path`를 사용하세요. + ## 화면 문구 커스터마이징 화면에 보이는 글, 클릭 이모지, 클릭 메시지는 [`public/custom.js`](public/custom.js) 한 파일에서 바꿉니다. @@ -285,7 +287,7 @@ http://localhost:40101 코드를 몰라도 이 파일의 따옴표 안 문장과 이모지만 수정하면 됩니다. ```js -window.COLLECTION_KEEPER_CUSTOM = { +window.LAST_SIGNAL_CUSTOM = { keeperTextLines: [ '가끔은 생각해.', '', @@ -412,12 +414,14 @@ curl -X POST http://localhost:40100/api/reset \ 스크립트에는 다음 환경 변수가 전달됩니다. -- `COLLECTION_PATH` -- `KEEPER_SCRIPT` +- `TARGET_PATH` +- `TRIGGER_SCRIPT` + +기존 스크립트 호환을 위해 `COLLECTION_PATH`와 `KEEPER_SCRIPT`도 같은 값으로 전달됩니다. 새 스크립트에는 새 이름을 사용하세요. ## 안전 원칙 -Collection Keeper는 다음 역할만 수행합니다. +Last Signal은 다음 역할을 수행합니다. 1. 마지막 확인 시간 관리 2. 통계 관리 @@ -425,7 +429,7 @@ Collection Keeper는 다음 역할만 수행합니다. 4. 조건 충족 여부 판단 5. 사용자 지정 스크립트 실행 -파일 삭제, 암호화, 정리 같은 실제 작업은 반드시 사용자 스크립트에서 구현해야 합니다. +파일 처리 동작은 실행 스크립트에 달려 있습니다. 기본 `scripts/cleanup.sh`는 `TARGET_PATH` 폴더의 내용을 전부 삭제합니다. 다른 작업을 원하면 해당 스크립트를 수정하고, 실제 데이터가 아닌 임시 폴더에서 먼저 검증하세요. ## API @@ -438,7 +442,7 @@ Collection Keeper는 다음 역할만 수행합니다. ```yaml volumes: - - keeper-data:/data + - last-signal-data:/data - ./scripts:/scripts:ro - /home/zenn/Collection:/home/zenn/Collection ``` diff --git a/config/default.json b/config/default.json index 18a8132..40aa293 100644 --- a/config/default.json +++ b/config/default.json @@ -3,10 +3,10 @@ "threshold_seconds": null, "cooldown_seconds": 1, "check_interval_seconds": 60, - "database_path": "./data/keeper.sqlite", + "database_path": "./data/last-signal.sqlite", "trigger_script": "./scripts/cleanup.sh", "background": "random", - "collection_path": "/home/zenn/Collection", + "target_path": "/home/zenn/Collection", "port": 40100, "recent_traces_limit": 5, "reset_token": "", diff --git a/docker-compose.yml b/docker-compose.yml index 0bd36c6..5bc2eb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ services: - collection-keeper: + last-signal: build: . - image: collection-keeper:latest - container_name: collection-keeper + image: last-signal:latest + container_name: last-signal restart: unless-stopped ports: - - "40100:40100" + - "44092:44092" environment: TZ: Asia/Seoul THRESHOLD_DAYS: 90 @@ -13,10 +13,10 @@ services: # THRESHOLD_SECONDS: 10 COOLDOWN_SECONDS: 1 CHECK_INTERVAL_SECONDS: 60 - PORT: 40100 - DATABASE_PATH: /data/keeper.sqlite + PORT: 44092 + DATABASE_PATH: /data/last-signal.sqlite TRIGGER_SCRIPT: /scripts/cleanup.sh - COLLECTION_PATH: /home/zenn/Collection + TARGET_PATH: /home/zenn/Collection BACKGROUND: random RESET_TOKEN: ${RESET_TOKEN:?Set RESET_TOKEN in .env} PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-https://collection.sori.studio} @@ -26,11 +26,11 @@ services: MAIL_TO: ${MAIL_TO:-zenn.message@gmail.com} MAIL_CONFIRM_TOKEN: ${MAIL_CONFIRM_TOKEN:-} volumes: - - keeper-data:/data + - last-signal-data:/data - ./config/default.json:/app/config/default.json:ro - ./scripts:/scripts:ro - ./assets/backgrounds:/app/assets/backgrounds:ro - /home/zenn/Collection:/home/zenn/Collection volumes: - keeper-data: + last-signal-data: diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 2c0e579..7c1ae53 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -1,4 +1,4 @@ -# Collection Keeper 설치 가이드 +# Last Signal 설치 가이드 이 문서는 컴퓨터를 잘 모르는 분도 따라할 수 있도록 작성했습니다. @@ -12,16 +12,18 @@ ## 1. 이 프로그램이 하는 일 -Collection Keeper는 웹페이지에 들어와서 가끔 클릭해 주는 작은 서비스입니다. +Last Signal은 웹페이지에 들어와서 가끔 생존 확인 버튼을 눌러주는 서비스입니다. - 클릭하면 "아직 살아있음" 기록이 남습니다. - 일정 기간 동안 아무도 클릭하지 않으면, 미리 준비한 스크립트가 실행됩니다. -- NAS에서는 `/home/zenn/Collection` 폴더를 삭제하도록 설정되어 있습니다. +- 확인이 설정된 기간 동안 없으면 지정된 스크립트를 실행합니다. +- 기본 스크립트는 `/home/zenn/Collection` 폴더 **안의 내용 전체를 삭제**합니다. 폴더 자체는 남습니다. 중요: - 웹앱이 직접 폴더를 지우지 않습니다. -- 실제 삭제는 `scripts/cleanup.sh`가 담당합니다. +- 웹앱이 직접 파일을 지우지는 않습니다. 기본 `scripts/cleanup.sh`가 `TARGET_PATH`의 내용을 삭제합니다. +- `docker-compose.yml`에서 호스트 폴더를 컨테이너에 마운트하고, `TARGET_PATH`에는 컨테이너 쪽 경로를 지정합니다. --- @@ -173,7 +175,7 @@ config/default.json | --- | --- | --- | | `threshold_days` | `90` | 며칠 동안 클릭이 없으면 스크립트 실행 | | `cooldown_seconds` | `1` | 클릭 인정 최소 간격 | -| `collection_path` | `/home/zenn/Collection` | 삭제 대상 폴더 | +| `target_path` | `/home/zenn/Collection` | 만료 시 스크립트가 처리할 대상 폴더 | | `trigger_script` | `./scripts/cleanup.sh` | 실행할 스크립트 | | `port` | `40100` | 웹 접속 포트 | @@ -207,13 +209,13 @@ docker compose up -d --build ```bash docker compose ps -docker compose logs -f collection-keeper +docker compose logs -f last-signal ``` 브라우저 접속: ```text -http://NAS_IP:40100 +http://NAS_IP:44092 ``` --- @@ -228,10 +230,10 @@ http://NAS_IP:40100 | `threshold_seconds` | `null` | 테스트용 초 단위 타이머. 값이 있으면 일수보다 우선 | | `cooldown_seconds` | `1` | 클릭 인정 쿨다운 | | `check_interval_seconds` | `60` | 스크립트 검사 주기 | -| `database_path` | `./data/keeper.sqlite` | SQLite 경로 | +| `database_path` | `./data/last-signal.sqlite` | SQLite 경로 | | `trigger_script` | `./scripts/cleanup.sh` | 만료 시 실행 스크립트 | | `background` | `random` | 배경 이미지 설정 | -| `collection_path` | `/home/zenn/Collection` | Collection 폴더 | +| `target_path` | `/home/zenn/Collection` | 만료 시 스크립트가 처리할 대상 폴더 | | `port` | `40100` | 웹 포트 | | `recent_traces_limit` | `5` | 최근 흔적 개수 | | `reset_token` | `""` | 초기화 API 보호 토큰 | @@ -264,7 +266,7 @@ http://NAS_IP:40100 - `PORT` - `DATABASE_PATH` - `TRIGGER_SCRIPT` -- `COLLECTION_PATH` +- `TARGET_PATH` - `BACKGROUND` - `RESET_TOKEN` - `TZ` @@ -284,10 +286,12 @@ http://NAS_IP:40100 ```bash cd /home/zenn/docker/collection.sori.studio git pull -docker compose down +docker compose down --remove-orphans docker compose up -d --build ``` +새 데이터 볼륨 이름은 `last-signal-data`입니다. 기존 `keeper-data` 볼륨은 새 설정에서 사용하지 않습니다. + 개발 서버: ```bash diff --git a/package-lock.json b/package-lock.json index 37d287f..708de9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "collection-keeper", + "name": "last-signal", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "collection-keeper", + "name": "last-signal", "version": "1.0.0", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fce6f2c..a95ef0b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "collection-keeper", + "name": "last-signal", "version": "1.0.0", "description": "A lightweight survival check web app for personal NAS users", "main": "src/server.js", diff --git a/public/app.js b/public/app.js index 9a7082d..f80a702 100644 --- a/public/app.js +++ b/public/app.js @@ -1,4 +1,4 @@ -const custom = window.COLLECTION_KEEPER_CUSTOM ?? {}; +const custom = window.LAST_SIGNAL_CUSTOM ?? {}; const activeEmojis = custom.emojis?.length ? custom.emojis : ['✨']; const activeClickMessages = custom.clickMessages?.length ? custom.clickMessages : ['기록은 남겨둘게.']; const timeMessageGroups = { @@ -23,7 +23,7 @@ const NOTICE_THRESHOLDS = { const SEOUL_TIME_ZONE = 'Asia/Seoul'; const KEEPER_TEXT_LINES = custom.keeperTextLines?.length ? custom.keeperTextLines - : ['Collection Keeper']; + : ['Last Signal']; const FAREWELL_TEXT_LINES = custom.farewellTextLines?.length ? custom.farewellTextLines : ['안녕.', '고마웠어.']; diff --git a/public/custom.js b/public/custom.js index 1b48ecc..91c6ca0 100644 --- a/public/custom.js +++ b/public/custom.js @@ -1,7 +1,7 @@ -// Collection Keeper 사용자 설정 파일 +// Last Signal 사용자 설정 파일 // 이 파일만 수정하면 화면 문구, 클릭 이모지, 클릭 메시지를 바꿀 수 있습니다. -window.COLLECTION_KEEPER_CUSTOM = { +window.LAST_SIGNAL_CUSTOM = { // 오른쪽 아래에 표시할 글입니다. 줄을 추가하거나 지우면 화면에도 그대로 반영됩니다. keeperTextLines: [ '가끔은 생각해.', diff --git a/public/index.html b/public/index.html index 31c5c56..f706f8d 100644 --- a/public/index.html +++ b/public/index.html @@ -3,7 +3,7 @@ - Collection Keeper + Last Signal diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh index ccd660f..b9ead39 100755 --- a/scripts/cleanup.sh +++ b/scripts/cleanup.sh @@ -1,20 +1,35 @@ #!/bin/sh set -eu -TARGET="${COLLECTION_PATH:-/home/zenn/Collection}" +TARGET="${TARGET_PATH:-/home/zenn/Collection}" case "$TARGET" in - /home/zenn/Collection) ;; + /*) ;; *) - echo "[collection-keeper] refused unsafe path: $TARGET" + echo "[last-signal] refused non-absolute target path: $TARGET" exit 1 ;; esac -if [ ! -e "$TARGET" ]; then - echo "[collection-keeper] target not found: $TARGET" +if [ -L "$TARGET" ]; then + echo "[last-signal] refused symbolic-link target: $TARGET" + exit 1 +fi + +if [ ! -d "$TARGET" ]; then + if [ -e "$TARGET" ]; then + echo "[last-signal] refused non-directory target: $TARGET" + exit 1 + fi + echo "[last-signal] target directory not found: $TARGET" exit 0 fi -rm -rf -- "$TARGET" -echo "[collection-keeper] deleted: $TARGET" +TARGET_REAL="$(CDPATH= cd "$TARGET" && pwd -P)" +if [ "$TARGET_REAL" = "/" ]; then + echo "[last-signal] refused filesystem root" + exit 1 +fi + +find "$TARGET_REAL" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + +echo "[last-signal] deleted contents of: $TARGET_REAL" diff --git a/scripts/encrypt.sh b/scripts/encrypt.sh index 155e397..eb4dad6 100755 --- a/scripts/encrypt.sh +++ b/scripts/encrypt.sh @@ -1,8 +1,8 @@ #!/bin/sh set -eu -echo "[collection-keeper] encrypt.sh example script" -echo "[collection-keeper] COLLECTION_PATH=${COLLECTION_PATH:-/home/zenn/Collection}" -echo "[collection-keeper] Encrypt sensitive files here." +echo "[last-signal] encrypt.sh example script" +echo "[last-signal] TARGET_PATH=${TARGET_PATH:-/home/zenn/Collection}" +echo "[last-signal] Encrypt sensitive files here." exit 0 diff --git a/scripts/warning.sh b/scripts/warning.sh index 4e94e9a..501fb23 100755 --- a/scripts/warning.sh +++ b/scripts/warning.sh @@ -1,8 +1,8 @@ #!/bin/sh set -eu -echo "[collection-keeper] warning.sh example script" -echo "[collection-keeper] COLLECTION_PATH=${COLLECTION_PATH:-/home/zenn/Collection}" -echo "[collection-keeper] Send a warning notification here." +echo "[last-signal] warning.sh example script" +echo "[last-signal] TARGET_PATH=${TARGET_PATH:-/home/zenn/Collection}" +echo "[last-signal] Send a warning notification here." exit 0 diff --git a/src/config.js b/src/config.js index 43eda75..8b891e6 100644 --- a/src/config.js +++ b/src/config.js @@ -25,10 +25,15 @@ function loadConfig() { check_interval_seconds: Number( process.env.CHECK_INTERVAL_SECONDS ?? fileConfig.check_interval_seconds ?? 60 ), - database_path: process.env.DATABASE_PATH ?? fileConfig.database_path ?? './data/keeper.sqlite', + database_path: process.env.DATABASE_PATH ?? fileConfig.database_path ?? './data/last-signal.sqlite', trigger_script: process.env.TRIGGER_SCRIPT ?? fileConfig.trigger_script ?? './scripts/cleanup.sh', background: process.env.BACKGROUND ?? fileConfig.background ?? 'random', - collection_path: process.env.COLLECTION_PATH ?? fileConfig.collection_path ?? '/home/zenn/Collection', + target_path: + process.env.TARGET_PATH ?? + process.env.COLLECTION_PATH ?? + fileConfig.target_path ?? + fileConfig.collection_path ?? + '/home/zenn/Collection', port: Number(process.env.PORT ?? fileConfig.port ?? 40100), recent_traces_limit: Number( process.env.RECENT_TRACES_LIMIT ?? fileConfig.recent_traces_limit ?? 5 diff --git a/src/confirmPage.js b/src/confirmPage.js index 157e6f6..20e8b92 100644 --- a/src/confirmPage.js +++ b/src/confirmPage.js @@ -4,7 +4,7 @@ function renderConfirmSuccessPage() { - Collection Keeper + Last Signal