Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
7ee65b1
fix: strip queue ID prefix from final library filenames and folders
drondeseries Dec 28, 2025
56f9b6b
fix(health): ensure playback failures trigger immediate repair
drondeseries Dec 29, 2025
b98f8a7
fix(importer): do not mark items as fallback if fallback is not confi…
drondeseries Dec 29, 2025
6a66bd8
Merge pull request #46 from javi11/main
drondeseries Dec 29, 2025
e6857f9
Merge pull request #47 from drondeseries/fix/fallback-not-configured
drondeseries Dec 29, 2025
d13700c
Merge pull request #48 from drondeseries/fix/playback-failure-repair
drondeseries Dec 29, 2025
e3b8002
fix(health): allow periodic re-checks for healthy files
drondeseries Dec 29, 2025
100d331
fix(health): allow periodic re-checks for healthy files
drondeseries Dec 29, 2025
9856fab
fix(health): add 1-hour delay between repair retries
drondeseries Dec 29, 2025
1e3e6d0
Merge pull request #49 from javi11/main
drondeseries Dec 29, 2025
df1a956
fix(api): include DownloadClientBaseURL in SABnzbd config response
drondeseries Dec 29, 2025
748ab2e
fix(arrs): improve category handling for ARR auto-setup and connectiv…
drondeseries Dec 29, 2025
1ed7bc3
fix(ui): improve default category handling for new ARR instances
drondeseries Dec 29, 2025
2b0617d
fix(arrs): make library_dir optional for sonarr rescan triggering
drondeseries Dec 30, 2025
79b7561
fix(health): persist library sync result and fix frontend display
drondeseries Dec 30, 2025
0bc1ab7
fix: resolve test compilation error and remove unused field
drondeseries Dec 30, 2025
f24d945
fix: remove unused field to satisfy linter
drondeseries Dec 30, 2025
e31fcf6
Merge branch 'fix/library-sync-status' into fix/all-consolidated-fixes
drondeseries Dec 30, 2025
d102b8a
Merge branch 'fix/allow-healthy-recheck' into fix/all-consolidated-fixes
drondeseries Dec 30, 2025
c6d1670
Merge branch 'fix/arr-category-registration' into fix/all-consolidate…
drondeseries Dec 30, 2025
da469d9
Merge branch 'fix/fallback-not-configured' into fix/all-consolidated-…
drondeseries Dec 30, 2025
d8d7921
Merge branch 'fix/playback-failure-repair' into fix/all-consolidated-…
drondeseries Dec 30, 2025
b0bf6f4
Merge branch 'fix/sabnzbd-api-url-missing' into fix/all-consolidated-…
drondeseries Dec 30, 2025
6f27905
Fix(bugs): import file and frontend
MythodeaLoL Dec 30, 2025
d6d880c
other changes
MythodeaLoL Dec 30, 2025
b123077
Merge remote-tracking branches 'origin/fix/allow-healthy-recheck' and…
drondeseries Dec 30, 2025
b6b6c5e
Merge branch 'main' into fix-bugs-import-file-and-frontend
MythodeaLoL Dec 30, 2025
603a39d
Merge pull request #59 from MythodeaLoL/fix-bugs-import-file-and-fron…
drondeseries Dec 30, 2025
03d8f35
Merge pull request #60 from javi11/main
drondeseries Dec 31, 2025
1162678
Merge remote-tracking branch 'upstream/main'
drondeseries Dec 31, 2025
87644ae
Merge remote-tracking branch 'origin/main'
drondeseries Dec 31, 2025
ebd68fe
fix(importer): consolidate importer fixes
drondeseries Dec 31, 2025
6b60b23
fix(api): correct target path calculation in import response
drondeseries Dec 31, 2025
d6fd989
fix(health): persist library sync status and result
drondeseries Dec 31, 2025
e5a9426
fix(ui): improve ARRs config validation and defaults
drondeseries Dec 31, 2025
2666d35
refactor(health): remove metadata and library deletion counts from sy…
drondeseries Dec 31, 2025
6d8b78e
Merge branch 'split/importer-fixes'
drondeseries Dec 31, 2025
5dab1ca
Merge branch 'split/health-persistence'
drondeseries Dec 31, 2025
4dc457c
Merge branch 'split/arrs-frontend'
drondeseries Dec 31, 2025
d7293d5
chore(arrs): move queue cleanup logs to debug level to reduce noise
drondeseries Jan 1, 2026
dca27e5
fix(arrs): fix compilation error in worker queue cleanup
drondeseries Jan 1, 2026
ef058f3
feat(frontend): display provider top speed in health page
drondeseries Jan 1, 2026
be250a0
feat(frontend): remove failure reason column from provider health table
drondeseries Jan 1, 2026
dee720b
fix(api): normalize SABnzbd paths and expose fallback items in history
drondeseries Jan 1, 2026
56a0940
fix(nzbdav): limit concurrency and use read-only mode for parser
drondeseries Jan 1, 2026
2e5d9e2
feat(nzbdav): log available tables during import for debugging
drondeseries Jan 1, 2026
47f0baa
refactor(nzbdav): rewrite parser to stream files ordered by release path
drondeseries Jan 1, 2026
7cb34dc
refactor(nzbdav): rewrite parser to stream files ordered by release p…
drondeseries Jan 1, 2026
0638967
feat(nzbdav): add support for DavMultipartFiles and AES encryption me…
drondeseries Jan 1, 2026
a68047d
merge(nzbdav): resolve conflicts and include multipart/AES support
drondeseries Jan 1, 2026
e6606f4
perf(nzbdav): optimize memory usage and improve import robustness
drondeseries Jan 1, 2026
ac6105d
feat(nzbdav): robust import with support for DavMultipartFiles and AE…
drondeseries Jan 1, 2026
c0ff3b4
feat(importer): implement directory watcher for automated NZB imports
drondeseries Jan 1, 2026
6b99044
perf(nzbdav): optimize query grouping and add progress logging
drondeseries Jan 1, 2026
0c499b5
merge(nzbdav): update parser with multipart and performance fixes
drondeseries Jan 1, 2026
975690f
perf(nzbdav): optimize query grouping and add progress logging
drondeseries Jan 1, 2026
853fc6a
feat(ui): add watch directory configuration to import settings
drondeseries Jan 1, 2026
2e96843
Merge branch 'feat/watch-dir'
drondeseries Jan 1, 2026
6c22cb1
fix(importer): use persistent storage for NZBDav temp files to preven…
drondeseries Jan 1, 2026
17a0317
fix(importer): use persistent storage for NZBDav temp files to preven…
drondeseries Jan 1, 2026
ff20256
fix(importer): fix filename truncation by quoting subjects and improv…
drondeseries Jan 1, 2026
e065749
fix(importer): fix filename truncation by quoting subjects and improv…
drondeseries Jan 1, 2026
0112b49
merge: sync fixes from pr/nzbdav-fixes to main
drondeseries Jan 1, 2026
9dc6dac
fix(nzbdav): intelligently handle 'extracted' folders and properly es…
drondeseries Jan 1, 2026
5724ca1
fix(nzbdav): intelligently handle 'extracted' folders and properly es…
drondeseries Jan 1, 2026
988b5cd
feat(config): set skip_health_check default to true
drondeseries Jan 1, 2026
55eee57
feat(config): set skip_health_check default to true
drondeseries Jan 1, 2026
d61b863
merge: final fixes and config updates from pr/nzbdav-fixes
drondeseries Jan 1, 2026
a998636
feat(config): restore skip_health_check default to true
drondeseries Jan 1, 2026
8752c29
feat(config): restore skip_health_check default to true
drondeseries Jan 1, 2026
4a39659
chore: remove redundant watch_nzbs.sh script
drondeseries Jan 1, 2026
cf12138
chore: remove redundant watch_nzbs.sh script
drondeseries Jan 1, 2026
f01ca4e
Merge branch 'main' into pr/nzbdav-fixes
drondeseries Jan 1, 2026
1a5ecda
fix(api): strip internal ID prefix from queue item filenames for clea…
drondeseries Jan 1, 2026
e383024
fix(watcher): correctly handle category assignment and subfolder pres…
drondeseries Jan 1, 2026
3e01f5d
fix(api): include watch_dir and watch_interval_seconds in config resp…
drondeseries Jan 1, 2026
68df1cd
fix(importer): ensure category is correctly merged into virtual path …
drondeseries Jan 1, 2026
7cf41e0
fix(importer): align NZB persistence and TargetPath handling with ups…
drondeseries Jan 2, 2026
ce82ccb
refactor: simplify usenet writer, cleanup code style, and update depe…
javi11 Jan 2, 2026
cd785d0
merge: apply cleanup and dependency updates from upstream
drondeseries Jan 3, 2026
d805134
fix: ensure NZB cleanup after both success and failure
drondeseries Jan 3, 2026
00eea09
feat: bypass /tmp for NZB uploads and ensure cleanup after failure
drondeseries Jan 3, 2026
b0c626c
Revert "refactor: simplify usenet writer, cleanup code style, and upd…
drondeseries Jan 3, 2026
0775ba6
feat(importer): move failed NZB files to .nzbs/failed instead of dele…
drondeseries Jan 3, 2026
346d24c
feat(importer): add configurable NZB cleanup behavior (defaults to de…
drondeseries Jan 3, 2026
c3c3657
fix(importer): use persistent storage for NZBDav temp files to preven…
drondeseries Jan 1, 2026
838f850
fix(importer): align NZB persistence and TargetPath handling with ups…
drondeseries Jan 2, 2026
1ce986f
fix: ensure NZB cleanup after both success and failure
drondeseries Jan 3, 2026
4be9c09
feat: bypass /tmp for NZB uploads and ensure cleanup after failure
drondeseries Jan 3, 2026
f6f4cc3
feat(importer): move failed NZB files to .nzbs/failed instead of dele…
drondeseries Jan 3, 2026
71bea53
feat(importer): add configurable NZB cleanup behavior (defaults to de…
drondeseries Jan 3, 2026
2c510fd
fix(api): return error if arrs disabled on sabnzbd auth
drondeseries Jan 3, 2026
d0d1b73
chore: remove accidental log file
drondeseries Jan 3, 2026
0e7016c
chore: remove accidental log file
drondeseries Jan 3, 2026
4446526
fix(importer): correct AES encryption type in RAR file validation
drondeseries Jan 4, 2026
e86ab8d
fix: resolve watcher persistence and stop issues
drondeseries Jan 4, 2026
25611ae
Merge upstream/main and resolve conflicts
drondeseries Jan 4, 2026
7491677
Merge upstream/main and resolve final conflicts
drondeseries Jan 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/altmount/cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/javi11/altmount/internal/health"
"github.com/javi11/altmount/internal/httpclient"
"github.com/javi11/altmount/internal/importer"
"github.com/javi11/altmount/internal/importer/scanner"
"github.com/javi11/altmount/internal/metadata"
"github.com/javi11/altmount/internal/nzbfilesystem"
"github.com/javi11/altmount/internal/pool"
Expand Down Expand Up @@ -82,7 +83,8 @@ func initializeImporter(
Workers: maxProcessorWorkers,
}

importerService, err := importer.NewService(serviceConfig, metadataService, db, poolManager, rcloneClient, configGetter, healthRepo, broadcaster, userRepo)
nzbdavImporter := scanner.NewNzbDavImporter(db.Repository)
importerService, err := importer.NewService(serviceConfig, metadataService, db, poolManager, rcloneClient, configGetter, healthRepo, broadcaster, userRepo, nzbdavImporter)
if err != nil {
slog.ErrorContext(ctx, "failed to create importer service", "err", err)
return nil, err
Expand Down
7 changes: 7 additions & 0 deletions config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ import:
import_strategy: 'NONE' # Import strategy: NONE (direct import), SYMLINK (create symlinks), STRM (create .strm files)
import_dir: '' # Import directory (required when import_strategy is SYMLINK or STRM, must be absolute path)
skip_health_check: true # Bypass Usenet article validation during import (default: true)
# Directory Watcher settings
watch_dir: '' # Path to monitor for new .nzb files. Leave empty to disable.
watch_interval_seconds: 10 # How often to scan the watch directory (default: 10s)
# NZB cleanup behavior
nzb_cleanup_behavior:
on_success: 'delete' # Options: delete, keep (default: delete)
on_failure: 'delete' # Options: delete, keep (default: delete)

# Health monitoring configuration
health:
Expand Down
28 changes: 0 additions & 28 deletions config/altmount.log

This file was deleted.

2 changes: 2 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ import:
segment_sample_percentage: 1
import_strategy: NONE
import_dir: null
watch_dir: /config/watch
watch_interval_seconds: 15
log:
file: /config/altmount.log
level: info
Expand Down
24 changes: 10 additions & 14 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,12 @@ ENV PGID=${PGID}
# Set the working directory inside the container
WORKDIR /app

# Copy web binary from builder
COPY --from=backend-builder /app/altmount /app/altmount
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist

# Install required packages for runtime (mime-support already in custom-base)
# Install required packages and rclone in a single cached layer
# This layer will only rebuild if the Dockerfile itself changes
RUN apt-get update && \
apt-get install -y --no-install-recommends \
wget \
ca-certificates && \
rm -rf /var/lib/apt/lists/*

# Install rclone and FUSE support for internal mount functionality
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
fuse3 \
curl \
unzip && \
Expand All @@ -117,12 +109,16 @@ RUN apt-get update && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*

# Make binary executable
RUN chmod +x /app/altmount

# Copy s6-overlay service configuration
COPY docker/root/ /

# Copy web binary and frontend from builders (at the end to maximize cache usage)
COPY --from=backend-builder /app/altmount /app/altmount
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist

# Make binary executable
RUN chmod +x /app/altmount

# Set environment variables
ENV PORT=8080
ENV HOST=0.0.0.0
Expand Down
75 changes: 74 additions & 1 deletion frontend/src/components/config/WorkersConfigSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,30 @@ export function ImportConfigSection({

const handleInputChange = (
field: keyof ImportConfig,
value: number | boolean | string | string[],
value: number | boolean | string | string[] | any,
) => {
const newData = { ...formData, [field]: value };
setFormData(newData);
setHasChanges(JSON.stringify(newData) !== JSON.stringify(config.import));
};

const handleNestedInputChange = (
parentField: keyof ImportConfig,
childField: string,
value: string
) => {
const parentData = formData[parentField] as any || {};
const newData = {
...formData,
[parentField]: {
...parentData,
[childField]: value
}
};
setFormData(newData);
setHasChanges(JSON.stringify(newData) !== JSON.stringify(config.import));
};

const handleSave = async () => {
if (onUpdate && hasChanges) {
await onUpdate("import", formData);
Expand Down Expand Up @@ -252,6 +269,11 @@ export function ImportConfigSection({
)}
</div>

<<<<<<< HEAD
<div className="divider" />

=======
>>>>>>> upstream/main
<div className="space-y-4">
<div>
<h4 className="font-medium">Watch Directory</h4>
Expand Down Expand Up @@ -295,6 +317,57 @@ export function ImportConfigSection({
)}
</div>

<<<<<<< HEAD
<div className="divider" />

{/* NZB Cleanup Behavior Configuration */}
<div className="space-y-4">
<div>
<h4 className="font-medium text-lg">NZB Cleanup Behavior</h4>
<p className="text-base-content/70 text-sm">
Configure what happens to the source NZB file after processing.
</p>
</div>

<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<fieldset className="fieldset">
<legend className="fieldset-legend">On Success</legend>
<select
className="select w-full"
value={formData.nzb_cleanup_behavior?.on_success || "delete"}
disabled={isReadOnly}
onChange={(e) => handleNestedInputChange("nzb_cleanup_behavior", "on_success", e.target.value)}
>
<option value="delete">Delete</option>
<option value="keep">Keep</option>
</select>
<p className="label">
Action when import is successful (default: delete)
</p>
</fieldset>

<fieldset className="fieldset">
<legend className="fieldset-legend">On Failure</legend>
<select
className="select w-full"
value={formData.nzb_cleanup_behavior?.on_failure || "delete"}
disabled={isReadOnly}
onChange={(e) => handleNestedInputChange("nzb_cleanup_behavior", "on_failure", e.target.value)}
>
<option value="delete">Delete</option>
<option value="keep">Keep (Move to failed folder)</option>
</select>
<p className="label">
Action when import fails (default: delete)
</p>
</fieldset>
</div>
</div>

<div className="divider" />

=======
>>>>>>> upstream/main
<fieldset className="fieldset">
<legend className="fieldset-legend">Segment Sample Percentage</legend>
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,4 @@ export function ProviderHealth() {
</div>
</div>
);
}
}
11 changes: 9 additions & 2 deletions frontend/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ export interface LibrarySyncProgress {
export interface LibrarySyncResult {
files_added: number;
files_deleted: number;
metadata_deleted: number;
duration: string;
duration: number;
completed_at: string;
}

Expand Down Expand Up @@ -169,6 +168,11 @@ export interface FuseConfig {
// Import strategy type
export type ImportStrategy = "NONE" | "SYMLINK" | "STRM";

export interface NzbCleanupBehaviorConfig {
on_success: string;
on_failure: string;
}

// Import configuration
export interface ImportConfig {
max_processor_workers: number;
Expand All @@ -183,6 +187,7 @@ export interface ImportConfig {
skip_health_check?: boolean;
watch_dir?: string | null;
watch_interval_seconds?: number | null;
nzb_cleanup_behavior: NzbCleanupBehaviorConfig;
}

// Log configuration
Expand Down Expand Up @@ -357,6 +362,7 @@ export interface ImportUpdateRequest {
skip_health_check?: boolean;
watch_dir?: string | null;
watch_interval_seconds?: number | null;
nzb_cleanup_behavior?: NzbCleanupBehaviorConfig;
}

// Log update request
Expand Down Expand Up @@ -443,6 +449,7 @@ export interface ImportFormData {
import_dir: string;
watch_dir?: string;
watch_interval_seconds?: number;
nzb_cleanup_behavior?: NzbCleanupBehaviorConfig;
}

export interface MetadataFormData {
Expand Down
11 changes: 7 additions & 4 deletions internal/api/queue_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,14 +398,17 @@ func (s *Server) handleUploadToQueue(c *fiber.Ctx) error {
priority = database.QueuePriorityNormal
}

// Create temporary directory for upload
tempDir := os.TempDir()
uploadDir := filepath.Join(tempDir, "altmount-uploads")
// Determine upload directory (prefer persistent storage)
uploadDir := filepath.Join(os.TempDir(), "altmount-uploads")
if s.importerService != nil {
uploadDir = s.importerService.GetNzbFolder()
}

if err := os.MkdirAll(uploadDir, 0755); err != nil {
return RespondInternalError(c, "Failed to create upload directory", err.Error())
}

// Save the uploaded file to temporary location
// Save the uploaded file directly to persistent location
tempFile := filepath.Join(uploadDir, file.Filename)
if err := c.SaveFile(file, tempFile); err != nil {
return RespondInternalError(c, "Failed to save file", err.Error())
Expand Down
16 changes: 12 additions & 4 deletions internal/api/sabnzbd_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,12 @@ func (s *Server) handleSABnzbdAddFile(c *fiber.Ctx) error {
}

// Build category path and create temporary file with category subdirectory
tempDir := os.TempDir()
uploadDir := filepath.Join(tempDir, "altmount-uploads")
// Build category path and create temporary file with category subdirectory (prefer persistent storage)
uploadDir := filepath.Join(os.TempDir(), "altmount-uploads")
if s.importerService != nil {
uploadDir = s.importerService.GetNzbFolder()
}

if err := os.MkdirAll(uploadDir, 0755); err != nil {
return s.writeSABnzbdErrorFiber(c, "Failed to create upload directory")
}
Expand Down Expand Up @@ -380,8 +384,12 @@ func (s *Server) handleSABnzbdAddUrl(c *fiber.Ctx) error {
}

// Create temporary file with category path
tempDir := os.TempDir()
uploadDir := filepath.Join(tempDir, "altmount-uploads")
// Build category path and create temporary file with category subdirectory (prefer persistent storage)
uploadDir := filepath.Join(os.TempDir(), "altmount-uploads")
if s.importerService != nil {
uploadDir = s.importerService.GetNzbFolder()
}

if err := os.MkdirAll(uploadDir, 0755); err != nil {
return s.writeSABnzbdErrorFiber(c, "Failed to create upload directory")
}
Expand Down
Loading
Loading