diff --git a/README.md b/README.md index f80bb57..a55d2c7 100644 --- a/README.md +++ b/README.md @@ -54,20 +54,23 @@ Two images: the application, and a Redis sidecar. ## Volume and Data Layout -One volume, mounted four times at four different subpaths, plus an optional fifth mount from FileBrowser Quantum. +One volume, mounted four times at four different subpaths, plus an optional fifth mount from FileBrowser Quantum or NextExplorer. -| Volume | Subpath | Mount Point | Purpose | -| ---------------------- | --------- | ------------------ | --------------------------------------- | -| `main` | `data` | `…/data` | The database and the search index | -| `main` | `media` | `…/media` | The stored documents | -| `main` | `consume` | `…/consume` | The private intake folder | -| `main` | `export` | `…/export` | Where exports are written | -| `filebrowser` → `data` | — | `/mnt/filebrowser` | FileBrowser Quantum's files, read-write | +| Volume | Subpath | Mount Point | Purpose | +| ----------------------- | --------- | ------------------- | --------------------------------------- | +| `main` | `data` | `…/data` | The database and the search index | +| `main` | `media` | `…/media` | The stored documents | +| `main` | `consume` | `…/consume` | The private intake folder | +| `main` | `export` | `…/export` | Where exports are written | +| `filebrowser` → `data` | — | `/mnt/filebrowser` | FileBrowser Quantum's files, read-write | +| `nextexplorer` → `data` | — | `/mnt/nextexplorer` | NextExplorer's locations, read-write | **All four `main` mounts are required, not just the ones being used.** Django's startup checks verify every one of those paths exists and is writable, and refuse to run otherwise — which is why the same mount set is used by the password action's temporary container as by the daemon itself. **The FileBrowser Quantum mount exists only while the consume folder points there** (see Set Consume Folder). `PAPERLESS_CONSUMPTION_DIR` is then the chosen subfolder under `/mnt/filebrowser`, and the private `consume` subpath stays mounted for Django's checks but is not watched. The mount is read-write because consumption **deletes** the source file after a successful import — inside the same database transaction, so a read-only mount would roll back every import. Paperless runs as uid 1000, the uid FileBrowser Quantum serves its volume as, so no id mapping is needed. +**The NextExplorer mount works the same way while the consume folder points there**: `PAPERLESS_CONSUMPTION_DIR` is the chosen location, `/mnt/nextexplorer/`. NextExplorer also serves its volume as uid 1000. + | Path | Written by | Holds | | ------------ | ----------- | ----------------------------------------------------------------- | | `db.sqlite3` | Paperless | Documents' metadata, tags, users | @@ -85,7 +88,7 @@ One model, holding two generated values and one user choice. - **The Django secret key**, generated once at install. It signs sessions and is not rotatable — changing it invalidates every session and anything else derived from it. - **The admin password**, recorded so the package knows whether one has been set. -- **The consume folder** — `consumeSource` (`local` or `filebrowser`) and `filebrowserSubfolder`. Read reactively by the daemon and by the dependency declaration, so changing them restarts the service with the right mounts and re-evaluates the dependency. +- **The consume folder** — `consumeSource` (`local`, `filebrowser` or `nextexplorer`), `filebrowserSubfolder` and `nextexplorerLocation`. Read reactively by the daemon and by the dependency declaration, so changing them restarts the service with the right mounts and re-evaluates the dependency. Everything else Paperless needs is **passed as environment**, and two of those values are computed rather than fixed: the allowed CORS origins and the CSRF trusted origins are built from the interface's **current addresses**. StartOS terminates TLS in front of the application, so without those the browser's origin would not match what Django expects and **logins would be rejected as CSRF failures** — which presents as a wrong password rather than a proxy problem. @@ -93,13 +96,14 @@ Paperless's own settings — document types, tags, mail rules, workflows — liv ## Dependencies -One, optional, and declared only while it is in use. +Two, both optional, and each declared only while the consume folder points at it. -| Dependency | Id | Required | Kind | Purpose | -| ------------------- | ------------- | -------- | -------- | ------------------------------------ | -| FileBrowser Quantum | `filebrowser` | No | `exists` | Hosts the consume folder, read-write | +| Dependency | Id | Required | Kind | Purpose | +| ------------------- | -------------- | -------- | -------- | ------------------------------------ | +| FileBrowser Quantum | `filebrowser` | No | `exists` | Hosts the consume folder, read-write | +| NextExplorer | `nextexplorer` | No | `exists` | Hosts the consume folder, read-write | -While the consume folder points at FileBrowser Quantum, `setupDependencies` declares it as `exists` — it only has to be installed, not running, for the volume to be there — and StartOS shows the usual dependency warning if it is missing. With the private folder selected, no dependency is declared at all. +While the consume folder points at one of them, `setupDependencies` declares it as `exists` — it only has to be installed, not running, for the volume to be there — and StartOS shows the usual dependency warning if it is missing. With the private folder selected, no dependency is declared at all. Redis runs as a private sidecar of this service rather than as a StartOS dependency. @@ -146,12 +150,13 @@ Generates a password for the `admin` account and shows it once. ### Set Consume Folder -Chooses where Paperless watches for new documents: the private `consume` subpath (the default — reachable only by Paperless itself, so in practice "web upload only"), or a subfolder of FileBrowser Quantum's data volume (default `paperless`). +Chooses where Paperless watches for new documents: the private `consume` subpath (the default — reachable only by Paperless itself, so in practice "web upload only"), a subfolder of FileBrowser Quantum's data volume (default `paperless`), or a NextExplorer location (default `Paperless`). -- **What it changes:** `consumeSource` and `filebrowserSubfolder` in the store. +- **What it changes:** `consumeSource`, and `filebrowserSubfolder` or `nextexplorerLocation`, in the store. - **Cost:** a restart. The daemon reads both values reactively, mounts FileBrowser Quantum's volume when selected, and points `PAPERLESS_CONSUMPTION_DIR` at the subfolder; Paperless's own entrypoint creates the subfolder if it is missing. - **Runnable at any status.** Selecting FileBrowser Quantum before it is installed still starts the service: StartOS mounts an empty placeholder where the volume would be, so Paperless watches a folder nothing can reach, and the dependency warning is the only sign. Install FileBrowser Quantum, then restart Paperless. -- **Repeat safety:** switching back to the private folder keeps the last subfolder, so it is pre-filled if FileBrowser Quantum is selected again. Files left in either folder are not moved. +- **NextExplorer:** refused unless NextExplorer is installed. The action declares the dependency, then runs NextExplorer's `add-location` itself (`access: 'dependent'`), which creates the location or accepts one that already exists, and writes the store only once that succeeds. A name NextExplorer rejects leaves the previous choice in place. +- **Repeat safety:** switching away keeps the last subfolder and location, so each is pre-filled if selected again. Files left in any folder are not moved. ## Tasks @@ -178,7 +183,7 @@ The application waits for the broker, so a failing broker shows as the applicati **Neither check says anything about document processing.** A stuck OCR job, an unreadable scan, or a consume folder nobody is writing to all show two green checks; those are visible in the interface's own task list. -**Nor do they cover the FileBrowser Quantum mount.** A file dropped there and never imported is a consumer problem — Paperless's log in the interface is where it surfaces — not a health-check failure. +**Nor do they cover the FileBrowser Quantum or NextExplorer mount.** A file dropped there and never imported is a consumer problem — Paperless's log in the interface is where it surfaces — not a health-check failure. ## Backups and Restore @@ -190,7 +195,7 @@ The `main` volume is copied wholesale — `sdk.Backups.ofVolumes('main')`. That Note that the intake and export folders are backed up along with everything else, so a backup taken mid-import is larger than the library alone. -**A consume folder in FileBrowser Quantum is FileBrowser Quantum's data**, backed up by that package, not this one. The store records the choice, so a restore on a server without FileBrowser Quantum starts but imports nothing until it is installed and Paperless restarted. +**A consume folder in FileBrowser Quantum or NextExplorer is that package's data**, backed up by it, not this one. The store records the choice, so a restore on a server without that package starts but imports nothing until it is installed and Paperless restarted. ## Limitations and Differences @@ -201,7 +206,7 @@ Note that the intake and export folders are backed up along with everything else 5. **The task broker is private.** It cannot be shared, substituted, or reached from outside the service. 6. **The timezone is fixed to UTC** and OCR is configured for English; other languages are set in Paperless's own settings. 7. **Backups include the intake and export folders**, not just the library. -8. **The consume folder can be shared only through FileBrowser Quantum**, and only one folder is watched, non-recursively. Paperless's own document store (`media`) is not exposed to other services. +8. **The consume folder can be shared only through FileBrowser Quantum or NextExplorer**, and only one folder is watched, non-recursively. Paperless's own document store (`media`) is not exposed to other services. --- @@ -220,12 +225,13 @@ volumes: main: # mounted four times by subpath data: /usr/src/paperless/data # db.sqlite3, search index, store.json at the volume root media: /usr/src/paperless/media - consume: /usr/src/paperless/consume # the private intake folder; idle while FileBrowser Quantum is the consume source + consume: /usr/src/paperless/consume # the private intake folder; idle while another consume source is selected export: /usr/src/paperless/export dependency_mounts: filebrowser/data: /mnt/filebrowser # read-write, only while consumeSource is filebrowser + nextexplorer/data: /mnt/nextexplorer # read-write, only while consumeSource is nextexplorer file_models: - - store.json # adminPassword, the generated Django secretKey, consumeSource, filebrowserSubfolder + - store.json # adminPassword, the generated Django secretKey, consumeSource, filebrowserSubfolder, nextexplorerLocation startos_managed_env_vars: - PAPERLESS_REDIS - PAPERLESS_PORT @@ -235,11 +241,12 @@ startos_managed_env_vars: - PAPERLESS_CSRF_TRUSTED_ORIGINS # same — omit and logins 403 on CSRF - PAPERLESS_TIME_ZONE - PAPERLESS_OCR_LANGUAGE - - PAPERLESS_CONSUMPTION_DIR # /usr/src/paperless/consume, or /mnt/filebrowser/ + - PAPERLESS_CONSUMPTION_DIR # /usr/src/paperless/consume, /mnt/filebrowser/ or /mnt/nextexplorer/ - USERMAP_UID - USERMAP_GID dependencies: - { id: filebrowser, optional: true, kind: exists } # declared only while it is the consume source + - { id: nextexplorer, optional: true, kind: exists } # same; set-consume-folder runs its add-location interfaces: ui: { type: ui, port: 8000 } # Paperless's own login; no gate added by StartOS actions: diff --git a/instructions.md b/instructions.md index 788f9c7..3a26ffb 100644 --- a/instructions.md +++ b/instructions.md @@ -14,6 +14,7 @@ Forgot your password, or want a new one? Run **Set Admin Password** again at any - **Web upload**: use the drag-and-drop area in the Paperless-ngx UI. - **Consume folder in FileBrowser Quantum**: run the **Set Consume Folder** action, choose **FileBrowser Quantum**, and pick a subfolder (the default is `paperless`). Paperless-ngx watches that folder, imports anything you drop into it, and then deletes the file. FileBrowser Quantum must be installed (if you install it afterwards, restart Paperless-ngx); the subfolder is created for you. If you also have Nextcloud with FileBrowser Quantum mounted as external storage, dropping a file into that folder from Nextcloud works the same way. +- **Consume folder in NextExplorer**: run the **Set Consume Folder** action, choose **NextExplorer**, and pick a location (the default is `Paperless`). NextExplorer must be installed first; the location is added to NextExplorer for you. NextExplorer's admin sees it straight away; to let another NextExplorer account drop files into it, add the location in that account's **Volumes** tab. Nextcloud shows NextExplorer's locations as external storage, so dropping a file into it from Nextcloud works too. - **Email**: configure a mail account under **Settings → Mail** in the Paperless-ngx UI and it will fetch and consume attachments automatically — handy for scanners that scan-to-email. - **Mobile apps and API**: any Paperless-ngx-compatible app can upload via the API using your Web UI address and an API token from your user profile. diff --git a/package-lock.json b/package-lock.json index ad2dc51..2ff9209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,8 @@ "name": "paperless-startos", "dependencies": { "@start9labs/start-sdk": "2.0.9", - "filebrowser-startos": "github:Start9Labs/filebrowser-startos#next" + "filebrowser-startos": "github:Start9Labs/filebrowser-startos#next", + "nextexplorer-startos": "github:Start9Labs/nextexplorer-startos#next" }, "devDependencies": { "@types/node": "^22.19.0", @@ -1659,6 +1660,13 @@ "node": ">=16" } }, + "node_modules/nextexplorer-startos": { + "resolved": "git+ssh://git@github.com/Start9Labs/nextexplorer-startos.git#c0539454628fffa7873841cbd286b20174acd33b", + "dependencies": { + "@start9labs/start-sdk": "2.0.9", + "filebrowser-startos": "github:Start9Labs/filebrowser-startos#next" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", diff --git a/package.json b/package.json index bf6cd66..e95bf82 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ }, "dependencies": { "@start9labs/start-sdk": "2.0.9", - "filebrowser-startos": "github:Start9Labs/filebrowser-startos#next" + "filebrowser-startos": "github:Start9Labs/filebrowser-startos#next", + "nextexplorer-startos": "github:Start9Labs/nextexplorer-startos#next" }, "devDependencies": { "@types/node": "^22.19.0", diff --git a/startos/actions/setConsumeFolder.ts b/startos/actions/setConsumeFolder.ts index 0530d4a..b81c63e 100644 --- a/startos/actions/setConsumeFolder.ts +++ b/startos/actions/setConsumeFolder.ts @@ -1,7 +1,8 @@ +import { setDependencies, nextexplorerVersionRange } from '../dependencies' import { storeJson } from '../fileModels/store.json' import { i18n } from '../i18n' import { sdk } from '../sdk' -import { defaultConsumeSubfolder } from '../utils' +import { defaultConsumeLocation, defaultConsumeSubfolder } from '../utils' const { InputSpec, Value, Variants } = sdk @@ -31,6 +32,20 @@ export const inputSpec = InputSpec.of({ }), }), }, + nextexplorer: { + name: i18n('NextExplorer'), + spec: InputSpec.of({ + location: Value.text({ + name: i18n('NextExplorer Location'), + description: i18n( + 'Location in NextExplorer that Paperless-ngx watches. Added to NextExplorer if it does not exist; NextExplorer must be installed.', + ), + default: defaultConsumeLocation, + required: true, + placeholder: defaultConsumeLocation, + }), + }), + }, }), }), }) @@ -41,7 +56,7 @@ export const setConsumeFolder = sdk.Action.withInput( async () => ({ name: i18n('Set Consume Folder'), description: i18n( - 'Choose where Paperless-ngx watches for new documents: a private folder, or a folder in FileBrowser Quantum you can drop files into.', + 'Choose where Paperless-ngx watches for new documents: a private folder, or a folder in FileBrowser Quantum or NextExplorer you can drop files into.', ), warning: null, allowedStatuses: 'any', @@ -51,31 +66,76 @@ export const setConsumeFolder = sdk.Action.withInput( inputSpec, - async ({ effects }) => { - const subfolder = - (await storeJson.read((s) => s.filebrowserSubfolder).const(effects)) ?? - defaultConsumeSubfolder + async () => { + const store = await storeJson.read().once() + const subfolder = store?.filebrowserSubfolder ?? defaultConsumeSubfolder + const location = store?.nextexplorerLocation ?? defaultConsumeLocation + const other = { + filebrowser: { subfolder }, + nextexplorer: { location }, + } return { source: - (await storeJson.read((s) => s.consumeSource).const(effects)) === - 'filebrowser' - ? { selection: 'filebrowser' as const, value: { subfolder } } - : { - selection: 'local' as const, - value: {}, - other: { filebrowser: { subfolder } }, - }, + store?.consumeSource === 'filebrowser' + ? { selection: 'filebrowser' as const, value: { subfolder }, other } + : store?.consumeSource === 'nextexplorer' + ? { selection: 'nextexplorer' as const, value: { location }, other } + : { selection: 'local' as const, value: {}, other }, } }, - async ({ effects, input }) => - storeJson.merge( - effects, - input.source.selection === 'filebrowser' - ? { - consumeSource: 'filebrowser', - filebrowserSubfolder: input.source.value.subfolder, - } - : { consumeSource: 'local' }, - ), + async ({ effects, input }) => { + if (input.source.selection !== 'nextexplorer') { + await storeJson.merge( + effects, + input.source.selection === 'filebrowser' + ? { + consumeSource: 'filebrowser', + filebrowserSubfolder: input.source.value.subfolder, + } + : { consumeSource: 'local' }, + ) + return null + } + + if (!(await sdk.getInstalledPackages(effects)).includes('nextexplorer')) { + throw new Error(i18n('Install NextExplorer first')) + } + const location = input.source.value.location.trim() + // add-location admits only a declared dependent, and the store must not name the location until it exists. + await effects.setDependencies({ + dependencies: [ + { + id: 'nextexplorer', + kind: 'exists', + versionRange: nextexplorerVersionRange, + }, + ], + }) + try { + await sdk.action.run({ + effects, + packageId: 'nextexplorer', + actionId: 'add-location', + input: () => ({ name: location }), + }) + } catch (e) { + await setDependencies(effects) + throw e + } + await storeJson.merge(effects, { + consumeSource: 'nextexplorer', + nextexplorerLocation: location, + }) + + return { + version: '1', + title: i18n('Consume Folder Set'), + message: i18n( + 'Paperless-ngx now watches the ${location} location in NextExplorer. NextExplorer accounts other than the admin see it only once you add it in their Volumes tab.', + { location }, + ), + result: null, + } + }, ) diff --git a/startos/dependencies.ts b/startos/dependencies.ts index 8584b27..0b38b4c 100644 --- a/startos/dependencies.ts +++ b/startos/dependencies.ts @@ -1,9 +1,19 @@ import { storeJson } from './fileModels/store.json' import { sdk } from './sdk' -export const setDependencies = sdk.setupDependencies(async ({ effects }) => - (await storeJson.read((s) => s.consumeSource).const(effects)) === - 'filebrowser' +// From the release whose add-location a dependent may run. +export const nextexplorerVersionRange = '>=3.1.0:1 && <4.0.0:0' + +export const setDependencies = sdk.setupDependencies(async ({ effects }) => { + const source = await storeJson.read((s) => s.consumeSource).const(effects) + return source === 'filebrowser' ? { filebrowser: { kind: 'exists', versionRange: '>=2.63.18:3' } } - : {}, -) + : source === 'nextexplorer' + ? { + nextexplorer: { + kind: 'exists', + versionRange: nextexplorerVersionRange, + }, + } + : {} +}) diff --git a/startos/fileModels/store.json.ts b/startos/fileModels/store.json.ts index 001f44f..3339e95 100644 --- a/startos/fileModels/store.json.ts +++ b/startos/fileModels/store.json.ts @@ -1,14 +1,17 @@ import { FileHelper, z } from '@start9labs/start-sdk' import { sdk } from '../sdk' -import { defaultConsumeSubfolder } from '../utils' +import { defaultConsumeLocation, defaultConsumeSubfolder } from '../utils' const shape = z.object({ adminPassword: z.string().catch(''), // Generated once at install and not rotatable: changing it invalidates every // session and anything else Django derived from it. secretKey: z.string().catch(''), - consumeSource: z.enum(['local', 'filebrowser']).catch('local'), + consumeSource: z + .enum(['local', 'filebrowser', 'nextexplorer']) + .catch('local'), filebrowserSubfolder: z.string().catch(defaultConsumeSubfolder), + nextexplorerLocation: z.string().catch(defaultConsumeLocation), }) export const storeJson = FileHelper.json( diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts index 16da8b7..e73a4ee 100644 --- a/startos/i18n/dictionaries/default.ts +++ b/startos/i18n/dictionaries/default.ts @@ -36,7 +36,13 @@ const dict = { 'FileBrowser Quantum Subfolder': 21, 'Folder inside FileBrowser Quantum that Paperless-ngx watches. Created automatically; FileBrowser Quantum must be installed.': 22, 'Set Consume Folder': 23, - 'Choose where Paperless-ngx watches for new documents: a private folder, or a folder in FileBrowser Quantum you can drop files into.': 24, + 'Choose where Paperless-ngx watches for new documents: a private folder, or a folder in FileBrowser Quantum or NextExplorer you can drop files into.': 24, + NextExplorer: 25, + 'NextExplorer Location': 26, + 'Location in NextExplorer that Paperless-ngx watches. Added to NextExplorer if it does not exist; NextExplorer must be installed.': 27, + 'Install NextExplorer first': 28, + 'Consume Folder Set': 29, + 'Paperless-ngx now watches the ${location} location in NextExplorer. NextExplorer accounts other than the admin see it only once you add it in their Volumes tab.': 30, } as const /** diff --git a/startos/i18n/dictionaries/translations.ts b/startos/i18n/dictionaries/translations.ts index 7ffc761..8b90be7 100644 --- a/startos/i18n/dictionaries/translations.ts +++ b/startos/i18n/dictionaries/translations.ts @@ -26,7 +26,13 @@ export default { 21: 'Subcarpeta de FileBrowser Quantum', 22: 'Carpeta dentro de FileBrowser Quantum que Paperless-ngx vigila. Se crea automáticamente; FileBrowser Quantum debe estar instalado.', 23: 'Establecer carpeta de consumo', - 24: 'Elige dónde Paperless-ngx vigila la llegada de nuevos documentos: una carpeta privada o una carpeta de FileBrowser Quantum en la que puedes dejar archivos.', + 24: 'Elige dónde Paperless-ngx vigila la llegada de nuevos documentos: una carpeta privada o una carpeta de FileBrowser Quantum o NextExplorer en la que puedes dejar archivos.', + 25: 'NextExplorer', + 26: 'Ubicación de NextExplorer', + 27: 'Ubicación de NextExplorer que Paperless-ngx vigila. Se añade a NextExplorer si no existe; NextExplorer debe estar instalado.', + 28: 'Instala NextExplorer primero', + 29: 'Carpeta de consumo establecida', + 30: 'Paperless-ngx ahora vigila la ubicación ${location} de NextExplorer. Las cuentas de NextExplorer distintas de la de administrador solo la ven cuando la añades en su pestaña Volumes.', }, de_DE: { 0: 'Aufgaben-Broker', @@ -53,7 +59,13 @@ export default { 21: 'FileBrowser-Quantum-Unterordner', 22: 'Ordner in FileBrowser Quantum, den Paperless-ngx überwacht. Wird automatisch erstellt; FileBrowser Quantum muss installiert sein.', 23: 'Konsum-Ordner festlegen', - 24: 'Wähle, wo Paperless-ngx auf neue Dokumente wartet: in einem privaten Ordner oder in einem Ordner in FileBrowser Quantum, in dem du Dateien ablegen kannst.', + 24: 'Wähle, wo Paperless-ngx auf neue Dokumente wartet: in einem privaten Ordner oder in einem Ordner in FileBrowser Quantum oder NextExplorer, in dem du Dateien ablegen kannst.', + 25: 'NextExplorer', + 26: 'NextExplorer-Standort', + 27: 'Standort in NextExplorer, den Paperless-ngx überwacht. Wird in NextExplorer angelegt, falls er nicht existiert; NextExplorer muss installiert sein.', + 28: 'Installiere zuerst NextExplorer', + 29: 'Konsum-Ordner festgelegt', + 30: 'Paperless-ngx überwacht jetzt den Standort ${location} in NextExplorer. Andere NextExplorer-Konten als das des Admins sehen ihn erst, wenn du ihn in ihrem Volumes-Tab hinzufügst.', }, pl_PL: { 0: 'Broker zadań', @@ -80,7 +92,13 @@ export default { 21: 'Podfolder FileBrowser Quantum', 22: 'Folder w FileBrowser Quantum obserwowany przez Paperless-ngx. Tworzony automatycznie; FileBrowser Quantum musi być zainstalowany.', 23: 'Ustaw folder konsumpcji', - 24: 'Wybierz, gdzie Paperless-ngx ma wypatrywać nowych dokumentów: w prywatnym folderze lub w folderze FileBrowser Quantum, do którego możesz wrzucać pliki.', + 24: 'Wybierz, gdzie Paperless-ngx ma wypatrywać nowych dokumentów: w prywatnym folderze lub w folderze FileBrowser Quantum albo NextExplorer, do którego możesz wrzucać pliki.', + 25: 'NextExplorer', + 26: 'Lokalizacja NextExplorer', + 27: 'Lokalizacja w NextExplorer obserwowana przez Paperless-ngx. Dodawana do NextExplorer, jeśli nie istnieje; NextExplorer musi być zainstalowany.', + 28: 'Najpierw zainstaluj NextExplorer', + 29: 'Folder konsumpcji ustawiony', + 30: 'Paperless-ngx obserwuje teraz lokalizację ${location} w NextExplorer. Konta NextExplorer inne niż administratora widzą ją dopiero po dodaniu jej w ich zakładce Volumes.', }, fr_FR: { 0: 'File de tâches', @@ -107,6 +125,12 @@ export default { 21: 'Sous-dossier FileBrowser Quantum', 22: 'Dossier dans FileBrowser Quantum surveillé par Paperless-ngx. Créé automatiquement ; FileBrowser Quantum doit être installé.', 23: 'Définir le dossier de consommation', - 24: "Choisissez où Paperless-ngx surveille l'arrivée de nouveaux documents : un dossier privé, ou un dossier de FileBrowser Quantum dans lequel vous pouvez déposer des fichiers.", + 24: "Choisissez où Paperless-ngx surveille l'arrivée de nouveaux documents : un dossier privé, ou un dossier de FileBrowser Quantum ou NextExplorer dans lequel vous pouvez déposer des fichiers.", + 25: 'NextExplorer', + 26: 'Emplacement NextExplorer', + 27: "Emplacement de NextExplorer surveillé par Paperless-ngx. Ajouté à NextExplorer s'il n'existe pas ; NextExplorer doit être installé.", + 28: "Installez d'abord NextExplorer", + 29: 'Dossier de consommation défini', + 30: "Paperless-ngx surveille désormais l'emplacement ${location} de NextExplorer. Les comptes NextExplorer autres que celui de l'administrateur ne le voient qu'une fois ajouté dans leur onglet Volumes.", }, } satisfies Record diff --git a/startos/init/bootstrapDatabase.ts b/startos/init/bootstrapDatabase.ts index 6572422..d54b310 100644 --- a/startos/init/bootstrapDatabase.ts +++ b/startos/init/bootstrapDatabase.ts @@ -27,7 +27,7 @@ export const bootstrapDatabase = sdk.setupOnInit( trustedOrigins: ( await sdk.host.getOwn(effects, uiHostId, uiUrls).once() ).join(','), - filebrowserSubfolder: null, + consumeFolder: null, }).runUntilSuccess(300_000) phase.complete() }, diff --git a/startos/main.ts b/startos/main.ts index d138846..f13ff52 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -1,5 +1,6 @@ import { T, utils } from '@start9labs/start-sdk' import { manifest as filebrowserManifest } from 'filebrowser-startos/startos/manifest' +import { manifest as nextexplorerManifest } from 'nextexplorer-startos/startos/manifest' import { storeJson } from './fileModels/store.json' import { i18n } from './i18n' import { uiHostId, uiInterfaceId } from './interfaces' @@ -7,6 +8,7 @@ import { sdk } from './sdk' import { consumeMountpoint, filebrowserMountpoint, + nextexplorerMountpoint, paperlessMounts, redisPort, uiPort, @@ -31,11 +33,14 @@ export function paperlessDaemons( { secretKey, trustedOrigins, - filebrowserSubfolder, + consumeFolder, }: { secretKey: string trustedOrigins: string - filebrowserSubfolder: string | null + consumeFolder: + | { source: 'filebrowser'; subfolder: string } + | { source: 'nextexplorer'; location: string } + | null }, ) { return sdk.Daemons.of(effects) @@ -73,7 +78,7 @@ export function paperlessDaemons( subcontainer: sdk.SubContainer.of( effects, { imageId: 'paperless' }, - filebrowserSubfolder + consumeFolder?.source === 'filebrowser' ? paperlessMounts.mountDependency({ dependencyId: 'filebrowser', volumeId: 'data', @@ -81,7 +86,15 @@ export function paperlessDaemons( mountpoint: filebrowserMountpoint, readonly: false, }) - : paperlessMounts, + : consumeFolder?.source === 'nextexplorer' + ? paperlessMounts.mountDependency({ + dependencyId: 'nextexplorer', + volumeId: 'data', + subpath: null, + mountpoint: nextexplorerMountpoint, + readonly: false, + }) + : paperlessMounts, 'paperless-app', ), exec: { @@ -96,9 +109,12 @@ export function paperlessDaemons( PAPERLESS_CSRF_TRUSTED_ORIGINS: trustedOrigins, PAPERLESS_TIME_ZONE: 'UTC', PAPERLESS_OCR_LANGUAGE: 'eng', - PAPERLESS_CONSUMPTION_DIR: filebrowserSubfolder - ? `${filebrowserMountpoint}/${filebrowserSubfolder}` - : consumeMountpoint, + PAPERLESS_CONSUMPTION_DIR: + consumeFolder?.source === 'filebrowser' + ? `${filebrowserMountpoint}/${consumeFolder.subfolder}` + : consumeFolder?.source === 'nextexplorer' + ? `${nextexplorerMountpoint}/${consumeFolder.location}` + : consumeMountpoint, USERMAP_UID: '1000', USERMAP_GID: '1000', }, @@ -126,9 +142,19 @@ export const main = sdk.setupMain(async ({ effects }) => { trustedOrigins: ( await sdk.host.getOwn(effects, uiHostId, uiUrls).const() ).join(','), - filebrowserSubfolder: await storeJson + consumeFolder: await storeJson .read((s) => - s.consumeSource === 'filebrowser' ? s.filebrowserSubfolder : null, + s.consumeSource === 'filebrowser' + ? { + source: 'filebrowser' as const, + subfolder: s.filebrowserSubfolder, + } + : s.consumeSource === 'nextexplorer' + ? { + source: 'nextexplorer' as const, + location: s.nextexplorerLocation, + } + : null, ) .const(effects), }) diff --git a/startos/manifest/i18n.ts b/startos/manifest/i18n.ts index 9cfb4e6..f6db93f 100644 --- a/startos/manifest/i18n.ts +++ b/startos/manifest/i18n.ts @@ -31,3 +31,16 @@ export const filebrowserDescription = { fr_FR: 'Facultatif. Permet à Paperless-ngx de surveiller un dossier de FileBrowser Quantum, afin que les documents que vous y déposez soient importés automatiquement.', } + +export const nextexplorerDescription = { + en_US: + 'Optional. Lets Paperless-ngx watch a location in NextExplorer, so documents you drop there are imported automatically.', + es_ES: + 'Opcional. Permite que Paperless-ngx vigile una ubicación de NextExplorer, de modo que los documentos que dejes allí se importen automáticamente.', + de_DE: + 'Optional. Lässt Paperless-ngx einen Standort in NextExplorer überwachen, sodass dort abgelegte Dokumente automatisch importiert werden.', + pl_PL: + 'Opcjonalne. Pozwala Paperless-ngx obserwować lokalizację w NextExplorer, dzięki czemu umieszczone tam dokumenty są importowane automatycznie.', + fr_FR: + 'Facultatif. Permet à Paperless-ngx de surveiller un emplacement de NextExplorer, afin que les documents que vous y déposez soient importés automatiquement.', +} diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 03e2d99..a1c962b 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -1,5 +1,10 @@ import { setupManifest } from '@start9labs/start-sdk' -import { filebrowserDescription, long, short } from './i18n' +import { + filebrowserDescription, + long, + nextexplorerDescription, + short, +} from './i18n' export const manifest = setupManifest({ id: 'paperless-ngx', @@ -30,5 +35,13 @@ export const manifest = setupManifest({ icon: 'https://raw.githubusercontent.com/Start9Labs/filebrowser-quantum-startos/e936a6c85a97b930b43cad5e9c0dd4898a2df567/icon.svg', }, }, + nextexplorer: { + description: nextexplorerDescription, + optional: true, + metadata: { + title: 'NextExplorer', + icon: 'https://raw.githubusercontent.com/Start9Labs/nextexplorer-startos/c0539454628fffa7873841cbd286b20174acd33b/icon.svg', + }, + }, }, }) diff --git a/startos/utils.ts b/startos/utils.ts index c394ffc..aae0379 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -10,6 +10,8 @@ export const dataMountpoint = '/usr/src/paperless/data' export const consumeMountpoint = '/usr/src/paperless/consume' export const filebrowserMountpoint = '/mnt/filebrowser' export const defaultConsumeSubfolder = 'paperless' +export const nextexplorerMountpoint = '/mnt/nextexplorer' +export const defaultConsumeLocation = 'Paperless' export const srcDir = '/usr/src/paperless/src' // Both the main daemon and the set-admin-password action mount the same set of diff --git a/startos/versions/current.ts b/startos/versions/current.ts index 7cbe0bc..ba44ed6 100644 --- a/startos/versions/current.ts +++ b/startos/versions/current.ts @@ -1,18 +1,17 @@ import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' export const current = VersionInfo.of({ - version: '2.20.15:5', + version: '2.20.15:6', releaseNotes: { - en_US: - 'New **Set Consume Folder** action: Paperless-ngx can watch a folder in FileBrowser Quantum, so documents dropped there are imported automatically.', + en_US: '**Set Consume Folder** can now watch a location in NextExplorer.', es_ES: - 'Nueva acción **Establecer carpeta de consumo**: Paperless-ngx puede vigilar una carpeta de FileBrowser Quantum, de modo que los documentos que se dejen allí se importen automáticamente.', + '**Establecer carpeta de consumo** ahora puede vigilar una ubicación de NextExplorer.', de_DE: - 'Neue Aktion **Konsum-Ordner festlegen**: Paperless-ngx kann einen Ordner in FileBrowser Quantum überwachen, sodass dort abgelegte Dokumente automatisch importiert werden.', + '**Konsum-Ordner festlegen** kann jetzt einen Standort in NextExplorer überwachen.', pl_PL: - 'Nowa akcja **Ustaw folder konsumpcji**: Paperless-ngx może obserwować folder w FileBrowser Quantum, dzięki czemu umieszczone tam dokumenty są importowane automatycznie.', + '**Ustaw folder konsumpcji** może teraz obserwować lokalizację w NextExplorer.', fr_FR: - 'Nouvelle action **Définir le dossier de consommation** : Paperless-ngx peut surveiller un dossier de FileBrowser Quantum, afin que les documents qui y sont déposés soient importés automatiquement.', + '**Définir le dossier de consommation** peut désormais surveiller un emplacement de NextExplorer.', }, migrations: { up: async () => {},