This is a web based operating system architected with a React/Vite/TypeScript frontend and a Spring Boot 3 backend. The application simulates a desktop environment in the browser, providing a window manager, Dock, terminal, and games, with an available REST API for creating S3-backed system snapshots.
-
Retro-inspired desktop UI
-
State that survives reloads IndexedDB + localStorage snapshotting; optional server snapshots on disk or S3. Reopen and continue where you left off.
-
Games, the 90s way Included: Snake, Paddle Battle (pong-like), Call Break cards; start/replay screens, score boards, original assets. (Planned: Minefield, Crate Mover, Tile Merge, Space Rocks.)
-
Terminal(s) Fast virtual FS shell (built-in commands). Optional WASM Bash mode (lazy-loaded, offline-bundled).
-
In-OS Browser (sandboxed) Tabs + address bar via
<iframe sandbox>with an allow-list of embeddable sites and a retro βNostalgiNetβ homepage. CSP-blocked sites get a clear βOpen in new tabβ button. (Proxy mode optional later.) -
Backend APIs (Spring Boot 3 + Java 21) Health endpoints; snapshot store/fetch/delete to local disk or AWS S3 (v2 SDK). H2 for local dev; secure defaults for production.
- Architecture
- Repo Structure
- Prerequisites
- Quick Start (Docker All-in-One)
- Dev Workflow (docker-compose)
- Configuration & Env
- Backend API
- Front-End Tech Notes
- WASM & Python
- Theming & Accessibility
- Testing & Quality
- Deployment Options
ββββββββββββββββββββββββββββ
β Browser (SPA, PWA-ish) β React + Vite + TS
β β’ Desktop, Dock, Windowsβ IndexedDB/localStorage
β β’ Apps: Explorer, Games β Optional WASM (Pyodide/Bash)
β β’ Terminal, Settings β
βββββββββββββββ¬βββββββββββββ
β fetch /api/*
βΌ
ββββββββββββββββββββββββββββ
β Spring Boot 3 (Java 21) β REST APIs
β β’ /api/health, /api/ping β H2 for dev
β β’ /api/snap/* β S3 (AWS SDK v2) optional
β Serves SPA static files β
βββββββββββββββ¬βββββββββββββ
β
βΌ
ββββββββββββββββββββ
β Storage β
β β’ /app/data/... β (volume)
β β’ S3 bucket β (opt-in)
ββββββββββββββββββββ
nostalgios/
ββ apps/
β ββ web-os/
β ββ public/
β β ββ wallpapers/ # default.jpg (your default wallpaper)
β β ββ sdk/pyodide/ # local Pyodide bundle (offline)
β β ββ assets/games/... # game sprites, SFX, icons
β ββ src/
β β ββ components/ # Desktop, Window, Dock, MenuBar, etc.
β β ββ apps/ # Explorer, Settings, Help, Browser, Games, Terminal
β β ββ games/ # Snake, PaddleBattle, CallBreak (and more)
β β ββ os/ # boot, vfs, wallpaper utils
β β ββ state/ # Store, types, init (no top-level await)
β β ββ workers/ # py.worker.ts (loads pyodide via importScripts)
β β ββ ui/ # Menu, ModalProvider, shared UI
β β ββ ErrorBoundary.tsx
β β ββ main.tsx # awaits initStore() + bootOS(), mounts UI
β β ββ styles.css # themes (aqua-light default), tokens
β ββ index.html
β ββ vite.config.ts
β ββ package.json
β
ββ server/
β ββ snap/
β ββ src/main/java/com/nostalgi/snap/
β β ββ SnapApplication.java
β β ββ SecurityConfig.java
β β ββ HealthController.java # GET /api/health
β β ββ ApiControllers.java # GET /api/ping, APIs
β β ββ SnapshotService.java # local/S3 storage, path-style support
β β ββ ... repositories/entities if needed
β ββ src/main/resources/
β β ββ application.yaml # prod/dev props (optional)
β β ββ static/ # SPA bundle injected at build
β ββ pom.xml
β
ββ docker/
β ββ entrypoint.sh # fixes volume ownership, then gosu β appuser
β
ββ Dockerfile # all-in-one (build SPA + jar)
ββ docker-compose.yml # dev split: web (5173) + api (8080)
ββ README.md
- Docker (Desktop) or Docker Engine
- Optional: Node 18+/20+, Java 21 if you want to run parts without Docker
Build SPA β bundle into Spring Boot β run one container.
# From repo root
docker build --no-cache -t nostalgios:all-in-one .
docker run --rm -p 8080:8080 -v nostalgios_data:/app/data nostalgios:all-in-one
# Open the SPA served by Spring:
http://localhost:8080/Notes
-
The container runs an entrypoint that ensures
/app/datais owned by the app user:- It creates
${SNAP_STORAGE_DIR:-/app/data/snapshots}andchowns/app/data. - Then starts Spring with
-Dsnap.storageDir=....
- It creates
-
Data persists in the named volume
nostalgios_data.
Use Viteβs hot-reload (PORT 5173) + API (PORT 8080).
docker-compose.yml (example):
services:
web:
build:
context: .
dockerfile: Dockerfile.dev.web # or use the main Dockerfile if you prefer
working_dir: /app/apps/web-os
command: npm run dev -- --host 0.0.0.0
ports:
- "5173:5173"
volumes:
- ./apps/web-os:/app/apps/web-os
environment:
- VITE_API_BASE=http://localhost:8080
server:
build:
context: .
dockerfile: Dockerfile.dev.server
working_dir: /app/server/snap
ports:
- "8080:8080"
volumes:
- ./server/snap:/app/server/snap
- nostalgios_data:/app/data
environment:
- SPRING_PROFILES_ACTIVE=dev
- SNAP_STORAGE_DIR=/app/data/snapshots
volumes:
nostalgios_data:You can also drive both services from the single root Dockerfile; many teams keep small
.devDockerfiles for faster inner-loop.
Open:
- Frontend (Vite dev):
http://localhost:5173/ - API:
http://localhost:8080/api/health
- Default theme:
aqua-light(stronger contrast). Change under Settings β Appearance. - Wallpapers: add
apps/web-os/public/wallpapers/default.jpg. Users can upload images; we store them in IndexedDB (and optionally server snapshots). - Safe state init:
initStore()is awaited inmain.tsx.get snapshot()never throws;Desktoprenders a boot splash until ready.
Key properties (via env or application.yaml):
| Property | Default | Description |
|---|---|---|
snap.storageDir |
./data/snapshots |
Local snapshot path (overridden by entrypoint to /app/data/snapshots) |
snap.s3.enabled |
false |
Enable S3 storage |
snap.s3.bucket |
nostalgios-snaps |
S3 bucket |
snap.s3.region |
ap-south-1 |
AWS region |
snap.s3.endpoint |
(empty) | Custom endpoint (e.g. MinIO) |
snap.s3.forcePathStyle |
true |
Path style access |
aws.accessKeyId |
(empty) | S3 creds (if not using default provider chain) |
aws.secretAccessKey |
(empty) | S3 creds |
Security
In dev, Spring Security prints a generated password to logs; the API allows unauthenticated /api/health (and /api/ping if present). Harden for production as needed.
GET /api/healthβ{ "status": "ok" }GET /api/pingβ{ "status": "ok" }(optional)POST /api/snap/{deviceId}β body bytes stored (disk or S3)GET /api/snap/{deviceId}β returns snapshot bytesDELETE /api/snap/{deviceId}β delete snapshot
The SPA primarily uses the browser-local snapshot. Server snapshots are optional long-term persistence or sync.
- React + Vite + TypeScript (modern target,
esnext). - State Store with explicit
initStore()(no top-levelawait). - ErrorBoundary overlays any crash with a βReset & Reloadβ button that clears local storage + IndexedDB (one origin only).
- Windowing: draggable panels, right-docked Dock, z-index stacking, snap gap.
- Keyboard:
Esccloses menus, arrow keys in games, space/pause where relevant.
-
Pyodide: bundled locally under
apps/web-os/public/sdk/pyodide/so it works offline (no CDN). Inpy.worker.ts, Pyodide is loaded viaimportScripts(BASE + 'sdk/pyodide/pyodide.js')withindexURLpointing to the same folderβnot bundled by Vite. -
Bash (WASM): optional. When enabled, the wasm asset is lazy-loaded from
public/wasm/bash/. If it fails, terminal falls back to the built-in VFS shell.
-
Light/Dark/High-Contrast with tokens:
- Light: higher contrast for readability (
--text,--border,--shadowtuned). - Dark (Aqua-inspired): glow accents, subtle transparencyβoriginal palettes (no traffic-lights).
- Light: higher contrast for readability (
-
Focus rings, large hit targets on menus, color-blind safe accent defaults.
-
Users can upload wallpapers and pick fit (fill/fit/tile/center).
-
TypeScript strictness where practical.
-
Lint/format recommended:
- Frontend:
npm run lint,npm run format(add ESLint/Prettier as desired). - Backend:
mvn -q -DskipTests=false test(add tests over time).
- Frontend:
-
CI suggestion (GitHub Actions):
- Cache Maven & npm.
- Build SPA, inject into jar, run
mvn -DskipTests packagefor release. - Build Docker image per tag.
Already covered in βQuick Startβ. Push to any registry and run.
- Build from Docker. Expose port
8080. - Set
SNAP_STORAGE_DIR=/app/data/snapshots, mount a persistent volume to/app/data. - For S3: set
snap.s3.*env vars andsnap.s3.enabled=true.
- One Deployment + Service; mount a PVC at
/app/data. - Optional: HPA on CPU/RAM; externalized S3 credentials via Secret.