Skip to content

Commit b7b3596

Browse files
authored
Merge pull request #256 from rpuls/staging
Staging
2 parents 8af3c7a + 463ee9c commit b7b3596

83 files changed

Lines changed: 1776 additions & 377 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/dependabot.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ updates:
2222
timezone: "Europe/Amsterdam"
2323
open-pull-requests-limit: 10
2424

25+
- package-ecosystem: "docker"
26+
directory: "/apps/actual-budget"
27+
schedule:
28+
interval: "weekly"
29+
day: "monday"
30+
time: "06:50"
31+
timezone: "Europe/Amsterdam"
32+
open-pull-requests-limit: 10
33+
2534
- package-ecosystem: "docker"
2635
directory: "/apps/immich"
2736
schedule:

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ Updater-facing software changes only — documentation, site, repository, and co
66

77
### Added
88

9+
- Actual Budget is available in the app catalog. It is envelope budgeting on your own server: you hand out the money that is actually in your accounts across categories at the start of each month and watch what is left in each one come down as you spend, with schedules for the bills you already know about and reports built from your own history. It runs as a single service with one data volume, sets its own password the first time you open it, and connects to no bank unless you add bank-sync credentials yourself.
10+
911
- Paperless-ngx is available in the app catalog. Scan or photograph letters, invoices and contracts and it reads the text so you can search for any word inside them, suggesting the sender and tags for each new document. Text recognition runs on your own server in English, Dutch, German, French, Italian or Spanish, chosen during install; its optional cloud AI and remote OCR features stay off, and its own update check is switched off because MOS handles app updates. Its privacy assessment is rated "Private by default": watched on the wire through a full session of uploading, text recognition, searching and browsing its settings, neither of its containers contacted anything outside your server.
1012

1113
### Changed
1214

15+
- Vaultwarden and Stirling PDF now show screens in the catalog before you install them: the vault, an entry with its two-factor code, the password generator and a shared Send for Vaultwarden; the toolbox, merging statements, redacting a payslip and reading a scan for Stirling PDF. Both ship a patch package update that carries the images and changes nothing about how either app runs.
16+
- Every catalog package now names the full set of commercial products it stands in for instead of two or three, ranked by how widely known each one is. Suite Manager's app search matches those names, so searching "onedrive", "lastpass" or "ynab" finds the app that takes their place; screens with limited room still show only the two best-known.
1317
- Immich's privacy assessment was redone against observed behaviour rather than documentation, using a network capture of the running package and a capture of the browser client. Its posture moves from "Privacy configured" to "External dependency". The map is enabled and, because MOS pins Immich's configuration to a file, no owner or administrator can turn it off; opening it requests map imagery from Immich's tile service, which its privacy policy says logs the requested tile, the IP address and timing. MOS accepted that trade because the map is part of what makes Immich worth running, and your photo coordinates are never sent — markers come from your own server and are drawn in your browser. The assessment now names every external host reached, corrects the version check's destination from GitHub to version.immich.cloud, and records that turning photo GPS into place names runs locally from data shipped inside the image.
14-
- The "External dependency" privacy label now states that MOS reviewed the dependency and accepted it, rather than leaving owners to read a bare warning. Every catalog app has a completed review, so the posture describes an accepted cost, not an unfinished assessment.
18+
- Privacy labels now answer two plain questions instead of five overlapping ones: installed the way MOS ships it, does anything leave your server, and if it does, who decided that. A new label, "Your choice", covers apps where something leaves and the app itself has a setting that stops it — MOS picks the default it thinks is right for most people and tells you where the switch is. "External dependency" is reserved for cases where nothing in the app can stop it, and now states that MOS reviewed and accepted the trade rather than leaving you a bare warning. A reviewed app can no longer be labelled "Not yet reviewed"; that is now only ever the state of an app whose review has not been done. Two labels changed as a result and no letter grade moved: Seafile is now "Private by default", because every external integration it offers is off unless you connect one; Vaultwarden is now "Your choice", because it fetches website icons for the sites you save passwords for, which tells those sites that someone at your server's address has an account there. That was previously labelled as though MOS had turned it off, which MOS had not. Vaultwarden's own Settings screen has a "Show website icons" preference that stops it. The label also now links to the full assessment, published on each app's page: every piece of evidence with its source, what the review covered and deliberately did not, the policies read, how it was produced — and the open questions it does not settle. **Compatibility:** the assessment format changed, and an older MOS cannot read it, so all eight catalog packages ship a patch update requiring this release or newer. Update MOS before updating apps; an older installation is not offered these package versions at all rather than failing partway.
1519
- Immich package 0.5.0 updates Immich to v3.1.0. Its health check now targets the endpoint Immich actually serves, so a server whose API is failing is reported as unhealthy instead of passing on a 404. Immich's database migrations are forward-only: once the update has applied, going back to the previous Immich version is not supported. Live Photos uploaded in the background on the previous version may have missing thumbnails; running Immich's "missing" thumbnail job clears them, as does the nightly job.
1620

1721
### Fixed

apps/README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,24 @@ Every installed package records its source repository, path, immutable source re
2525

2626
Each reviewed candidate package owns a `privacy-review.json` and a compact manifest summary. Reviews are validated by `npm run apps:privacy:check` against the contracts in `suite-manager/backend/src/apps/package-contracts.cjs`, which enforce the document's shape, its binding to the package it ships with, and the derivation of its posture. The assessment binds to the package version, digest, immutable source revision, component versions, and artifact digests. It travels into the installed package snapshot, so an owner sees the review for the package actually running rather than the latest repository wording. The assessment records provenance, including the AI model only when runtime-reported and whether a human reviewed it. It is not a legal audit or guarantee.
2727

28-
Postures are derived from their dimensions:
28+
The posture is derived from exactly two dimensions, and both are questions of fact rather than judgement. `defaultEgress` is settled by a runtime capture: installed as MOS ships it and used normally, does anything leave the owner's server? `control` is settled by reading the package and the app's own settings: who decided that, and can the owner change it? The line between `left-to-owner` and `accepted-by-mos` is whether the app offers a control the owner can actually reach — an in-app setting is the owner's decision to make, while a value reachable only by editing packaging MOS owns is MOS's decision and is recorded as MOS's.
2929

30-
- `private-by-default`: reviewed behavior requires no external service or account, processes app data locally, and has no known enabled telemetry.
31-
- `privacy-configured`: MOS uses supported package configuration to disable known optional telemetry, with no required external data processing.
32-
- `external-dependency`: normal operation requires an upstream account, service, telemetry, or external data processing. It is a completed assessment, not a warning to stay away: an app only reaches the catalog once its review is done, so this posture says MOS examined the dependency and accepted it as the price of the feature. The review's evidence carries what leaves, who receives it, and why it was accepted.
33-
- `review-required`: evidence is missing, stale, or unresolved.
30+
The four legal combinations map one-to-one onto the four postures:
3431

35-
Unknown facts always produce `review-required`. Evidence is labeled `observed`, `configured`, `documented`, or `inferred`; configuration alone must not be presented as proof of network silence. App updates and detected Terms, privacy-policy, ownership, telemetry, or outbound-dependency changes trigger reassessment.
32+
| `defaultEgress` | `control` | posture |
33+
| --- | --- | --- |
34+
| `none` | `nothing-to-decide` | `private-by-default` — the app has no external touchpoint to begin with. |
35+
| `none` | `disabled-by-mos` | `privacy-configured` — it had one, and MOS switched it off so the owner never has to. |
36+
| `external-contact` | `left-to-owner` | `owner-disableable` — something leaves, the app has a setting that stops it, and MOS judged the trade-off genuinely the owner's to make. |
37+
| `external-contact` | `accepted-by-mos` | `external-dependency` — something leaves, no in-app setting stops it, and MOS reviewed and accepted it as the price of the feature. |
38+
39+
Any other pairing is a contradiction and fails validation instead of resolving into a badge. The derivation has no fallthrough, so it cannot publish a verdict nobody chose.
40+
41+
The remaining dimensions — `accountDependency`, `dataProcessing`, `policyExposure`, `confidence` — describe the app and feed the published grade, but do not steer the posture. That separation is deliberate: no reviewer should ever have to bend a descriptive fact to reach a defensible badge.
42+
43+
No dimension may be `unknown`. An unestablished fact is not a posture — it means no review exists, which the catalog records as `privacy.status: review-required`, a process state that never reaches this derivation. A posture describes a finished review and nothing else.
44+
45+
Evidence is labeled `observed`, `configured`, `documented`, or `inferred`; configuration alone must not be presented as proof of network silence, so `defaultEgress: none` is only credible with an `observed` capture behind it. App updates and detected Terms, privacy-policy, ownership, telemetry, or outbound-dependency changes trigger reassessment.
3646

3747
Use `icon.png` in the package root for the catalog icon, and point `manifest.json` `icon` at that file. Richer screenshots, marketing assets, and `catalog.demoDeployTargets` are optional catalog metadata, not required package scaffolding. Demo deployment targets are public-site previews on third-party providers; they are not MOS installation instructions.
3848

apps/actual-budget/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Actual Budget 26.8.1.
2+
FROM ghcr.io/actualbudget/actual@sha256:6478d9ddfc0924479c09e6699c205e354c6f2216dfe7de3c0fb7b590d6edcdc5

apps/actual-budget/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Actual Budget MOS Package
2+
3+
## Services
4+
5+
- `actual`: the Actual sync server, which also serves the web client. Single service, exposed through the public `actual.<base-domain>` app route.
6+
7+
There is no database or cache service. The sync server keeps its account database, sessions and budget files as SQLite files under one data directory.
8+
9+
## Environment Variables
10+
11+
The package sets none. Every upstream default is correct for the way MOS runs the container, and the values worth knowing are:
12+
13+
- `ACTUAL_PORT` (default `5006`): matches `internalPort`.
14+
- `ACTUAL_DATA_DIR` (default `/data`): parent of everything persistent; the volume is mounted here.
15+
- `ACTUAL_SERVER_FILES` (default `/data/server-files`), `ACTUAL_USER_FILES` (default `/data/user-files`): both inside the mounted volume, so neither needs overriding.
16+
- `ACTUAL_TRUSTED_PROXIES` (default: private and loopback ranges): Caddy reaches the container over the package-owned Docker network from a private address, so the default already covers it.
17+
- `ACTUAL_LOGIN_METHOD` (default `password`): the server prompts for a password on first open. `openid` and `header` are upstream options this package does not configure.
18+
- `ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB` (default 20), `ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB` (default 50): sync payload ceilings. A budget with many years of transactions can grow past 20 MB; raising the limit is the fix if sync starts failing on upload.
19+
20+
The server reads `/data/config.json` if present, and environment variables override it. MOS does not write that file.
21+
22+
## First Start And The Bootstrap Window
23+
24+
The image ships no credentials. Until someone calls `POST /account/bootstrap`, `GET /account/needs-bootstrap` reports `bootstrapped: false` and the first visitor to the app URL is offered the "set a password" screen with no authentication in front of it.
25+
26+
The route is public as soon as the install finishes, and app hostnames are visible in certificate transparency logs, so this is a real if short window. Open `actual.<base-domain>` and set the password immediately after install.
27+
28+
The package deliberately collects no password as a setup field: the server has no environment variable that presets one, and the only startup-time bootstrap path upstream supports is OpenID. Closing the window properly needs an upstream credential-seeding mechanism, not manifest fields.
29+
30+
## Volumes And Persistence
31+
32+
- `data:/data`: everything. It holds `server-files/account.sqlite` (the server password hash, sessions, and any bank-sync API keys), `user-files/` (one folder per budget file, each with its own SQLite database and sync journal), and `.migrate`.
33+
34+
This single volume is the backup target; losing it loses the budgets. Disable stops and removes the container while keeping the route, volume and any stored secret references. Uninstall removes the container, route, MOS-owned Homepage shortcut, this volume, config and secret references.
35+
36+
Actual is local-first: each browser holds its own copy of the budget and syncs changes to the server. A restored server volume is authoritative, but a client that still has newer local changes will try to sync them on next open.
37+
38+
## Health Check
39+
40+
- `http://actual:5006/health`
41+
42+
Returns `200` with `{"status":"UP"}` and needs no session, before and after bootstrap. It is a dedicated liveness endpoint rather than a page borrowed from the UI, so it stays meaningful if the login flow changes.
43+
44+
## Outbound Network Behaviour
45+
46+
The image contains no analytics or crash-reporting library.
47+
48+
The server makes no outbound request on its own. The upstream hosts compiled into it are reached only when an owner switches something on:
49+
50+
- `bankaccountdata.gocardless.com`, `bridge.simplefin.org`, `api.enablebanking.com`, and the Pluggy.ai and Akahu endpoints: bank sync, each requiring API credentials the owner obtains and enters.
51+
- `raw.githubusercontent.com`, `api.github.com`, `github.com`: the experimental plugin store, fetched through the server's `/cors-proxy` route. That route requires a valid session, is rate limited to 25 requests per minute, resolves and rejects private, loopback and link-local targets, and only forwards to repositories on the upstream plugin allowlist. Nothing is fetched until the browser asks for it.
52+
53+
The **web client** does make one request by default. `getLatestVersion()` fetches `https://api.github.com/repos/actualbudget/actual/releases/latest` and compares the tag with the running client's version, so the About screen can say a newer release exists. Points worth being precise about:
54+
55+
- It comes from the owner's browser, not from the server, so the address disclosed to GitHub is the owner's, not the box's.
56+
- It carries no identifier: no install id, no version, no usage data. It is a read of a public URL.
57+
- It is gated on the global preference `notifyWhenUpdateIsAvailable`, which defaults to on and is exposed as "Display a notification when updates are available" in Settings.
58+
- Observed firing on first load of the budget during the screenshot capture session, which is how it was found rather than inferred.
59+
60+
Because it is a client-side preference rather than a server setting, there is no environment variable for this package to pin, and MOS does not disable it.
61+
62+
## Upgrades
63+
64+
The server runs SQLite migrations on start for both the account database and each budget file, hence `backupRequired: true` and brief downtime.
65+
66+
Actual's own guidance is to keep the sync server and the client in step; because MOS serves the client from the same image, that happens by construction. A budget file migrated by a newer server is not guaranteed to open on an older one, which is why `rollback: safe` refers to the container, not to reversing a data migration.
67+
68+
## Import And Export
69+
70+
Actual imports YNAB4 and nYNAB budgets and reads CSV, OFX/QFX and CAMT.053 transaction files, all through the web interface. Export produces a `.zip` of the budget file that can be re-imported into any Actual instance; it is the portable copy to keep alongside MOS volume backups.

apps/actual-budget/icon.png

19.6 KB
Loading

0 commit comments

Comments
 (0)