Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ docs/_private/

# Operator BIOS staging from scripts/import_bios.py (never bake dumps into the image)
.bios-install-out/
.bios-install-out-*/
bios/
**/bios/*.bin
**/bios/*.rom
Expand Down
1 change: 1 addition & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ REMEMBER_COOKIE_SECURE=true
CHAT_ATTACHMENT_STORAGE_MAX_BYTES=0
CHAT_ATTACHMENT_STORAGE_MAX_FILES=0
TRUSTED_PROXIES=0
TRUSTED_PROXY_CIDRS=

# Hosts allowed to become the origin of an emailed link (password reset, invite).
# Empty trusts private/LAN hosts only, so http://192.168.x.x links keep working
Expand Down
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ OIDC_DISPLAY_NAME=Sign in with SSO
SESSION_COOKIE_SECURE=true
REMEMBER_COOKIE_SECURE=true
TRUSTED_PROXIES=0
# Only peers in these addresses/CIDRs may supply forwarded headers. Example:
# TRUSTED_PROXY_CIDRS=172.20.0.5/32,172.20.0.0/24
TRUSTED_PROXY_CIDRS=

# Hosts allowed to become the origin of an emailed link (password reset, invite).
# Leave empty on a LAN install: private/loopback hosts are trusted by default, so
Expand Down
1 change: 1 addition & 0 deletions .env.nas.example
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ REMEMBER_COOKIE_SECURE=true
CHAT_ATTACHMENT_STORAGE_MAX_BYTES=0
CHAT_ATTACHMENT_STORAGE_MAX_FILES=0
TRUSTED_PROXIES=0
TRUSTED_PROXY_CIDRS=

# Hosts allowed to become the origin of an emailed link (password reset, invite).
# Empty trusts private/LAN hosts only, so http://192.168.x.x links keep working
Expand Down
4 changes: 4 additions & 0 deletions .env.single.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ TZ=UTC
# Use an external Postgres instead of the embedded one:
# set DATABASE_URL=postgresql://user:pass@host:5432/oneirodex
# Force the embedded one even if DATABASE_URL is set: ONEIRODEX_EMBEDDED_DB=true
# Optional reverse-proxy trust. Forwarded headers are honored only when the
# immediate proxy source also matches TRUSTED_PROXY_CIDRS.
TRUSTED_PROXIES=0
TRUSTED_PROXY_CIDRS=
1 change: 1 addition & 0 deletions .env.unraid.example
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ REMEMBER_COOKIE_SECURE=true
CHAT_ATTACHMENT_STORAGE_MAX_BYTES=0
CHAT_ATTACHMENT_STORAGE_MAX_FILES=0
TRUSTED_PROXIES=0
TRUSTED_PROXY_CIDRS=

# Hosts allowed to become the origin of an emailed link (password reset, invite).
# Empty trusts private/LAN hosts only, so http://192.168.x.x links keep working
Expand Down
32 changes: 26 additions & 6 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Oneirodex CI — PR gate with a *core* pytest subset (+ member-app / desktop vitest).
# Oneirodex CI — PR gate with the pytest marker set (+ member-app / desktop vitest).
#
# Full pytest suite remains local / release-time (needs Unraid-like fixtures,
# library paths, and heavier integrations). Do not treat this workflow as
Expand Down Expand Up @@ -31,7 +31,7 @@ jobs:
pytest-core:
name: Pytest (core)
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 35

services:
postgres:
Expand Down Expand Up @@ -73,6 +73,28 @@ jobs:
# branch red with no repo change behind it.
pip install -r requirements-dev.txt

- name: Set up Node.js 22
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '22'
cache: npm
cache-dependency-path: package-lock.json

- name: Install frontend dependencies
run: npm ci

- name: Build React shells required by Python route tests
run: |
npm run build --workspace=member-app
npm run build:auth-pilot --workspace=member-app
npm run build --workspace=admin-app
npm run build:pilot --workspace=admin-app

- name: Verify store sync transaction invariants
# Keep this SQLite compatibility slice ahead of the 20+ minute core
# suite so transaction regressions fail quickly and with a focused log.
run: python -m pytest tests/test_store_connections.py -q

- name: Run pytest (marker set, deselect by exception)
# Gate = every test not marked `integration`, minus a named exception
# list. Flipped from a hand-enumerated file list on 2026-09-17 after
Expand All @@ -81,8 +103,8 @@ jobs:
# default; the only way to keep one out is a line below, with its ID.
#
# The --deselect list is docs/dev/archive/test-suite-failures-2026-09-16.md's
# known failures as they reproduce on the runner (three are runner-
# only: the SPA dist is not built in this job, a Windows drive-letter
# known failures as they reproduce on the runner (two are runner-
# only: a Windows drive-letter
# case, the font catalogue). Remove a line when its test is fixed;
# never add one to get a red PR green.
#
Expand All @@ -92,14 +114,12 @@ jobs:
python -m pytest tests/ -q -m "not integration"
--cov=oneirodex --cov-report=term-missing
--cov-fail-under=65
--deselect tests/test_admin_spa_assets.py::test_admin_spa_dist_built
--deselect tests/test_checkout_paths.py::test_operator_docs_checkout_is_oneirodex
--deselect tests/test_collections_api_wiring.py::test_newsletter_ckeditor_targets_content_field
--deselect tests/test_collections_api_wiring.py::test_spa_collections_client_covers_new_actions
--deselect tests/test_library_roots.py::TestParseLibraryRoots::test_windows_drive_letter_is_not_mistaken_for_a_label
--deselect tests/test_member_chrome_css.py::test_tile_hover_has_no_accent_glow_and_grows_from_center
--deselect tests/test_member_chrome_css.py::test_topbar_cluster_is_not_a_shared_outline
--deselect tests/test_member_spa_assets.py::test_member_spa_links_member_app_css
--deselect tests/test_routes_admin_ext_settings_shell.py::TestSettingsShellSections::test_every_section_deep_link_is_reachable_from_the_react_hub
--deselect tests/test_routes_apis_download.py::TestDeleteDownloadRequest::test_delete_invalid_request_id_zero
--deselect tests/test_routes_apis_download.py::TestDeleteDownloadRequest::test_delete_request_database_error
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.1.9] — 2026-10-15

### Improved
- Unify member and admin navigation, controls, tables, filters and shared icon/theme primitives across the React applications.
- Complete the static React and native FastAPI runtime transition, removing Jinja-rendered application pages while retaining existing session compatibility.
- Scrub Docker build context of user BIOS staging output so firmware and key files cannot enter shipped image layers.

### Security
- Authenticate forwarded client and origin headers against `TRUSTED_PROXY_CIDRS` before applying `TRUSTED_PROXIES` hop parsing. Supported launchers disable Uvicorn's implicit rewriting. Reverse-proxy deployments must configure the immediate proxy source IP/CIDR.
- Bind desktop path reveals to an accessible game folder; require admin authority for unmatched and unbound scan-job paths.
- Revalidate qBittorrent hardlink pipeline redirects and outbound destinations through the shared safe-request policy.

## [1.1.8] — 2026-10-08

### Improved
Expand Down
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ COPY oneirodex/setup/default_theme/js/scanJobsDom.js oneirodex/setup/default_the
# vite writes each bundle to oneirodex/static/dist/<app>/ (outDir is resolved
# from the app dir in its vite.config); `npm run build` per workspace runs the
# app's `tsc --noEmit && vite build`.
RUN mkdir -p oneirodex/static/dist/member-app oneirodex/static/dist/admin-app oneirodex/static/dist/ops-glance \
RUN mkdir -p oneirodex/static/dist/member-app oneirodex/static/dist/admin-app oneirodex/static/dist/admin-pilot oneirodex/static/dist/ops-glance \
&& npm run build --workspace=member-app \
&& npm run build:auth-pilot --workspace=member-app \
&& npm run build --workspace=admin-app \
&& npm run build:pilot --workspace=admin-app \
&& npm run build --workspace=ops-glance

FROM python:3.12-slim
Expand Down Expand Up @@ -89,7 +91,9 @@ RUN mkdir -p /etc/postgresql-common \

COPY . .
COPY --from=frontend-build /build/oneirodex/static/dist/member-app /app/oneirodex/static/dist/member-app
COPY --from=frontend-build /build/oneirodex/static/dist/member-auth /app/oneirodex/static/dist/member-auth
COPY --from=frontend-build /build/oneirodex/static/dist/admin-app /app/oneirodex/static/dist/admin-app
COPY --from=frontend-build /build/oneirodex/static/dist/admin-pilot /app/oneirodex/static/dist/admin-pilot
COPY --from=frontend-build /build/oneirodex/static/dist/ops-glance /app/oneirodex/static/dist/ops-glance

RUN pip install -r requirements.txt
Expand Down
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</p>

<p align="center">
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-1.1.8-2fd67b?style=flat-square&labelColor=0b0d10" alt="Version 1.1.8" /></a>
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-1.1.9-2fd67b?style=flat-square&labelColor=0b0d10" alt="Version 1.1.9" /></a>
<a href="https://github.com/cephyrixzyth/oneirodex"><img src="https://img.shields.io/badge/github-cephyrixzyth%2Foneirodex-141820?style=flat-square&logo=github&labelColor=0b0d10" alt="GitHub" /></a>
<a href="#-quick-start"><img src="https://img.shields.io/badge/port-5006-141820?style=flat-square&labelColor=0b0d10" alt="Port 5006" /></a>
<a href="#-quick-start"><img src="https://img.shields.io/badge/docker-compose-2496ED?style=flat-square&logo=docker&logoColor=white&labelColor=0b0d10" alt="Docker Compose" /></a>
Expand Down Expand Up @@ -39,14 +39,14 @@
<a id="-what-is-oneirodex"></a>
<img src="docs/assets/readme/h-what.svg" alt="01 · Overview — What is Oneirodex?" width="100%" />

Oneirodex is a **Flask + React** server you run at home or on a NAS. Point it at folders of DRM-free games and ROMs, let it identify them against IGDB / Steam / GOG / RAWG, then give the household a modern browser app to **browse, download, play in the browser where a system supports it, and hang out** — with a desktop companion for PC titles and a ten-foot mode for the TV.
Oneirodex is a **FastAPI + React** server you run at home or on a NAS. Point it at folders of DRM-free games and ROMs, let it identify them against IGDB / Steam / GOG / RAWG, then give the household a modern browser app to **browse, download, play in the browser where a system supports it, and hang out** — with a desktop companion for PC titles and a ten-foot mode for the TV.

Every screenshot and clip on this page is **live UI** from a stock install seeded with five legal free ROMs: what you see is what ships.

<table>
<tr><td>🏷️ <b>Release</b></td><td><a href="CHANGELOG.md">1.1.8</a> · <a href="VERSION"><code>VERSION</code></a></td><td>🌐 <b>Default URL</b></td><td><code>http://localhost:5006</code></td></tr>
<tr><td>🐳 <b>Containers</b></td><td><code>oneirodex-app</code> · <code>oneirodex-db</code> · optional <code>oneirodex-livekit</code></td><td>🖼️ <b>Image</b></td><td>local Compose build <code>oneirodex:1.1.8</code></td></tr>
<tr><td>📦 <b>Package</b></td><td><code>oneirodex/</code> — Flask under ASGI (uvicorn)</td><td>🧩 <b>Front ends</b></td><td>member · admin · ops-glance SPAs, Tauri desktop, Quest PWA</td></tr>
<tr><td>🏷️ <b>Release</b></td><td><a href="CHANGELOG.md">1.1.9</a> · <a href="VERSION"><code>VERSION</code></a></td><td>🌐 <b>Default URL</b></td><td><code>http://localhost:5006</code></td></tr>
<tr><td>🐳 <b>Containers</b></td><td><code>oneirodex-app</code> · <code>oneirodex-db</code> · optional <code>oneirodex-livekit</code></td><td>🖼️ <b>Image</b></td><td>local Compose build <code>oneirodex:1.1.9</code></td></tr>
<tr><td>📦 <b>Package</b></td><td><code>oneirodex/</code> — FastAPI under uvicorn</td><td>🧩 <b>Front ends</b></td><td>member · admin · ops-glance SPAs, Tauri desktop, Quest PWA</td></tr>
</table>

> **Legal.** Use Oneirodex only with software you are authorised to share. It ships **no** Discord bots, pirate marketplaces or store download queues. **Authentik / OIDC is optional** — local username and password is the default for a home install.
Expand Down Expand Up @@ -364,12 +364,12 @@ Full lists: [`.env.example`](.env.example) · [`.env.docker.example`](.env.docke
```text
┌─────────────────┐ ┌──────────────────┐ ┌────────────┐ ┌────────────┐
│ Member SPA │ │ Admin SPA │ │ Desktop │ │ Quest PWA │
│ (React) │ │ (React + Jinja) │ │ companion │ │ (/vr) │
│ (React) │ │ (React) │ │ companion │ │ (/vr) │
└────────┬────────┘ └────────┬─────────┘ └─────┬──────┘ └─────┬──────┘
└─────────────┬───────┴───────────────────┴────────────────┘
▼
┌─────────────────┐
│ Flask on ASGI │ ← oneirodex/ :5006
│ FastAPI / ASGI │ ← oneirodex/ :5006
│ APIs · auth │
└───────┬─────────┘
┌───────────┼────────────┐
Expand All @@ -391,13 +391,18 @@ Full lists: [`.env.example`](.env.example) · [`.env.docker.example`](.env.docke

| Area | What Oneirodex uses |
|---|---|
| Language / server | **Python 3.12**, **Flask** under ASGI (uvicorn), SQLAlchemy + Alembic migrations |
| Front ends | **React 19 + Vite + TypeScript** single-page apps (member, admin, ops-glance); admin also uses Jinja templates |
| Language / server | **Python 3.12**, **FastAPI** (uvicorn), SQLAlchemy + Alembic; Flask compatibility tools are development-only |
| Front ends | **React 19 + Vite + TypeScript** single-page apps (member, admin, ops-glance); no application-authored Jinja templates remain |
| Desktop / VR | **Tauri** desktop companion · Quest PWA |
| Database | **PostgreSQL 17** — the only required service |
| Node.js | Build-time only (the Docker image compiles the SPAs, then ships Python); not a runtime dependency |
| Not used | Next.js, PHP, Django, MySQL/MariaDB |

The admin and member interfaces are React single-page apps. Application-authored
Jinja templates and the WSGI bridge have been retired. FastAPI owns request
dispatch; the runtime requirements exclude Flask, its extensions, and Jinja.
Legacy Flask route modules remain available to development and parity tests.

**Default ports:** `5006` (web app) and `5432` (PostgreSQL, internal to Compose).

**Optional services** — off by default, enabled by env vars or Compose profiles; none are needed for a normal install:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.8
1.1.9
8 changes: 5 additions & 3 deletions alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@
if config.config_file_name is not None:
fileConfig(config.config_file_name)

# Importing the models module registers every table on db.metadata.
from oneirodex import db # noqa: E402
# Importing the framework-independent models module registers every table on
# the shared declarative metadata. Alembic runs in the production image, where
# the legacy Flask compatibility runtime (and its Jinja dependency) is absent.
from oneirodex import models # noqa: E402,F401 (import for side effect)
from oneirodex.models.orm import ModelBase # noqa: E402
from config import Config # noqa: E402

target_metadata = db.metadata
target_metadata = ModelBase.metadata

# Indexes the pre-Alembic raw-DDL pass (oneirodex/updateschema.py) created that
# are NOT re-declared on models.py metadata. The baseline revision builds them
Expand Down
Loading
Loading