Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 2 deletions .github/workflows/inttest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ jobs:
go-version-file: go.mod
- name: Install archive tools
run: |
sudo apt-get update
sudo apt-get install -y rar p7zip-full zip unzip
sudo apt update
sudo apt install -y rar p7zip-full zip unzip
- name: Build unpackerr
run: go build -o unpackerr .
- name: Clone integration tests
Expand Down
3 changes: 2 additions & 1 deletion examples/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ services:
- UN_FOLDER_0_DISABLE_LOG=false
- UN_FOLDER_0_MOVE_BACK=false
- UN_FOLDER_0_EXTRACT_ISOS=false
- UN_FOLDER_0_PRESERVE_EXEC=false
- UN_FOLDER_0_SKIP_EMPTY=false
## Hook Payload
## Webhooks
Expand Down Expand Up @@ -174,4 +175,4 @@ services:
- UN_CMDHOOK_0_EXCLUDE_1=lidarr
- UN_CMDHOOK_0_TIMEOUT=10s

## => Content Auto Generated, 30 SEP 2026 07:06 UTC
## => Content Auto Generated, 04 OCT 2026 06:03 UTC
20 changes: 16 additions & 4 deletions examples/unpackerr.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,15 @@ remnant_action = "rename"
## Do not wrap the number in quotes. Raise this only if you have fast disks and CPU.
parallel = 1

## Use these configurations to control the file modes used for newly extracted
## files and folders. Recommend 0644/0755 or 0666/0777.
## Extracted files are created with this mode. The process umask still
## applies, so 0666 becomes 0644 under umask 0022. Use 0644, or 0660 or
## 0666 when another user must write the extracted files. Archive
## execute bits are kept only for a watched folder with preserve_exec
## enabled.
file_mode = "0644"

## Extracted directories are created with this mode. The process umask
## still applies.
dir_mode = "0755"

## List of passwords to use for encrypted archives. Must be a list of strings.
Expand Down Expand Up @@ -185,7 +191,7 @@ passwords = []
## The following sections can be repeated if you have more than one Sonarr, ##
## Radarr, Lidarr, Readarr, Folder, Webhook, and/or Command Hook. ##
## Identify each instance with a short key: [sonarr.uhd], [folder.tv]. ##
## Changed in v1.0.0 (September 2026): map keys, not [[array]] list rows. ##
## Changed in v1.0.0 (October 2026): map keys, not [[array]] list rows. ##
## You MUST uncomment the [header.0], url and api_key for any Starr app. ##
## Uncomment [sonarr.0], [radarr.0], [lidarr.0], and/or [readarr.0] plus the ##
## url and api_key if that app is in use. ##
Expand Down Expand Up @@ -443,6 +449,12 @@ passwords = []
# move_back = false
## Set this to true if you want this app to extract ISO files with .iso extension.
# extract_isos = false
## When true, an execute bit stored in the archive is kept for owner,
## group, or other only when file_mode already grants that class access.
## A 6 beside an archived 7 becomes 7, a 4 becomes 5, and a 0 stays 0.
## Off, extracted files use file_mode only. Starr extracts stay off.
## The process umask still applies.
# preserve_exec = false
## Incomplete-download suffixes such as `.part` or `.crdownload`. Extraction
## stays in waiting as long as any matching file is in the watched item's
## top folder. Subfolders are not scanned. The UI and config always store
Expand Down Expand Up @@ -547,4 +559,4 @@ passwords = []
## You can adjust how long to wait for the command to run.
# timeout = "10s"

## => Content Auto Generated, 30 SEP 2026 07:06 UTC
## => Content Auto Generated, 04 OCT 2026 06:03 UTC
13 changes: 9 additions & 4 deletions frontend/src/lib/i18n/locales/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,13 @@
},
"fileMode": {
"label": "Δικαιώματα αρχείων",
"description": "Δικαιώματα για νέα εξαγμένα αρχεία.",
"tooltip": "Μόνο Unix. Αγνοείται στα Windows."
"description": "Δικαιώματα για εξαγμένα αρχεία.",
"tooltip": "Μόνο Unix. Τα εξαγμένα αρχεία δημιουργούνται με αυτή τη λειτουργία. Η umask της διεργασίας εξακολουθεί να ισχύει. Αγνοείται στα Windows."
},
"dirMode": {
"label": "Δικαιώματα φακέλων",
"description": "Δικαιώματα για νέους εξαγμένους φακέλους.",
"tooltip": "Μόνο Unix. Αγνοείται στα Windows."
"description": "Δικαιώματα για εξαγμένους φακέλους.",
"tooltip": "Μόνο Unix. Οι εξαγμένοι φάκελοι δημιουργούνται με αυτή τη λειτουργία. Η umask της διεργασίας εξακολουθεί να ισχύει. Αγνοείται στα Windows."
},
"logFile": {
"label": "Αρχείο καταγραφής",
Expand Down Expand Up @@ -797,6 +797,11 @@
"description": "Τα αρχεία .iso αντιμετωπίζονται ως αρχειοθήκες.",
"tooltip": "Ανενεργό αγνοεί εικόνες ISO. Ενεργοποιήστε το μόνο αν θέλετε πραγματικά να αποσυμπιεστούν."
},
"preserve_exec": {
"label": "Διατήρηση bits εκτέλεσης",
"description": "Διατήρηση των bits εκτέλεσης του αρχείου.",
"tooltip": "Ανενεργό, τα εξαγμένα αρχεία παίρνουν μόνο τα δικαιώματα αρχείων. Ενεργό, ένα bit εκτέλεσης από το αρχείο διατηρείται για κάτοχο, ομάδα ή άλλους όταν τα δικαιώματα αρχείων δίνουν ήδη πρόσβαση σε αυτή την κλάση. Οι εξαγωγές Starr μένουν ανενεργές."
},
"disableRecursion": {
"label": "Αναδρομή",
"description": "Εξαγωγή αρχειοθηκών μέσα σε άλλες αρχειοθήκες.",
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/lib/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,13 @@
},
"fileMode": {
"label": "File mode",
"description": "Permissions for newly extracted files.",
"tooltip": "Unix only. Ignored on Windows."
"description": "Mode for extracted files.",
"tooltip": "Unix only. Extracted files are created with this mode. Umask still applies. Ignored on Windows."
},
"dirMode": {
"label": "Dir mode",
"description": "Permissions for newly extracted folders.",
"tooltip": "Unix only. Ignored on Windows."
"description": "Mode for extracted folders.",
"tooltip": "Unix only. Extracted folders are created with this mode. Umask still applies. Ignored on Windows."
},
"logFile": {
"label": "Log file",
Expand Down Expand Up @@ -797,6 +797,11 @@
"description": "Treat .iso files as archives.",
"tooltip": "Off ignores ISO images. Turn this on only if you really want them unpacked."
},
"preserve_exec": {
"label": "Keep execute bits",
"description": "Keep execute bits stored in the archive.",
"tooltip": "Off, extracted files use File mode only. On, an archived execute bit is kept for owner, group, or other when File mode already grants that class access. Starr extracts stay off."
},
"disableRecursion": {
"label": "Recursion",
"description": "Extract archives found inside other archives.",
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/lib/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,13 @@
},
"fileMode": {
"label": "Modo de archivo",
"description": "Permisos para archivos extraídos nuevos.",
"tooltip": "Solo Unix. Se ignora en Windows."
"description": "Modo de los archivos extraídos.",
"tooltip": "Solo Unix. Los archivos extraídos se crean con este modo. La umask del proceso sigue aplicando. Se ignora en Windows."
},
"dirMode": {
"label": "Modo de carpeta",
"description": "Permisos para carpetas extraídas nuevas.",
"tooltip": "Solo Unix. Se ignora en Windows."
"description": "Modo de las carpetas extraídas.",
"tooltip": "Solo Unix. Las carpetas extraídas se crean con este modo. La umask del proceso sigue aplicando. Se ignora en Windows."
},
"logFile": {
"label": "Archivo de registro",
Expand Down Expand Up @@ -797,6 +797,11 @@
"description": "Tratar los archivos .iso como archivos comprimidos.",
"tooltip": "Apagado ignora las imágenes ISO. Actívalo solo si de verdad quieres desempaquetarlas."
},
"preserve_exec": {
"label": "Conservar bits de ejecución",
"description": "Conservar los bits de ejecución del archivo.",
"tooltip": "Apagado, los archivos extraídos usan solo el modo de archivo. Encendido, un bit de ejecución del archivo se conserva para propietario, grupo u otros si el modo de archivo ya concede acceso a esa clase. Las extracciones de Starr siguen apagadas."
},
"disableRecursion": {
"label": "Recursión",
"description": "Extraer archivos encontrados dentro de otros archivos.",
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/lib/i18n/locales/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,13 @@
},
"fileMode": {
"label": "Bestandsmodus",
"description": "Rechten voor nieuw uitgepakte bestanden.",
"tooltip": "Alleen Unix. Wordt op Windows genegeerd."
"description": "Modus voor uitgepakte bestanden.",
"tooltip": "Alleen Unix. Uitgepakte bestanden krijgen deze modus. De umask van het proces geldt nog. Wordt op Windows genegeerd."
},
"dirMode": {
"label": "Mapmodus",
"description": "Rechten voor nieuw uitgepakte mappen.",
"tooltip": "Alleen Unix. Wordt op Windows genegeerd."
"description": "Modus voor uitgepakte mappen.",
"tooltip": "Alleen Unix. Uitgepakte mappen krijgen deze modus. De umask van het proces geldt nog. Wordt op Windows genegeerd."
},
"logFile": {
"label": "Logbestand",
Expand Down Expand Up @@ -797,6 +797,11 @@
"description": ".iso-bestanden als archieven behandelen.",
"tooltip": "Uit negeert ISO-images. Zet dit alleen aan als je ze echt wilt uitpakken."
},
"preserve_exec": {
"label": "Uitvoerbits behouden",
"description": "Uitvoerbits uit het archief behouden.",
"tooltip": "Uit krijgen uitgepakte bestanden alleen de bestandsmodus. Aan blijft een uitvoerbit uit het archief staan voor eigenaar, groep of anderen als de bestandsmodus die klasse al toegang geeft. Starr-extracties blijven uit."
},
"disableRecursion": {
"label": "Recursie",
"description": "Archieven in andere archieven uitpakken.",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/slug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export const FOLDER_ENV_FIELDS: Record<string, string> = {
move_back: 'MOVE_BACK',
delete_after: 'DELETE_AFTER',
extract_isos: 'EXTRACT_ISOS',
preserve_exec: 'PRESERVE_EXEC',
disableRecursion: 'DISABLE_RECURSION',
maxNested: 'MAX_NESTED',
extrasMaxDepth: 'EXTRAS_MAX_DEPTH',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ export interface FolderConfig {
move_back: boolean
delete_after: string | null
extract_isos: boolean
preserve_exec: boolean
disableRecursion: boolean
maxNested: number
extrasMaxDepth: number
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/pages/settings/FoldersForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
move_back: false,
delete_after: '',
extract_isos: false,
preserve_exec: false,
disableRecursion: false,
maxNested: 0,
extrasMaxDepth: 0,
Expand Down Expand Up @@ -469,6 +470,18 @@
envVar={envField(envPrefix, slug, 'EXTRACT_ISOS')}
/>
</Col>
<Col md="4">
<Input
id={`folder-${row.id}-preserve-exec`}
helpKey="config.folders.preserve_exec"
type="select"
label={$_('config.folders.preserve_exec.label')}
bind:value={folder.preserve_exec}
original={prev?.preserve_exec}
disabled={!canWrite || row.envOnly}
envVar={envField(envPrefix, slug, 'PRESERVE_EXEC')}
/>
</Col>
<Col md="4">
<Input
id={`folder-${row.id}-skip-empty`}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
golift.io/rotatorr v0.1.0
golift.io/starr v1.4.1
golift.io/version v0.0.2
golift.io/xtractr v0.7.1-0.20261001061402-3616e46c087b
golift.io/xtractr v0.7.1-0.20261004082945-75670be279e2
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ golift.io/udf v0.1.0 h1:arzc1C6Ko9MR2lvpZuZ1WXceGkUxsTOvxg3NIiX6iiA=
golift.io/udf v0.1.0/go.mod h1:rw8k1sEHRzKZJCgBFfSPolPb0CpyS0apwoeOwwQQM6o=
golift.io/version v0.0.2 h1:i0gXRuSDHKs4O0sVDUg4+vNIuOxYoXhaxspftu2FRTE=
golift.io/version v0.0.2/go.mod h1:76aHNz8/Pm7CbuxIsDi97jABL5Zui3f2uZxDm4vB6hU=
golift.io/xtractr v0.7.1-0.20261001061402-3616e46c087b h1:uI2CQFtkBE4Boh3iIm703IafdtlGtUAITAkWv1lOFmU=
golift.io/xtractr v0.7.1-0.20261001061402-3616e46c087b/go.mod h1:my82SdhkWFM+6jGnWoxEhKxtcGUmeYsAzmiw2Ku90sE=
golift.io/xtractr v0.7.1-0.20261004082945-75670be279e2 h1:l+DCHor4U9LUKK51Q/nHsaeBuG4CCIuhF3kMxxHPhu4=
golift.io/xtractr v0.7.1-0.20261004082945-75670be279e2/go.mod h1:mGuJ4E+lIyi0nslw3XabGwIrqRYznMk9KASXcTbTCQI=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
25 changes: 21 additions & 4 deletions pkg/configdef/definitions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,13 @@ sections:
envvar: FILE_MODE
default: '0644'
recommend: *FILE_MODES
short: Extracted files are written with this mode.
short: Mode for extracted files.
desc: |
Use these configurations to control the file modes used for newly extracted
files and folders. Recommend 0644/0755 or 0666/0777.
Extracted files are created with this mode. The process umask still
applies, so 0666 becomes 0644 under umask 0022. Use 0644, or 0660 or
0666 when another user must write the extracted files. Archive
execute bits are kept only for a watched folder with preserve_exec
enabled.
- name: dir_mode
envvar: DIR_MODE
default: '0755'
Expand All @@ -423,7 +426,10 @@ sections:
- value: '0770'
- value: '0775'
- value: '0777'
short: Extracted folders are written with this mode
short: Mode for extracted folders.
desc: |
Extracted directories are created with this mode. The process umask
still applies.
- name: passwords
envvar: PASSWORD_
default: []
Expand Down Expand Up @@ -974,6 +980,17 @@ sections:
recommend: *BOOLEAN
short: Setting this to true enables .iso file extraction.
desc: Set this to true if you want this app to extract ISO files with .iso extension.
- name: preserve_exec
envvar: PRESERVE_EXEC
default: false
recommend: *BOOLEAN
short: Keep execute bits stored in the archive.
desc: |
When true, an execute bit stored in the archive is kept for owner,
group, or other only when file_mode already grants that class access.
A 6 beside an archived 7 becomes 7, a 4 becomes 5, and a 0 stays 0.
Off, extracted files use file_mode only. Starr extracts stay off.
The process umask still applies.
- name: wait_extensions
envvar: WAIT_EXTENSION_
default: []
Expand Down
1 change: 1 addition & 0 deletions pkg/folders/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type FolderConfig struct {
DeleteAfter *cnfg.Duration `json:"delete_after" toml:"delete_after" xml:"delete_after" yaml:"delete_after"`
ExtractPath string `json:"extract_path" toml:"extract_path" xml:"extract_path" yaml:"extract_path"`
ExtractISOs bool `json:"extract_isos" toml:"extract_isos" xml:"extract_isos" yaml:"extract_isos"`
PreserveExec bool `json:"preserve_exec" toml:"preserve_exec" xml:"preserve_exec" yaml:"preserve_exec"`
DisableRecursion bool `json:"disableRecursion" toml:"disable_recursion" xml:"disable_recursion" yaml:"disableRecursion"`
MaxNested int `json:"maxNested" toml:"max_nested" xml:"max_nested" yaml:"maxNested"`
ExtrasMaxDepth int `json:"extrasMaxDepth" toml:"extras_max_depth" xml:"extras_max_depth" yaml:"extrasMaxDepth"`
Expand Down
8 changes: 4 additions & 4 deletions pkg/unpackerr/configdump.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,10 @@
}

printf(" => Folder Config: 1 path: %s%s; delete_after:%v delete_orig:%v delete_files:%v "+
"log_file:%v move_back:%v isos:%v skip_empty:%v wait_ext:%v max_bytes:%s files:%d ratio:%g "+
"log_file:%v move_back:%v isos:%v preserve_exec:%v skip_empty:%v wait_ext:%v max_bytes:%s files:%d ratio:%g "+
"nested:%d extras_depth:%d symlinks:%v poll:%v event_buffer:%d",
folder.Path, epath, folder.DeleteAfter, folder.DeleteOrig, folder.DeleteFiles,
!folder.DisableLog, folder.MoveBack, folder.ExtractISOs, folder.SkipEmpty, folder.WaitExtensions,
!folder.DisableLog, folder.MoveBack, folder.ExtractISOs, folder.PreserveExec, folder.SkipEmpty, folder.WaitExtensions,

Check failure on line 186 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (linux)

The line is 121 characters long, which exceeds the maximum of 120 characters. (lll)

Check failure on line 186 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (darwin)

The line is 121 characters long, which exceeds the maximum of 120 characters. (lll)

Check failure on line 186 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (freebsd)

The line is 121 characters long, which exceeds the maximum of 120 characters. (lll)

Check failure on line 186 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (freebsd)

The line is 121 characters long, which exceeds the maximum of 120 characters. (lll)

Check failure on line 186 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (linux)

The line is 121 characters long, which exceeds the maximum of 120 characters. (lll)
logMaxBytes(folder.MaxBytes, "uncapped"), folder.MaxFiles, folder.MaxRatio,
folder.MaxNested, folder.ExtrasMaxDepth, folder.AllowSymlinks, folder.Interval, u.Folder.Buffer)
} else {
Expand All @@ -195,10 +195,10 @@
}

printf(" => Path: %s%s; delete_after:%v delete_orig:%v delete_files:%v log_file:%v "+
"move_back:%v isos:%v skip_empty:%v wait_ext:%v max_bytes:%s files:%d ratio:%g "+
"move_back:%v isos:%v preserve_exec:%v skip_empty:%v wait_ext:%v max_bytes:%s files:%d ratio:%g "+
"nested:%d extras_depth:%d symlinks:%v poll:%v",
folder.Path, epath, folder.DeleteAfter, folder.DeleteOrig, folder.DeleteFiles,
!folder.DisableLog, folder.MoveBack, folder.ExtractISOs, folder.SkipEmpty, folder.WaitExtensions,
!folder.DisableLog, folder.MoveBack, folder.ExtractISOs, folder.PreserveExec, folder.SkipEmpty, folder.WaitExtensions,

Check failure on line 201 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (linux)

The line is 122 characters long, which exceeds the maximum of 120 characters. (lll)

Check failure on line 201 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (darwin)

The line is 122 characters long, which exceeds the maximum of 120 characters. (lll)

Check failure on line 201 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (freebsd)

The line is 122 characters long, which exceeds the maximum of 120 characters. (lll)

Check failure on line 201 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (freebsd)

The line is 122 characters long, which exceeds the maximum of 120 characters. (lll)

Check failure on line 201 in pkg/unpackerr/configdump.go

View workflow job for this annotation

GitHub Actions / golangci-lint (linux)

The line is 122 characters long, which exceeds the maximum of 120 characters. (lll)
logMaxBytes(folder.MaxBytes, "uncapped"), folder.MaxFiles, folder.MaxRatio,
folder.MaxNested, folder.ExtrasMaxDepth, folder.AllowSymlinks, folder.Interval)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/unpackerr/folder.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func (u *Unpackerr) extractTrackedItem(name string, folder *Folder, now time.Tim
Path: name,
ExcludeSuffix: exclude,
AllowSymlinks: folder.Config.AllowSymlinks,
PreserveExec: folder.Config.PreserveExec,
MaxBytes: folder.Config.ResolvedMaxBytes,
MaxFiles: folder.Config.MaxFiles,
MaxRatio: folder.Config.MaxRatio,
Expand Down
1 change: 1 addition & 0 deletions pkg/unpackerr/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@
"move_back": {"type": "boolean"},
"delete_after": {"type": "string", "nullable": true, "description": "Go duration; null omits"},
"extract_isos": {"type": "boolean"},
"preserve_exec": {"type": "boolean", "description": "Keep archive execute bits. Off, files use file_mode only."},
"disableRecursion": {"type": "boolean"},
"maxNested": {"type": "integer"},
"extrasMaxDepth": {"type": "integer"},
Expand Down
Loading