Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
f02a1a5
chore(release): pin Jellium in-page select click-outside fix
selmant Aug 21, 2026
3383dfa
feat: supervise bundled standalone Foreseerr
selmant Aug 22, 2026
27f30b1
fix: atomically persist desktop mode configuration
selmant Aug 22, 2026
9645f9c
feat: reap standalone child process groups on Unix
selmant Aug 22, 2026
440425f
feat: offer standalone mode in native setup
selmant Aug 22, 2026
9204a19
test: cover standalone setup action
selmant Aug 22, 2026
591a89e
build: pin bundled Node runtime
selmant Aug 22, 2026
774f77b
fix: enforce safe standalone cache minimum
selmant Aug 22, 2026
1f62eaa
feat: bridge browser cache clearing to Foreseer
selmant Aug 22, 2026
ae74c46
fix: keep browser cache clear behind server authorization
selmant Aug 22, 2026
0c121be
feat: expose managed child health states
selmant Aug 22, 2026
6a9886a
feat: track recovery health thresholds
selmant Aug 22, 2026
51f1344
build: bundle pinned Foreseerr runtime
selmant Aug 22, 2026
8031d28
build: validate pinned standalone runtime contracts
selmant Aug 22, 2026
0e0386f
feat: clear browser cache through native runtime bridge
selmant Aug 22, 2026
66f10fe
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
2084e22
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
74c511f
feat: contain standalone child tree in Windows job
selmant Aug 22, 2026
11015eb
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
d9e102e
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
e0ed46c
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
24ef9e2
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
b6f7d23
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
0b2185d
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
bc846bb
feat: show recovery screen after standalone failure
selmant Aug 22, 2026
82b2994
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
68f9587
fix: suppress recovery during normal shutdown
selmant Aug 22, 2026
bc5a8dd
feat: retry standalone runtime on original port
selmant Aug 22, 2026
218d093
build: advance pinned Foreseerr runtime
selmant Aug 22, 2026
30c5ed0
test: cover standalone runtime retry command
selmant Aug 22, 2026
f7f9f13
fix: safely render standalone startup failures
selmant Aug 22, 2026
800a3bf
build: pin managed runtime startup cleanup
selmant Aug 22, 2026
e6f2d2b
build: pin shared memory cache runtime
selmant Aug 22, 2026
ed08d0b
build: pin complete image cache clearing
selmant Aug 22, 2026
fb1eacd
feat: open standalone logs from recovery UI
selmant Aug 22, 2026
47e58a1
build: pin native recovery log contract
selmant Aug 22, 2026
114129b
feat: attempt one automatic standalone recovery
selmant Aug 22, 2026
59c2ab8
build: pin scheduler shutdown cleanup
selmant Aug 22, 2026
c5e7ed8
feat: record verified schema in upgrade backups
selmant Aug 22, 2026
6b48ea4
build: pin readiness schema metadata runtime
selmant Aug 22, 2026
541f230
fix: block upgrade backup while child owns data
selmant Aug 22, 2026
de30740
fix: reap managed child on readiness failure
selmant Aug 22, 2026
486a985
build: pin native browser cache redemption fix
selmant Aug 22, 2026
19bf038
feat: signal managed runtime when CEF is ready
selmant Aug 22, 2026
55c36aa
build: pin CEF-ready catch-up runtime
selmant Aug 22, 2026
a86cc7c
feat: relaunch after enabling standalone mode
selmant Aug 22, 2026
b89b31e
feat: offer remote mode from runtime recovery
selmant Aug 22, 2026
367f006
build: pin remote recovery protocol runtime
selmant Aug 22, 2026
57a3f68
build: pin managed restart cleanup
selmant Aug 22, 2026
91de8fe
build: pin initial setup catch-up fix
selmant Aug 22, 2026
756a505
build: pin bound-port diagnostics fix
selmant Aug 22, 2026
6ea740d
feat: add quit action to startup recovery
selmant Aug 22, 2026
5cce53f
feat: label standalone recovery retry explicitly
selmant Aug 22, 2026
5dfb884
fix: scrub hosted database variables from child
selmant Aug 22, 2026
3079102
build: pin generic Jellium cache APIs
selmant Aug 22, 2026
9f2d3c8
fix: separate CEF and standalone cache roots
selmant Aug 22, 2026
f60fec8
build: trim development files from staged runtime
selmant Aug 22, 2026
12d84de
fix: defer managed status verification until CEF is live
selmant Aug 22, 2026
41d122e
fix: verify managed child revision in readiness
selmant Aug 22, 2026
b967e24
build: pin standalone application URL runtime
selmant Aug 22, 2026
507d26a
build: pin playback-aware scheduler runtime
selmant Aug 22, 2026
9ccbf21
build: pin managed manual scan runtime
selmant Aug 22, 2026
be810f4
build: require pinned clean Foreseerr staging source
selmant Aug 22, 2026
e0714df
fix: stage Windows Node runtime with executable extension
selmant Aug 22, 2026
b1bcef2
build: pin serial playback scheduler drain
selmant Aug 22, 2026
e8e5c26
build: pin durable standalone URL guard
selmant Aug 22, 2026
191a993
build: pin normalized standalone URL guard
selmant Aug 22, 2026
dfd24a8
fix: probe standalone health immediately after CEF readiness
selmant Aug 22, 2026
3fdc937
docs: document standalone upgrade recovery
selmant Aug 22, 2026
5cc8a9e
feat: expose standalone cache budget preference
selmant Aug 22, 2026
66b71b9
feat: advertise desktop mode preference capability
selmant Aug 22, 2026
6b8dd63
build: pin desktop mode preference UI
selmant Aug 22, 2026
4809d88
fix: keep standalone Foreseerr alive and ignore staged bundle artifacts
selmant Aug 22, 2026
415c092
feat: in-process remote setup and Jellyfin HTTPS fallback
selmant Aug 22, 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
127 changes: 126 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@ jobs:
path: foreseer-desktop
fetch-depth: 0

- name: Read pinned Foreseerr revision
id: foreseerr
shell: bash
run: |
revision="$(tr -d '[:space:]' < foreseer-desktop/foreseerr.rev)"
test -n "${revision}"
echo "revision=${revision}" >> "${GITHUB_OUTPUT}"

- name: Checkout pinned Foreseerr
uses: actions/checkout@v4
with:
repository: selmant/foreseerr
ref: ${{ steps.foreseerr.outputs.revision }}
path: SeerrSuggestArr
submodules: recursive

- name: Install pinned Node
uses: actions/setup-node@v4
with:
node-version-file: foreseer-desktop/node.rev

- name: Validate release pins
working-directory: foreseer-desktop
shell: bash
env:
FORESEERR_DIR: ${{ github.workspace }}/SeerrSuggestArr
run: ./scripts/check-release-pins.sh

- name: Read pinned Jellium revision
id: jellium
shell: bash
Expand All @@ -44,6 +72,24 @@ jobs:
path: jellium-desktop
submodules: recursive

- name: Audit pinned runtime boundary and protocol contracts
working-directory: foreseer-desktop
shell: bash
env:
JELLIUM_DIR: ${{ github.workspace }}/jellium-desktop
FORESEERR_DIR: ${{ github.workspace }}/SeerrSuggestArr
run: |
set -euo pipefail
./scripts/boundary-audit.sh
node -e '
const fs = require("fs");
const a = JSON.parse(fs.readFileSync(process.env.FORESEERR_DIR + "/protocol/protocol-v1.json"));
const b = JSON.parse(fs.readFileSync("protocol/protocol-v1.json"));
if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error("Foreseerr protocol fixture differs from desktop fixture");
'
grep -q 'FORESEERR_DESKTOP_READY' "$FORESEERR_DIR/server/index.ts"
grep -q 'protocolVersion: 1' "$FORESEERR_DIR/server/index.ts"

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

Expand Down Expand Up @@ -98,6 +144,12 @@ jobs:
print "cd /workspace/jellium-desktop"
next
}
$0 == "# Desktop integration" {
print "# Bundled Foreseerr + Node runtime (resolved relative to the executable)."
print "mkdir -p \"$APPDIR/usr/bin/resources\""
print "cp -a /workspace/foreseer-desktop/resources/. \"$APPDIR/usr/bin/resources/\""
print
}
{
# POSIX awk replacement strings do not support capture-group
# backreferences. Every Jellium executable-name occurrence in
Expand Down Expand Up @@ -158,17 +210,44 @@ jobs:
mkdir -p .build/appimage dist

docker run --rm \
--env FORESEERR_DIR=/workspace/SeerrSuggestArr \
--env FORESEERR_NODE_BIN=/opt/foreseer-node/bin/node \
--env VERSION="${VERSION}" \
--volume "${GITHUB_WORKSPACE}:/workspace" \
--volume "${GITHUB_WORKSPACE}/.build/appimage:/build" \
--volume "${GITHUB_WORKSPACE}/dist:/host-output" \
foreseer-desktop-appimage:ci \
/workspace/jellium-desktop/dev/linux/appimage/container-build.sh
sh -ec '
set -eu
node_version="$(tr -d "[:space:]" < /workspace/foreseer-desktop/node.rev)"
architecture="$(uname -m)"
case "$architecture" in
x86_64) node_arch=x64 ;;
aarch64) node_arch=arm64 ;;
*) echo "unsupported Node architecture: $architecture" >&2; exit 1 ;;
esac
wget -q -O /tmp/node.tar.xz "https://nodejs.org/dist/$node_version/node-$node_version-linux-$node_arch.tar.xz"
mkdir -p /opt/foreseer-node
tar -xJf /tmp/node.tar.xz --strip-components=1 -C /opt/foreseer-node
corepack enable
corepack prepare pnpm@10.24.0 --activate
/workspace/foreseer-desktop/scripts/stage-foreseerr.sh /workspace/foreseer-desktop/resources
/workspace/jellium-desktop/dev/linux/appimage/container-build.sh
'

sudo chown -R "$(id -u):$(id -g)" dist .build jellium-desktop/.cache || true
test -f "dist/ForeseerDesktop-${VERSION}-x86_64.AppImage"
chmod +x "dist/ForeseerDesktop-${VERSION}-x86_64.AppImage"

- name: Verify Linux runtime payload
shell: bash
run: |
set -euo pipefail
test -f foreseer-desktop/resources/foreseerr/launcher.js
test -x foreseer-desktop/resources/node/node
test -f foreseer-desktop/resources/THIRD_PARTY_NOTICES.txt
strings "dist/ForeseerDesktop-${{ steps.version.outputs.version }}-x86_64.AppImage" | grep -q 'foreseerr/launcher.js'

- name: Upload Linux artifact
uses: actions/upload-artifact@v4
with:
Expand All @@ -187,6 +266,41 @@ jobs:
path: foreseer-desktop
fetch-depth: 0

- name: Read pinned Foreseerr revision
id: foreseerr
shell: pwsh
run: |
$revision = (Get-Content foreseer-desktop/foreseerr.rev -Raw).Trim()
if (-not $revision) { throw "foreseerr.rev is empty" }
"revision=$revision" >> $env:GITHUB_OUTPUT

- name: Checkout pinned Foreseerr
uses: actions/checkout@v4
with:
repository: selmant/foreseerr
ref: ${{ steps.foreseerr.outputs.revision }}
path: SeerrSuggestArr
submodules: recursive

- name: Install pinned Node
uses: actions/setup-node@v4
with:
node-version-file: foreseer-desktop/node.rev

- name: Stage bundled Foreseerr runtime
shell: pwsh
env:
FORESEERR_DIR: ${{ github.workspace }}\SeerrSuggestArr
run: |
corepack enable
corepack prepare pnpm@10.24.0 --activate
$bash = Join-Path $env:ProgramFiles 'Git\bin\bash.exe'
if (-not (Test-Path $bash)) { throw "Git Bash is required to stage the runtime" }
& $bash -lc 'cd "$(cygpath -u "$GITHUB_WORKSPACE")"; export FORESEERR_DIR="$(cygpath -u "$FORESEERR_DIR")"; ./foreseer-desktop/scripts/stage-foreseerr.sh ./bundle/resources'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
if (-not (Test-Path bundle/resources/foreseerr/launcher.js)) { throw "Foreseerr runtime was not staged" }
if (-not (Test-Path bundle/resources/node/node.exe)) { throw "Node runtime was not staged" }

- name: Read pinned Jellium revision
id: jellium
shell: pwsh
Expand All @@ -205,6 +319,14 @@ jobs:
path: jellium-desktop
submodules: recursive

- name: Audit pinned runtime boundary and protocol contracts
shell: pwsh
run: |
$bash = Join-Path $env:ProgramFiles 'Git\bin\bash.exe'
if (-not (Test-Path $bash)) { throw "Git Bash is required for the boundary audit" }
& $bash -lc 'cd "$(cygpath -u "$GITHUB_WORKSPACE")/foreseer-desktop"; export JELLIUM_DIR="$(cygpath -u "$GITHUB_WORKSPACE")/jellium-desktop"; export FORESEERR_DIR="$(cygpath -u "$GITHUB_WORKSPACE")/SeerrSuggestArr"; ./scripts/boundary-audit.sh; node -e '\''const fs=require("fs"); const a=JSON.parse(fs.readFileSync(process.env.FORESEERR_DIR + "/protocol/protocol-v1.json")); const b=JSON.parse(fs.readFileSync("protocol/protocol-v1.json")); if(JSON.stringify(a)!==JSON.stringify(b)) throw new Error("Foreseerr protocol fixture differs from desktop fixture");'\''; grep -q FORESEERR_DESKTOP_READY "$FORESEERR_DIR/server/index.ts"; grep -q "protocolVersion: 1" "$FORESEERR_DIR/server/index.ts"'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

Expand Down Expand Up @@ -300,6 +422,9 @@ jobs:
if (-not (Test-Path bundle/foreseer-desktop.exe)) {
throw "foreseer-desktop.exe was not staged"
}
if (-not (Test-Path bundle/resources/THIRD_PARTY_NOTICES.txt)) {
throw "bundled runtime notices were not staged"
}

Compress-Archive -Path bundle/* -DestinationPath $archive -Force

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/target
/logs
/resources
*.AppImage
**/*.rs.bk
.idea/
.vscode/
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Unreleased

### Fixed

- Keep the request modal open when picking a quality profile, root folder, or
other native `<select>` option. The in-page dropdown overlay was reaching the
modal's click-outside handler (browser-native popups do not).

- Adopt a new Jellyfin server Id after the media server is reinstalled, instead
of stalling on ConnectionManager `ServerMismatch` because cached credentials
and the `/login` hash gate blocked session bootstrap.

## 0.2.9 — 2026-08-14

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ sha2 = "0.10"
directories = "6"
url = "2"
tracing = "0.1"
libc = "0.2"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_System_JobObjects",
"Win32_System_Threading",
] }

[dev-dependencies]
tempfile = "3"
Expand Down
52 changes: 41 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ See [LICENSE](LICENSE).

## How this fits the Foreseer product

Foreseer Desktop is an optional client for the hosted
[Foreseerr](https://github.com/selmant/foreseerr) application. It does not
replace or bundle the web app, run a separate request server, or own the user's
media-account configuration.
Foreseer Desktop defaults to standalone mode: it starts a bundled Foreseerr
server on an ephemeral `127.0.0.1` port and owns its local data. Remote mode
remains available for an existing Foreseerr deployment.

| Component | Owns |
| --- | --- |
Expand Down Expand Up @@ -63,15 +62,17 @@ Foreseer Desktop persists its configuration in a standard OS config directory:

```json
{
"server_url": "https://foreseer.example.com",
"allow_insecure_http": false
"schema_version": 2,
"mode": "standalone",
"remote": { "server_url": "https://foreseer.example.com", "allow_insecure_http": false },
"standalone": { "cache_limit_bytes": 2147483648 }
}
```

### CLI Commands & Environment Variables

```sh
# Run with default or saved server URL:
# Run the saved standalone or remote mode:
cargo run

# Launch the graphical server setup GUI:
Expand All @@ -80,16 +81,45 @@ cargo run -- --setup
# View current configuration and file location:
cargo run -- --show-config

# Set a new default server URL:
cargo run -- --set-url https://foreseer.example.com
# Switch modes:
cargo run -- --standalone
cargo run -- --remote https://foreseer.example.com

# Allow HTTP (non-HTTPS) server URL:
cargo run -- --set-url http://192.168.1.50:5055 --allow-http
# Set the combined transient cache budget (images + CEF HTTP cache):
cargo run -- --cache-limit 2147483648

# HTTP or HTTPS — the URL scheme is the choice:
cargo run -- --set-url http://192.168.1.50:5055

# Temporary environment variable override (does not modify config.json):
FORESEER_URL=https://foreseer.example cargo run
```

### Standalone data and recovery

Standalone Foreseerr data is separate from the Jellium profile:

```text
<Foreseer config>/standalone/
settings.json
db/db.sqlite3
logs/
state/
backups/
```

Before starting a bundled Foreseerr version different from the last verified
standalone version, the desktop creates a timestamped backup of `settings.json`
and the SQLite database (including WAL/SHM files). Caches and logs are never
included in those backups, and only the three newest automatic backups are
kept.

If migration or startup fails, use the recovery screen’s **Open Logs** action,
then stop the desktop before attempting manual recovery. Do not copy an older
database over a database while a newer bundled binary is running: automatic
restore is intentionally not implemented because schema downgrades are unsafe.
Keep the failed database, logs, and backup together until recovery is complete.

## Test / lint

```sh
Expand Down
Loading
Loading