Commit Graph
304 Commits
Author SHA1 Message Date
admin d8e01f3398 fix(backup): exclude TDLib's disposable file cache from the tdlib tar
continuous-integration/drone/push Build is passing
files/temp is TDLib's own redundant download cache — the worker
already prunes it after every ingestion run (see the recent
optimizeTdlibStorage fix), and its content still lives in the
source/destination Telegram chats regardless. With the cache grown
back to ~59GB between prune cycles, tarring it made today's backup
run for 4+ hours straight, fighting the actively-ingesting worker for
disk I/O and degrading the whole host. Excluding it keeps the backup
to what's actually irreplaceable: the DB dump and the TDLib session
state itself.
2026-08-14 11:40:03 +02:00
admin b8672a44d0 feat(worker): forward on allowsForwarding channels regardless of file type or listing success
continuous-integration/drone/push Build is passing
tryForwardArchiveSet only attempted the no-download forward path for
ZIP/RAR/7z archives, and bailed to download+reupload whenever the
ranged listing failed — even though the channel already grants
forwarding permission and forwarding a message costs nothing
regardless of what's inside it. Standalone DOCUMENT/STL/3MF
attachments never got a chance at the forward path at all.

Now any file on a forwarding-enabled channel is forwarded directly;
when there's no listing to derive a content hash from (non-archive
types, or ranged-listing failures), dedup falls back to
remote.unique_id identity — deriveForwardContentHash, crcFingerprint,
and compareFingerprints already degrade to this safely for
empty/incomplete entries, so there's no risk of unrelated files
colliding as false duplicates. Inner-file indexing is simply skipped
for these cases, matching the existing accepted limitation for
RAR/7z rebuild placeholders.
2026-08-13 18:52:34 +02:00
admin e123a5cc44 diag(worker): log the reason when RAR ranged-listing silently returns null
continuous-integration/drone/push Build is passing
Several channels with allowsForwarding=true were still falling back to
download+reupload for specific RAR archives after the signature-bytes
fix, with zero log output to explain why — walkRarVolume and
readRarListingRanged return null silently on signature-detection
failure, the MAX_RAR_BLOCKS/MAX_RAR_HEADER_BYTES guards, and a
non-positive advance. Log each case so the next occurrence identifies
the actual cause instead of requiring another guess-and-redeploy cycle.
2026-08-13 12:10:50 +02:00
admin 73b2c33305 fix(worker): include the RAR signature in the ranged-listing sparse reconstruction
continuous-integration/drone/push Build is passing
readRarListingRanged read the archive signature only to detect the RAR
version, then walkRarVolume began harvesting header regions at
pos = sigLen — the signature bytes themselves were never captured.
The reconstructed sparse file therefore started with zero bytes
instead of "Rar!", so every real unrar invocation rejected it as
"not RAR archive". This silently forced every RAR archive through the
expensive download+reupload fallback regardless of the source
channel's forwarding permission — defeating the forward-priority path
for the RAR-heavy channels it matters most for. The existing tests
didn't catch this because their assertions accepted either a null or
non-null result as passing, deferring real verification to production.
2026-08-11 23:17:22 +02:00
admin 06a48a419b fix(bot): resolve private chat before sending to a user
continuous-integration/drone/push Build is passing
After the bot's TDLib session was rebuilt from scratch (following a
disk-full corruption on 2026-08-05), sendMessage started failing with
"Chat not found" for every previously-known user — a fresh TDLib
database has no cached chat/peer info until createPrivateChat
explicitly resolves it. Call it before every send.
2026-08-10 08:27:41 +02:00
admin 7bc57ec227 fix(worker): prune TDLib's unbounded local file cache after each run
continuous-integration/drone/push Build is failing
TDLib keeps a permanent local copy of every file it downloads or
uploads (via inputFileLocal) with no automatic cleanup. Across the two
worker accounts this had grown to ~270GB, filling the host disk to 91%
and triggering a cascading disk-full failure in the bot's TDLib
session on 2026-08-05. Call optimizeStorage after every ingestion run
to clear it; a 5-minute immunity_delay protects files an in-flight
operation might still reference.
2026-08-10 08:17:51 +02:00
admin 148d688d43 fix(worker): fall through to download pipeline on any forward-path throw
continuous-integration/drone/push Build is passing
2026-07-31 05:25:16 +02:00
admin b28e38d233 feat(worker): fork to the forward-priority path in processOneArchiveSet 2026-07-31 05:14:11 +02:00
admin 662f5ac711 feat(worker): native TDLib forward from source to destination channel 2026-07-31 05:06:11 +02:00
admin 960da01ec6 feat(worker): cross-channel CRC-fingerprint repost check for the forward path 2026-07-31 05:00:14 +02:00
admin a46e746298 feat(worker): derive a dedup identity for forward-path packages without bytes 2026-07-31 04:53:24 +02:00
admin 26be615918 refactor(worker): promote ranged-listing dispatcher to a shared module 2026-07-31 04:46:39 +02:00
admin eda882dc90 feat(worker): detect + persist per-channel forwarding permission 2026-07-31 04:40:15 +02:00
admin 80d41ac78f feat(db): add TelegramChannel.allowsForwarding + IngestionRun.zipsForwarded 2026-07-31 04:30:33 +02:00
admin 40894267d4 Merge branch 'feat/ranged-archive-listing' into main
# Conflicts:
#	.env.example
#	backup/Dockerfile
#	docker-compose.yml
2026-07-31 04:24:58 +02:00
admin 9a45fdf6d9 docs: implementation plan for forward-priority ingestion pipeline
Task-by-task plan for the design in
docs/superpowers/specs/2026-07-30-forward-priority-ingestion-design.md,
grounded against current worker.ts/provenance-backfill.ts/schema
signatures so a fresh agent can execute it without prior context.
2026-07-30 23:15:57 +02:00
admin 809d72660d docs: design for forward-priority ingestion pipeline
Prioritize native Telegram forwarding over download+reupload for
source channels that allow it, reusing the ranged archive-listing
readers to keep indexing complete without a local download. Falls
back to the existing download+reupload pipeline per-channel (when
forwarding is blocked) and per-archive (when ranged listing fails).
2026-07-30 23:02:49 +02:00
admin 6102fd474f Merge branch 'main' of https://github.com/xCyanGrizzly/DragonsStash 2026-07-30 22:38:41 +02:00
adminandClaude Opus 4.8 dadf03212c fix(worker): clamp RAR header re-read to 8MB to bound corrupt-archive reads
Add MAX_RAR_HEADER_BYTES constant to prevent unbounded ranged reads when
a RAR block's HeaderSize is bogus. Real RAR block headers are far smaller;
this guards against amplification attacks on corrupt/desynced archives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 12:34:00 +02:00
admin 267c72bbe8 feat(worker): enable RAR ranged listing + format-aware destination reads 2026-07-27 12:06:46 +02:00
admin 497c4876a6 test(worker): lock in RAR multipart per-volume walk 2026-07-27 12:03:14 +02:00
adminandClaude Opus 4.8 f5d913eb18 feat(worker): RAR ranged header-walk + single-part listing
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 11:58:33 +02:00
adminandClaude Opus 4.8 1e11dd3fd8 fix(worker): read7zNumber throws on first-byte buffer overrun
Prevent silent masking of short reads by validating buffer bounds before
accessing the first byte. Continuation-byte overflow was already caught,
but a short read that leaves pos at/past buffer.length would return {0, pos+1}
instead of throwing, masking the error from callers' try/catch handlers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 11:55:59 +02:00
admin 086f58f9dd fix(worker): fetch packed header region for encoded-header 7z ranged listing 2026-07-27 11:50:46 +02:00
adminandClaude Opus 4.8 3595f6f097 docs: amend 7z ranged design for encoded (LZMA) headers
Live spike showed start+end sparse reconstruction is insufficient for
encoded-header 7z; fetch the mid-file packed header region as a 3rd
region (parse PackInfo). Adds Task 4b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 11:49:19 +02:00
adminandClaude Opus 4.8 abdfa437d9 feat(worker): RAR vint/signature/block-extent parsers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 11:39:19 +02:00
admin 49f14bcb0d feat(worker): dispatch 7z ranged listing + size-capped full-download fallback 2026-07-27 11:29:08 +02:00
adminandClaude Opus 4.8 d4a1cfec99 feat(worker): ranged 7z listing orchestrator + RangeReader
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 11:22:08 +02:00
admin ecedc0fec4 feat(worker): 7z signature-header parser 2026-07-27 11:17:58 +02:00
admin 1b1f5b7972 feat(worker): sparse-file reconstruction helper for ranged archive listing 2026-07-27 11:14:17 +02:00
adminandClaude Opus 4.8 e822ea3e76 docs: implementation plan for ranged RAR/7z inner-file listing
8 TDD tasks: sparse reconstruction, 7z end-header read, RAR block-walk
(incl. multipart), size-capped full-download fallback, format-aware
dispatch. 7z ships first behind the fallback as the productive spike.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 07:16:20 +02:00
adminandClaude Opus 4.8 9d16156161 docs: design for ranged inner-file listing (RAR & 7z)
Cheap listing without full download for RAR/7z placeholders: harvest header
regions via ranged reads, sparse-reconstruct, list with native 7z/unrar CLIs.
Full-download fallback (size-capped) for stragglers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 06:41:34 +02:00
adminandClaude Opus 4.8 2e7e6cca9b Backup: switch NAS transport to SMB/CIFS, fix crond/OOM/live-tar defects
Wire the backup service against the Synology share over SMB/CIFS (the NAS
authenticates with a user/password; NFS is IP-allowlist only). Also fixes
three defects found bringing the service up live:

- entrypoint crash-loop: dcron's crond fails "setpgid: Operation not
  permitted" in this runtime -> use busybox crond; make repo-init idempotent
  (check via `restic cat config`, tolerate init-on-existing) so a transient
  CIFS/lock hiccup can't kill PID 1.
- OOM: pg_dump of a ~276MB DB + tar + restic exceeded the 256M cap -> 1G.
- live tar abort: GNU tar exits 1 when TDLib files change mid-read (worker is
  live); per design this is best-effort, so tolerate exit 1, fatal only >=2.

Kuma push is now optional (empty URL disables alerting) since it's deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:36:19 +02:00
adminandClaude Opus 4.8 ceae4f384b Fix provenance-backfill multipart offsets, incomplete-fingerprint fallback, and add name-size audit trail
Multipart ZIP fingerprint reads now use per-part sizes instead of the
whole-archive total, so the tail download offset stays within the last
part's bounds on both the scanned side and the destination-copy side
(scannedFileId replaced with an ordered scannedParts list). A fingerprint
comparison is now only treated as a real mismatch when both sides have
complete CRCs and differ; incomplete comparisons (e.g. empty files) fall
back to name+size confidence instead of silently refusing to backfill.
Name+size-confidence backfills now also create an INFO
INTEGRITY_AUDIT systemNotification for later review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 14:46:45 +02:00
adminandClaude Opus 4.8 7595543386 feat(worker): notify on ambiguous provenance candidates instead of guessing
When multiple placeholder packages share the same name+size, try to
disambiguate via ZIP fingerprint; if that can't uniquely resolve a
single match, emit a SystemNotification and skip the backfill rather
than attributing provenance to the wrong package.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 14:28:34 +02:00
adminandClaude Opus 4.8 09ee9da9cc feat(worker): fingerprint listing-less ZIP candidates via destination copy
Rebuild-created placeholder candidates have no PackageFile CRCs, so
name-side fingerprinting can't confirm them. When the stored candidate
fingerprint is incomplete, read the candidate's own copy from its
destination message and fingerprint against that instead of falling
straight to name+size confidence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 14:27:07 +02:00
adminandClaude Opus 4.8 7ddf13053f feat(worker): opportunistic provenance backfill during scan
Wire tryProvenanceBackfill into processOneArchiveSet: before downloading
a scanned ZIP/RAR/7Z, check whether it's the true origin of a
placeholder-provenance package in the destination channel and backfill
in place, skipping the download. Add the zipsBackfilled counter through
PipelineContext, updateRunActivity, and completeIngestionRun.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 14:23:53 +02:00
adminandClaude Opus 4.8 4a7b9e2a09 feat(db): IngestionRun.zipsBackfilled counter
Additive migration (applied on deploy via prisma migrate deploy). Counts
packages whose provenance was backfilled during an ingestion run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 14:20:07 +02:00
adminandClaude Opus 4.8 9a06130c5b feat(worker): provenance-backfill orchestrator
Implements tryProvenanceBackfill() per Task 6 of the provenance-backfill
plan: looks up a placeholder candidate by fileName+fileSize, confirms ZIP
candidates via a ranged central-directory CRC32 fingerprint, and falls
back to name+size confidence for RAR/7z/failed listings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:23:15 +02:00
adminandClaude Opus 4.8 a7aa4ce285 feat(worker): DB helpers for provenance backfill
Adds findPlaceholderCandidate, getPackageFileCrcs, and
backfillProvenance to worker/src/db/queries.ts (Task 5). Candidate
predicate matches placeholder packages by source==dest or the
sourceMessageId==0 rebuild sentinel; backfillProvenance re-checks
placeholder status inside the transaction before overwriting fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:12:41 +02:00
adminandClaude Opus 4.8 38072d250f feat(worker): ranged TDLib file download (downloadFileRange)
Implements Task 4 Step 2 of the provenance-backfill plan. The live
spike (Step 1) and manual verification (Step 3) were not run in this
environment because a second TDLib client would corrupt the running
worker's authenticated session; the absolute-offset assumption is
noted as pending live verification on deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:12:37 +02:00
adminandClaude Opus 4.8 0bce1168a9 docs: correct provenance-backfill candidate predicate for rebuild records
Rebuild records use sourceMessageId=0 + synthetic 'rebuild:' contentHash and an
arbitrary fallback sourceChannelId, so the original sourceChannelId==destChannelId
candidate definition missed them. Predicate is now
(sourceChannelId==destChannelId OR sourceMessageId==0), verified against 59,893
live rebuilt records.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:05:34 +02:00
adminandClaude Opus 4.8 018b0f5d74 feat(worker): parse ZIP central directory from a tail buffer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:20:16 +02:00
adminandClaude Opus 4.8 c2590fb66f feat(worker): CRC32 archive fingerprint compare
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:19:28 +02:00
adminandClaude Opus 4.8 8b443620c8 test(worker): add vitest harness
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:18:57 +02:00
adminandClaude Opus 4.8 80aa2b0ee0 docs: provenance-backfill spec and implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:05:26 +02:00
adminandClaude Opus 4.8 f0e0e79d34 fix(auth): survive a stale session whose user was deleted
A JWT session pointing at a user no longer in the DB (e.g. after a DB reset)
made getUserSettings create settings for a non-existent user -> FK violation
(P2003) -> Server Component render crash. getUserSettings now returns defaults
on P2003; the (app) layout detects the missing user and redirects to a new
server-side /logout route that clears the cookie, avoiding the middleware
redirect loop that otherwise blocks reaching /login.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:05:26 +02:00
adminandClaude Opus 4.8 21bd46010f feat(stls): flat package list, drop heuristic auto-grouping
Render the STL view as a flat per-package list (listDisplayItems no longer
collapses packages into group rows) and hide the Ungrouped tab, now that the
creator filter organizes the list. Remove the worker's heuristic auto-grouping
passes (rule/time/pattern/creator/zip-path/reply-chain/caption); album grouping
is kept. Existing groups and manual grouping actions are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:05:18 +02:00
admin 2252ac01f5 Add build-backup CI step, include it in deploy dependencies 2026-07-23 01:17:35 +02:00
adminandClaude Sonnet 5 63df348028 Fix db service network alias for backup.sh pg_dump connection
The backup.sh script hardcodes 'pg_dump -h dragonsstash-db' (matching
production), but the db service lacked a network alias for that hostname.
Added network alias to the backend network so the backup service can
successfully connect using the hardcoded hostname.

Verified: both 'db' and 'dragonsstash-db' resolve from the backend network.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 01:15:20 +02:00