Repository navigation
Releases: hxx0611/Domain-Monitor
Releases · hxx0611/Domain-Monitor
Release list
v0.8.9 — Documentation & Operations Closeout
v0.8.9 — Documentation & Operations Closeout
Phase 13A audit report archive
- Full security/reliability audit report (
docs/PHASE13A-AUDIT-REPORT.md) with Post-Audit Status appendix
Phase 13B — Domain/DNS action test coverage
- 36 tests added; total test suite reaches 849 tests / 57 files
Phase 13C — Production backup
- SQLite online backup API (better-sqlite3, consistent snapshot, read-only source)
- NFS persistent backup directory (mode 600, survives container rebuild)
- Daily QwenPaw cron
domain-monitor-daily-backup(0 13 * * * Asia/Shanghai, silent) - 7-day retention (auto-prune), failure → Telegram alert (timestamp/exit/error only, no secrets)
- Restore drill PASS
Phase 13D — SQLite/NFS migration preflight (blocked)
- Current NFSv3 +
nolockmount is NOT approved for hosting the production SQLite database - Production SQLite remains on local
/tmpoverlay - Production backups stored on NFS persistent storage
Phase 13E — Audit archive & documentation closeout
- Operations, disaster-recovery, handover, database, testing, and architecture documentation synchronized
- SQLite/NFS restriction documented
- PostgreSQL or a local persistent volume remains a future architecture option (not implemented)
Important
- Production SQLite remains on local
/tmpoverlay (/tmp/domain-monitor/data/domain-monitor.db) - Production backups are stored on NFS persistent storage
- Current NFSv3 +
nolockmount is NOT approved for hosting the production SQLite database
v0.8.8 — Windows CI SQLite Test Cleanup Fix
v0.8.8 — Windows CI SQLite Test Cleanup Fix
Fixed
- Windows CI: SQLite temp test database could not be deleted (Phase 12A).
rdap-link.test.tsfailed on thewindows-fresh-install (Node 24)job withEPERM / Permission denied / syscall: rmwhen theafterAllhook tried to remove its temporary directory. - Root cause:
@/dbopens a module-level SQLite connection (new Database(databaseUrl)) that lives for the whole process. The test's dynamic import of the domain repository triggered that connection, so at cleanup time a native file handle still heldtest.db. Linux allows unlinking an open file and silently masked the leak; Windows refuses to delete a file held open by a process. - Fix:
src/db/index.tsnow exports a test-lifecycle helpercloseDb()(never called by production code);rdap-link.test.tscloses the connection before deleting the temp directory and usesrmSync({ recursive, force, maxRetries: 5, retryDelay: 100 })as a residual native-lock fallback.
Notes
- Test infrastructure / cross-platform compatibility fix only. This release does not change production database behavior, does not touch migrations or schema, and does not change RDAP business logic.
- Windows Node 24 fresh-install CI passes with no EPERM; all four CI jobs pass with 813 tests / 55 files.
v0.8.7
v0.8.7 — Phase 11J: Channel Notification Timezone\n\n### Added\n- Channel-level notification timezone for Telegram: notification_channels.config gains an optional timezone field (IANA name, e.g. Asia/Shanghai / UTC / America/New_York), defaulting to UTC. No schema/migration change — the config is a JSON text column and existing channels without the field keep rendering exactly as before (UTC).\n- buildTelegramMessage(event, hostname, language, timezone) renders the message timestamp via Node's built-in Intl.DateTimeFormat as YYYY-MM-DD HH:mm:ss (Timezone) — language-independent numeric format, correct DST handling, zero new dependencies.\n- Internal storage stays UTC: only the rendered message timestamp is converted; event.occurredAt, the worker, dedup/CAS and the DB are untouched.\n- Channel edit UI gains a Message Timezone field (datalist of common IANA zones + free-form IANA name); saveTelegramChannelAction validates it (invalid_timezone) and persists it with the config; the channels table displays it.\n- New src/lib/notifications/timezone.ts (isValidTimezone, formatNotificationTime, COMMON_NOTIFICATION_TIMEZONES) + unit tests.\n\n### Quality\n- 813 tests (55 files) — 13 new (validation/render/action/view); tsc 0, next lint 0, prettier PASS, build PASS (BUILD_ID q1VeYr6rXq2W46t7BNaOm)\n\n### Notes\n- Scope: Telegram message timestamp only. webhook / email notifications and UI time displays are NOT covered in this release (decided separately).\n- Default UTC keeps every existing channel byte-for-byte compatible — zero migration.
v0.8.6
v0.8.6 — Phase 11I: Channel-Level Notification Language
Added
- Per-channel notification language for Telegram:
notification_channels.configgains optionallanguage(en/zh-CN), defaulten— no schema/migration change, existing channels unchanged buildTelegramMessage(event, hostname, language)localizes template labels (app title / event / domain / status / time / event ID) and event labels; machine state values stay canonical (ok/down/server_error/client_error, HTTP status codes) to avoid ambiguity- Channel edit UI: Notification Language dropdown (English / 简体中文);
saveTelegramChannelActionvalidates (invalid_language) and persists with config - New
src/lib/notifications/i18n.tsdictionary + integrity tests (identical key sets across languages) - Language is independent from the UI locale (cookie-based)
Scope
- Telegram only (webhook / email channels adopt the dictionary later)
Quality
- 800 tests (54 files) — 15 new (parse/ignore language, zh-CN rendering incl. test-notification label, sender body language, action persistence/validation, toChannelView display); tsc 0, lint 0, prettier PASS, build PASS (BUILD_ID
CVfpMJqXYG7srT4aZ4GAt)
v0.8.5
v0.8.5 — Phase 11G-C: Telegram Rejection Diagnosis
Added
- When Telegram rejects a send (non-2xx), the sender now reads the API response body's
description(e.g.Forbidden: bot was blocked by the user) and includes it in the controlled error stored on the delivery:Telegram API returned HTTP 403: Forbidden: ... - Sanitization: description truncated to 200 chars; bot-token-shaped substrings →
[token]; URL-shaped →[url]; non-JSON/missing body → status-only message (unchanged behavior). Errors never contain the token, API base, or sensitive values
Quality
- 785 tests (53 files) — 5 new (description inclusion, token/URL redaction, non-JSON fallback, long-description truncation); tsc 0, lint 0, prettier PASS, build PASS (BUILD_ID
vaw7Vhxu43aSQAc2U_hOY)
Notes
- Added to diagnose a production Telegram 403 (controlled test sends reached the API and were rejected with HTTP 403, but the previous sender only recorded the status code). The precise Telegram-side reason is captured on the next real test send.
- No schema/migration change; no behavior change for successful sends
v0.8.4
v0.8.4 — Phase 11G-A: Controlled Test Notification
Added
- Admin-triggered test notifications:
sendTestNotificationAction(channelId)+ "Send Test Notification" button on the Notifications channel UI (EN/ZH)requireAdmin()guard; validates channel exists → enabled →type === "telegram"→ encrypted token configured (hasChannelSecret, never reads the value)- Exactly 1 event (
source="test",event_type="test_notification",dedupKey = test-notification:<channelId>:<nonce>) + exactly 1 delivery directly for the target channel — rule engine not involved, same-nonce replay absorbed by UNIQUE dedupKey - Reuses the existing chain:
createSender("telegram")→TelegramSender→getChannelSecret(AES-256-GCM) → Telegram API →deliverDeliverystate machine - Message explicitly labelled Test Notification; never contains tokens, ciphertext, secretRefs, DB paths or other sensitive values
Security
- Action never returns tokens/ciphertext/secretRefs/ENCRYPTION_KEY; machine-readable error codes (
unauthorized,channel_not_found,channel_disabled,unsupported_channel,secret_not_configured,no_domain,duplicate_request,send_failed) - Tests prove: unauthenticated →
unauthorized(no channel/event access), non-Telegram →unsupported_channel, no duplicate event/delivery on replay,attempts=1,error=nullon success, zero real Telegram API calls during tests (stubbed fetch + fakeAAH_*fixtures)
Quality
- 780 tests (53 files) — 17 new (unit + real-DB integration); tsc 0, next lint 0, prettier PASS, build PASS (BUILD_ID
hguDKK6wigYItW0QTJQx5)
Notes
- Deployment to production follows immediately for the Phase 11G-REAL gate (exactly one real Telegram test message through the production UI); real notification delivery remains an explicitly approved safety-gate exercise
v0.8.3 — Production Worker & Expiration Reminder
v0.8.3 — Production Worker & Expiration Reminder (2026-08-19)
Added
- Production Worker watchdog (Phase 11D):
scripts/worker-watchdog.sh— single-instance hourly scheduler (flock-guarded, self-contained, nopnpm-on-PATHdependency). Each tick runs the worker exactly once (./node_modules/.bin/tsx --conditions=react-server scripts/worker.ts --limit 50); a non-zero tick exit is logged and the loop continues;TERM/INTexit cleanly. - Hourly expiration reminder processing:
evaluateExpirationReminders()emits one deduplicatedexpiration_reminderevent per domain once the reminder day arrives. - Expiration reminder → delivery pipeline (Phase 11D):
insertEventsAndGenerateDeliveriescreates the event and its deliveries together; CAS claim (UPDATE … WHERE status='pending') guarantees at most one event, one delivery and one sender invocation per reminder per day. - Migration journal repair (Phase 11E):
0007_manual_expirationis now registered in the drizzle journal (_journal.jsonidx: 7,when: 1787063220000),0007_snapshot.jsongenerated (13 tables incl.expiration_reminders), and the production__drizzle_migrationstable registered with the migration's sha256 hash. A fresh0000 → 0007migration was verified end-to-end; the migration itself was not re-executed on any database.
Fixed
- Worker crash under react-server / production conditions (Phase 11D):
senders/factory.tsimported senders through the@/lib/domainsbarrel, which pulled innext/cacheand crashed under production conditions. The factory now imports from the repository module directly. - Expiration reminder events never produced deliveries (Phase 11D): events and deliveries are now created together via
insertEventsAndGenerateDeliveries.
Changed
- Version bumped from v0.8.2 → v0.8.3;
tsx@4.23.12added as a development dependency. src/db/migrations/meta/_journal.jsonnow has 8 entries (0000–0007);0007_snapshot.jsonadded.
Notes
- The worker is enabled in production (hourly watchdog). Real notification delivery was not exercised as part of this release; the real-notification safety gate remains a separate approved exercise.
- 763 tests / 51 files all green; tsc 0, lint 0, prettier PASS, build PASS.
v0.8.2 — Manual Expiration & Reminders
v0.8.2 — Manual Expiration & Reminders (Phase 11A)
Added
- Manual expiration (source
manual): set registration date, expiration date, registration platform (validated presets: GNAME, Alibaba Cloud, Tencent Cloud, Namecheap, Porkbun; or custom provider + HTTPS URL) from the Add/Edit domain forms. - Manual dates never overwritten by RDAP refreshes: Refresh only updates RDAP metadata or clears it for no-object/parent results.
- Expiration reminders: per-domain reminder days (1–3650, presets + custom), evaluated by the delivery worker as deduplicated
expiration_reminderevents (sourceexpiration, once per domain per day) through the normal rule → delivery pipeline. - Migration 0007:
expiration_source/registration_provider/registration_provider_urlcolumns +expiration_reminderstable (additive). - Worker summary now reports
expirationEvents.
Notes
- Automatic delivery is not yet enabled in production (worker
tsxruntime not installed there). UI + rule configuration are fully live; reminder delivery activates once the runtime dependency is installed andpnpm workeris scheduled via cron.
Quality
- 761 tests / 50 files, tsc / lint / prettier / build all green.
v0.8.1 — RDAP Ownership & Expiration Fixes
v0.8.1 — RDAP Ownership & Expiration Fixes (bugfix / stability release)
Bugfix release over v0.8.0 (which introduced admin authentication, the notification configuration UI, the Telegram channel, and encrypted secret storage — all unchanged here).
Fixed
- RDAP fallback ownership: registered-domain fallback no longer treats parent-domain data as child-domain ownership. A subdomain without its own RDAP object (e.g.
opusai.eu.cc→ 404) previously resolved to the parent (eu.cc) and the parent's expiration, registrar, nameservers and status were stored on the child row. RDAP results now carry explicit ownership detection (exact/parent), decided strictly from the RDAP object's canonical/LDH identity — never merely from "a fallback succeeded". - No parent data on the child: parent-derived expiration / registrar / nameservers / status are never written to the subdomain's own fields. The child is marked
rdap_status = ["no-object"]and its registration fields are cleared; the UI shows Unavailable instead of inventing a parent expiration for the subdomain. - Expiration-date UI duplication: the detail page showed the expiration twice (
Expirationheading plus anExpires: …prefix); it now renders a plain date. - Fallback safety net: fallback is allowed only for
not-found(HTTP 404 / no domain object) or a successful response without an expiration date; network, timeout, 429, 500 and invalid responses are never masked by a parent query; a bare TLD is never queried. - Production data repair: production records affected by the previous fallback behavior were corrected through the normal Refresh flow.
Notes
- No schema / migration changes.
updateDomainRdap(id, data, ownership)now requires an explicit ownership argument. - Telegram secret storage, authentication, and the notification pipeline are unchanged.
- Test suite: 708 tests (46 files) — tsc / lint / prettier all green.
v0.8.0
v0.8.0 — Admin Authentication, Telegram Notifications & Encrypted Secrets
Added
- Admin authentication: one-time
/setupwizard with scrypt-hashed admin password,/login//logout, one-time recovery code for password reset (recovery rotates the session secret, invalidating all old sessions). - HMAC-signed admin session: signed, HttpOnly, SameSite=Lax cookies with expiry + entropy; no third-party auth dependency.
- Protected pages and Server Actions:
/,/domains/[id],/notificationsand every mutating Server Action require an authenticated admin session. - Telegram notification channel: create/edit Telegram channels from the notification UI; bot token validated via Telegram
getMe(server-side only) before saving. - Encrypted notification secret storage: AES-256-GCM (
iv:tag:ciphertext) for the Telegram bot token;ENCRYPTION_KEYread from the environment (required in production). - Telegram sender secret resolution: encrypted store first, legacy
TELEGRAM_BOT_TOKENenv fallback, controlled failure when neither is available (decryption failure never falls back). - Notification configuration UI: channel CRUD, rule CRUD, token configuration status, delivery retry.
- Migration 0006:
admin_settingsandnotification_secretstables.
Security
- Telegram bot tokens encrypted at rest; never rendered back into HTML/RSC/client bundles — only CONFIGURED/NOT CONFIGURED status exposed.
- Unified, non-enumerating auth errors.
- SSRF protection kept: HTTPS-only outbound with per-redirect re-validation.
- No secret in logs: tokens, passwords, recovery codes, encryption keys never appear in logs/worker output/errors.
Changed
- Version bumped v0.7.3 → v0.8.0.
- Channel types now include Telegram alongside Webhook and Email.
- Secret handling moved to encrypted storage with legacy env fallback.
Fixed
- Notification configuration pages previously public; now admin-authenticated.
- Documentation synchronized with the implemented feature set (README bilingual, docs, CHANGELOG).
Compatibility
- Migration
0006adds two tables; existing data preserved (non-destructive). - Production requires
ENCRYPTION_KEY(32-byte / 64 hex). ExistingTELEGRAM_BOT_TOKENdeployments keep working via legacy fallback. - Node.js >= 22 supported (22 / 24 / 26 CI-tested).