feat: rename app to Last Signal
Rename service, UI, storage, and target-path configuration; clarify expiration cleanup behavior.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
54
README.md
54
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
|
||||
```
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
: ['안녕.', '고마웠어.'];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Collection Keeper 사용자 설정 파일
|
||||
// Last Signal 사용자 설정 파일
|
||||
// 이 파일만 수정하면 화면 문구, 클릭 이모지, 클릭 메시지를 바꿀 수 있습니다.
|
||||
|
||||
window.COLLECTION_KEEPER_CUSTOM = {
|
||||
window.LAST_SIGNAL_CUSTOM = {
|
||||
// 오른쪽 아래에 표시할 글입니다. 줄을 추가하거나 지우면 화면에도 그대로 반영됩니다.
|
||||
keeperTextLines: [
|
||||
'가끔은 생각해.',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Collection Keeper</title>
|
||||
<title>Last Signal</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,7 @@ function renderConfirmSuccessPage() {
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Collection Keeper</title>
|
||||
<title>Last Signal</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -35,7 +35,7 @@ function renderConfirmSuccessPage() {
|
||||
<main>
|
||||
<h1>❤️ 확인 완료</h1>
|
||||
<p>다시 와줘서 고마워.</p>
|
||||
<p>컬렉션은 계속 안전하게 보관될 거야.</p>
|
||||
<p>설정한 폴더는 다음 확인 전까지 그대로 있어.</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>`;
|
||||
@@ -47,7 +47,7 @@ function renderConfirmErrorPage(message) {
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Collection Keeper</title>
|
||||
<title>Last Signal</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
@@ -127,7 +127,7 @@ function buildStatus(db, config) {
|
||||
threshold_days: config.threshold_days,
|
||||
threshold_seconds: config.threshold_seconds,
|
||||
cooldown_seconds: config.cooldown_seconds,
|
||||
collection_path: config.collection_path,
|
||||
target_path: config.target_path,
|
||||
recent_traces: getRecentEvents(db, config.recent_traces_limit).map((event) => ({
|
||||
emoji: event.emoji,
|
||||
time: formatTime(new Date(event.created_at)),
|
||||
|
||||
@@ -2,21 +2,21 @@ const nodemailer = require('nodemailer');
|
||||
|
||||
const EMAIL_TEMPLATES = {
|
||||
60: {
|
||||
subject: '[Collection Keeper] 오랜만이네.',
|
||||
subject: '[Last Signal] 오랜만이네.',
|
||||
headline: '오랜만이네.',
|
||||
body: [
|
||||
'안녕.',
|
||||
'',
|
||||
'마지막 확인 이후 60일이 지났어.',
|
||||
'',
|
||||
'컬렉션은 아직 안전하게 보관되고 있어.',
|
||||
'설정한 폴더는 아직 그대로 있어.',
|
||||
'',
|
||||
'혹시 잊고 있었더라도 괜찮아.',
|
||||
'아래 버튼을 누르면 바로 확인할 수 있어.',
|
||||
],
|
||||
},
|
||||
75: {
|
||||
subject: '[Collection Keeper] 조금 걱정하고 있어.',
|
||||
subject: '[Last Signal] 조금 걱정하고 있어.',
|
||||
headline: '조금 걱정하고 있어.',
|
||||
body: [
|
||||
'안녕.',
|
||||
@@ -26,11 +26,11 @@ const EMAIL_TEMPLATES = {
|
||||
'아직은 괜찮지만,',
|
||||
'생각보다 긴 시간이 흘렀네.',
|
||||
'',
|
||||
'컬렉션은 계속 기다리고 있어.',
|
||||
'설정한 폴더는 아직 그대로 있어.',
|
||||
],
|
||||
},
|
||||
89: {
|
||||
subject: '[Collection Keeper] 정말 마지막 안내야.',
|
||||
subject: '[Last Signal] 정말 마지막 안내야.',
|
||||
headline: '정말 마지막 안내야.',
|
||||
body: [
|
||||
'안녕.',
|
||||
@@ -139,7 +139,7 @@ async function sendAlertEmail(config, milestone, status) {
|
||||
const content = buildEmailContent(milestone, status, confirmUrl);
|
||||
|
||||
await transport.sendMail({
|
||||
from: `"Collection Keeper" <${config.mail_smtp_user}>`,
|
||||
from: `"Last Signal" <${config.mail_smtp_user}>`,
|
||||
to: config.mail_to,
|
||||
subject: content.subject,
|
||||
text: content.text,
|
||||
|
||||
@@ -101,7 +101,7 @@ async function evaluateTrigger() {
|
||||
|
||||
try {
|
||||
console.log(`[keeper] Threshold exceeded. Running script: ${config.trigger_script}`);
|
||||
const result = await runTriggerScript(config.trigger_script, config.collection_path);
|
||||
const result = await runTriggerScript(config.trigger_script, config.target_path);
|
||||
markTriggerExecuted(db, config.trigger_script, result.success, result.output);
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
function runTriggerScript(scriptPath, collectionPath) {
|
||||
function runTriggerScript(scriptPath, targetPath) {
|
||||
return new Promise((resolve) => {
|
||||
if (!fs.existsSync(scriptPath)) {
|
||||
resolve({
|
||||
@@ -14,7 +14,9 @@ function runTriggerScript(scriptPath, collectionPath) {
|
||||
const child = spawn('sh', [scriptPath], {
|
||||
env: {
|
||||
...process.env,
|
||||
COLLECTION_PATH: collectionPath,
|
||||
TARGET_PATH: targetPath,
|
||||
COLLECTION_PATH: targetPath,
|
||||
TRIGGER_SCRIPT: scriptPath,
|
||||
KEEPER_SCRIPT: scriptPath,
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
|
||||
Reference in New Issue
Block a user