diff --git a/.env.example b/.env.example index 98cdc62..9083283 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,5 @@ -# Flashpoint Web - Environment Template -# Copy to .env and fill in required values. -# -# Production: docker compose up -d -# Development: docker compose -f docker-compose.dev.yml up -d --build +# Dotcom Arcade - Environment Template +# Copy to .env and fill in required values, then: docker compose up -d --build # --------------------------------------------------------------------------- # Required @@ -11,10 +8,15 @@ FLASHPOINT_HOST_PATH=/path/to/Flashpoint JWT_SECRET=replace-with-a-secure-random-string # --------------------------------------------------------------------------- -# Container Image (production only) +# Container Images # --------------------------------------------------------------------------- -# Override image tag (image from Docker Hub: darkraise/flashpoint-web) +# Built from source by default. To pull published images instead, set the +# registry path they live under (ending in /), then: +# docker compose pull && docker compose up -d +# IMAGE_PREFIX=registry.example.com/owner/ # IMAGE_TAG=latest +# "Latest release" API URL to check for updates (GitHub or Gitea); off when unset +# APP_UPDATE_URL=https://git.example.com/api/v1/repos/owner/dotcom-arcade/releases/latest # --------------------------------------------------------------------------- # Port (host mapping) @@ -43,7 +45,7 @@ LOG_LEVEL=info OTEL_ENABLED=false OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # OTEL_API_KEY=your-api-key-here -OTEL_SERVICE_NAME=flashpoint-web +OTEL_SERVICE_NAME=dotcom-arcade OTEL_TRACES_ENABLED=true OTEL_METRICS_ENABLED=true # OTEL_METRICS_EXPORT_INTERVAL=60000 @@ -103,15 +105,16 @@ ENABLE_CACHE_PREWARM=true # --------------------------------------------------------------------------- # Application # --------------------------------------------------------------------------- -# HOME_RECENT_HOURS=24 # ENABLE_CGI=false -# Streamed play for games that can't run in a browser, such as Java applets. -# Off unless both are set. See docs/09-deployment/environment-variables.md. -# STREAMING_API_URL=http://flashpoint-session-manager:8080 +# Streamed play for games that can't run in a browser (Java, Shockwave, the +# original Flash Player, Windows games). Set a token (e.g. `openssl rand -hex 32`) +# and enable the profile. See docs/10-features/13-streaming.md. +# COMPOSE_PROFILES=streaming # STREAMING_API_TOKEN= +# MAX_SESSIONS=4 # Sign in users an authenticating reverse proxy (e.g. Authentik forward auth) # names in a header. Off unless TRUSTED_HEADER_USERNAME and TRUSTED_PROXIES are -# both set. See docs/09-deployment/environment-variables.md. +# both set. See docs/10-features/14-proxy-sign-in.md. # TRUSTED_HEADER_USERNAME=X-authentik-username # TRUSTED_HEADER_EMAIL=X-authentik-email # TRUSTED_PROXIES=192.0.2.10 diff --git a/.gitea/workflows/images.yml b/.gitea/workflows/images.yml new file mode 100644 index 0000000..f2e36bd --- /dev/null +++ b/.gitea/workflows/images.yml @@ -0,0 +1,65 @@ +# Builds the app, session manager and streamer images and pushes them to this +# Gitea instance's container registry, under the repository's owner. +# +# Needs repository secrets REGISTRY_USERNAME and REGISTRY_TOKEN: a user, and an +# access token of theirs with package write access. +name: Images + +on: + push: + branches: [main] + tags: ['v*.*.*'] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - image: dotcom-arcade + context: . + - image: dotcom-arcade-session-manager + context: streaming/session-manager + - image: dotcom-arcade-streamer + context: streaming/streamer + steps: + - uses: actions/checkout@v4 + + # The registry lives on the same host as this Gitea instance + - name: Locate the registry + id: registry + run: | + host=${GITHUB_SERVER_URL#*://} + echo "host=${host%/}" >> "$GITHUB_OUTPUT" + echo "owner=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_OUTPUT" + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ${{ steps.registry.outputs.host }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ steps.registry.outputs.host }}/${{ steps.registry.outputs.owner }}/${{ matrix.image }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=ref,event=branch + + - uses: docker/build-push-action@v6 + with: + context: ${{ matrix.context }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64 + build-args: | + VERSION=${{ steps.meta.outputs.version }} + provenance: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2109efa..e539373 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ blank_issues_enabled: true contact_links: - - name: Ask a Question - url: https://github.com/darkraise/flashpoint-web/discussions - about: Use Discussions for questions, help, and general conversation - name: Flashpoint Archive url: https://github.com/FlashpointProject about: For issues with Flashpoint itself (not this web interface) diff --git a/.github/workflows/README.md b/.github/workflows/README.md deleted file mode 100644 index 4678e9e..0000000 --- a/.github/workflows/README.md +++ /dev/null @@ -1,204 +0,0 @@ -# GitHub Actions Workflows - -This directory contains automated workflows for the Flashpoint Web project. - -## Workflows - -### 1. Docker Build and Push (`docker-build-push.yml`) - -Builds and pushes the single Docker image to the container registry. - -#### Triggers - -- **Version tags** (`v*.*.*`): Builds and pushes the image with version tags and `latest` tag -- **Manual dispatch**: Can be triggered manually from GitHub Actions tab - -#### Image Built - -- `flashpoint-web` - Single image serving both the REST API (including game - content) and the React web UI - -#### Container Registry - -The workflow pushes images to **Docker Hub**. - -#### Setup Instructions - -##### Docker Hub (Required) - -1. **Create Docker Hub account** at https://hub.docker.com - -2. **Create access token**: - - Go to Account Settings → Security → Access Tokens - - Click "New Access Token" - - Name: "GitHub Actions" - - Permissions: Read & Write - - Copy the token (you won't see it again!) - -3. **Add GitHub Secrets**: - - Go to your repository → Settings → Secrets and variables → Actions - - Click "New repository secret" - - Add two secrets: - - `DOCKERHUB_USERNAME`: Your Docker Hub username - - `DOCKERHUB_TOKEN`: The access token from step 2 - -4. **Image will be pushed to**: - ``` - docker.io/darkraise/flashpoint-web:latest - ``` - -#### Image Tags - -The workflow automatically creates multiple tags when you push a version tag: - -| Tag Type | Example | Description | -|----------|---------|-------------| -| `latest` | `latest` | Always created for every version tag | -| Full version | `1.2.3` | Git tag `v1.2.3` → `1.2.3` | -| Major.Minor | `1.2` | Git tag `v1.2.3` → `1.2` | -| Major | `1` | Git tag `v1.2.3` → `1` | - -#### Creating a Release - -To create a versioned release: - -```bash -# Ensure you're on master branch -git checkout master - -# Tag the commit -git tag v1.0.0 - -# Push the tag (this triggers the workflow) -git push origin v1.0.0 -``` - -This will create images tagged as: -- `1.0.0` -- `1.0` -- `1` -- `latest` - -#### Platform Support - -Images are built for: -- `linux/amd64` (x86_64) - Standard servers and desktops - -**Note:** ARM64 support has been disabled to significantly reduce build times. If you need ARM64 images, you can build them locally or enable multi-platform builds by adding `linux/arm64` to the platforms in the workflow file. - -#### Build Caching - -The workflow uses GitHub Actions cache to speed up builds: -- Layer cache stored between runs -- Significantly faster builds after the first run -- Cache automatically invalidated when dependencies change - -#### Usage Examples - -##### Pull and Run the Image - -```bash -# Pull the latest image -docker pull darkraise/flashpoint-web:latest - -# Or use a specific version -docker pull darkraise/flashpoint-web:1.0.0 -``` - -##### Use in Docker Compose - -Update your `docker-compose.yml` to use the pre-built image: - -```yaml -services: - flashpoint-web: - image: darkraise/flashpoint-web:latest - # Remove build section - ports: - - "80:3100" - # ... rest of config -``` - -Then simply run: -```bash -docker compose pull # Pull the latest image -docker compose up -d # Start the service -``` - -#### Making Images Public - -By default, Docker Hub images are private. To make them public: - -1. Go to https://hub.docker.com/repositories -2. Click on the repository (e.g., `flashpoint-web`) -3. Click "Settings" -4. Change visibility to "Public" - -#### Troubleshooting - -##### Build Fails - -Check the Actions tab for detailed error logs: -``` -https://github.com///actions -``` - -##### Docker Hub Not Pushing - -Verify secrets are set correctly: -- Repository → Settings → Secrets and variables → Actions -- Check `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` exist - -##### Image Not Found - -For private images, authenticate first: -```bash -docker login docker.io -``` - -#### Performance - -Typical build times (optimized for amd64 only): -- First build: 3-5 minutes -- Subsequent builds (with cache): 1-2 minutes -- Frontend and backend build stages run within a single image build - -**Optimizations applied:** -- Single platform (amd64) instead of multi-platform -- npm ci with `--prefer-offline` and `--no-audit` flags -- Disabled provenance and SBOM generation -- Latest BuildKit image -- GitHub Actions cache for Docker layers - -#### Security - -- Secrets are never exposed in logs -- Images are scanned for vulnerabilities (optional - can add) -- Multi-stage builds minimize attack surface -- Non-root users in all containers -- Read-only root filesystems where possible - ---- - -### 2. Performance Budget (`performance-budget.yml`) - -Frontend performance monitoring workflow. - -See the workflow file for details. - ---- - -## Adding New Workflows - -To add a new workflow: - -1. Create a new YAML file in `.github/workflows/` -2. Use the GitHub Actions syntax -3. Test locally with [act](https://github.com/nektos/act) (optional) -4. Push to trigger the workflow - -## References - -- [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [Docker Build Push Action](https://github.com/docker/build-push-action) -- [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml deleted file mode 100644 index 2dc3d92..0000000 --- a/.github/workflows/docker-build-push.yml +++ /dev/null @@ -1,144 +0,0 @@ -name: Build and Push Docker Images - -on: - push: - tags: - - 'v*.*.*' - workflow_dispatch: - -env: - REGISTRY_DOCKERHUB: docker.io - -jobs: - build-and-push: - runs-on: ubuntu-latest - timeout-minutes: 30 - - permissions: - contents: read - - outputs: - version: ${{ steps.meta.outputs.version }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver-opts: | - image=moby/buildkit:latest - network=host - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY_DOCKERHUB }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata (tags, labels) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY_DOCKERHUB }}/${{ secrets.DOCKERHUB_USERNAME }}/flashpoint-web - tags: | - # Tag with full version (v1.0.0 -> 1.0.0) - type=semver,pattern={{version}} - # Tag with major.minor (v1.0.0 -> 1.0) - type=semver,pattern={{major}}.{{minor}} - # Tag with major version (v1.0.0 -> 1) - type=semver,pattern={{major}} - # Latest tag - type=raw,value=latest - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha,scope=flashpoint-web - cache-to: type=gha,scope=flashpoint-web,mode=max - platforms: linux/amd64 - build-args: | - BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} - VCS_REF=${{ github.sha }} - VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} - provenance: false - sbom: false - - summary: - needs: build-and-push - runs-on: ubuntu-latest - if: always() - steps: - - name: Build Summary - run: | - echo "## Docker Build Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Image Built:" >> $GITHUB_STEP_SUMMARY - echo "- \`flashpoint-web\` (API + UI)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Registry:" >> $GITHUB_STEP_SUMMARY - echo "- Docker Hub: \`${{ secrets.DOCKERHUB_USERNAME }}/flashpoint-web\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Pull Command:" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY - echo "docker pull ${{ secrets.DOCKERHUB_USERNAME }}/flashpoint-web:latest" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - - create-release: - needs: build-and-push - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') - - permissions: - contents: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get version from tag - id: version - run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - - - name: Generate release notes - id: release-notes - run: | - # Get the previous tag - PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") - - echo "## Docker Image" >> release_notes.md - echo "" >> release_notes.md - echo "Pull the image from Docker Hub:" >> release_notes.md - echo "" >> release_notes.md - echo "\`\`\`bash" >> release_notes.md - echo "docker pull ${{ secrets.DOCKERHUB_USERNAME }}/flashpoint-web:${{ steps.version.outputs.version }}" >> release_notes.md - echo "\`\`\`" >> release_notes.md - echo "" >> release_notes.md - - if [ -n "$PREV_TAG" ]; then - echo "## Changes since $PREV_TAG" >> release_notes.md - echo "" >> release_notes.md - git log --pretty=format:"- %s (%h)" $PREV_TAG..HEAD >> release_notes.md - echo "" >> release_notes.md - fi - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.version.outputs.version }} - name: Release ${{ steps.version.outputs.version }} - body_path: release_notes.md - draft: false - prerelease: ${{ contains(steps.version.outputs.version, '-') }} - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CLAUDE.md b/CLAUDE.md index e3df29f..7232ae0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,8 +2,8 @@ ## Project Overview -Flashpoint Web: self-hosted web app for browsing/playing Flashpoint Archive -games. +Dotcom Arcade: a self-hosted, Flash-era game portal for Flashpoint Ultimate. +Built on darkraise's flashpoint-web. - **Backend** (Express/TypeScript, port 3100): REST API + game content via `/game-proxy/*`, `/game-zip/*` @@ -11,6 +11,8 @@ games. Tailwind, Ruffle - **Databases**: `flashpoint.sqlite` (read-only, never write) + `user.db` (app data) +- **Streaming** (`streaming/`): a session manager and per-player streamer + containers for games that can't run in a browser; see `streaming/README.md` ## Commands @@ -1191,13 +1193,8 @@ Before submitting code, verify: ## Environment ```bash -FLASHPOINT_PATH=D:/Flashpoint # Required - path to Flashpoint installation +FLASHPOINT_PATH=/path/to/flashpoint # Required - path to Flashpoint installation JWT_SECRET=your-secret # Required - random string for JWT signing DOMAIN=http://localhost:5173 # Optional - frontend URL for CORS LOG_LEVEL=info # Optional - debug, info, warn, error ``` - -## Source References - -- Flashpoint Launcher: `D:\Repositories\Community\launcher` -- Flashpoint Game Server: `D:\Repositories\Community\FlashpointGameServer` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cecfaf3..3b0045d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ -# Contributing to Flashpoint Web +# Contributing to Dotcom Arcade Thanks for your interest in contributing! We welcome bug fixes, features, documentation improvements, and questions. -## What is Flashpoint Web? +## What is Dotcom Arcade? A self-hosted web application for browsing and playing games from [Flashpoint Archive](https://flashpointarchive.org/). @@ -57,17 +57,14 @@ docs/ # Documentation cp .env.example .env # Edit .env - set FLASHPOINT_HOST_PATH and JWT_SECRET (required) -# Build and start (development) -docker compose -f docker-compose.dev.yml up -d --build +# Build and start +docker compose up -d --build # View logs -docker compose -f docker-compose.dev.yml logs -f - -# Rebuild specific service -docker compose -f docker-compose.dev.yml up -d --build backend +docker compose logs -f ``` -Frontend runs on `http://localhost:80`, backend API on `http://localhost:3100`. +The app (UI and API) runs on `http://localhost:80`. ### Option 2: Manual Setup (Recommended for Development) diff --git a/Dockerfile b/Dockerfile index 9ab1cf2..6736ad0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -# Production Dockerfile for Flashpoint Web (single image: API + UI) +# Production Dockerfile for Dotcom Arcade (single image: API + UI) # # The backend serves both the REST API / game content and the built React UI, # so this image runs a single Node process. Build from the repo root: -# docker build -t flashpoint-web . +# docker build -t dotcom-arcade . # ============================================================================= # Stage 1: Frontend builder - Build the React app @@ -54,10 +54,9 @@ FROM node:20.18.2-alpine # Metadata labels ARG VERSION=dev -LABEL org.opencontainers.image.title="Flashpoint Web" -LABEL org.opencontainers.image.description="Self-hosted web app for Flashpoint Archive (API + UI)" +LABEL org.opencontainers.image.title="Dotcom Arcade" +LABEL org.opencontainers.image.description="Self-hosted Flash-era game portal for Flashpoint Ultimate (API + UI)" LABEL org.opencontainers.image.version="${VERSION}" -LABEL org.opencontainers.image.source="https://github.com/darkraise/flashpoint-web" # An ARG does not survive into the running container; the server reads this to # decide whether it is a released build. diff --git a/README.md b/README.md index 8109b79..158fad2 100644 --- a/README.md +++ b/README.md @@ -1,238 +1,207 @@ -# Flashpoint Web +# Dotcom Arcade -
+**Your own Flash-era game portal, powered by +[Flashpoint Ultimate](https://flashpointarchive.org/).** -**A modern web interface for the Flashpoint Archive** +Dotcom Arcade serves a [Flashpoint Ultimate](https://flashpointarchive.org/) +collection as a website you'd have played at a friend's house or in the computer +lab, with modern UX. It aims to play as much of the archive as possible in any +browser: Flash and HTML5 run in the page, and Java, Shockwave and the original +Flash Player stream from your server. -Browse and play 200,000+ preserved Flash games and animations directly in your -browser. +![The Dotcom Arcade homepage](docs/images/home.png) - -

- Node.js - TypeScript - React - Express - Vite - Tailwind CSS - SQLite - Docker -

- - -

- License - Node.js 20+ - PRs Welcome -

- -
- ---- - -> [!IMPORTANT] +> [!NOTE] > -> **Already running 1.0.31 or earlier? Updating is a breaking change.** The -> separate `flashpoint-backend` and `flashpoint-frontend` containers were -> replaced in 1.0.32 by a single `darkraise/flashpoint-web` image, and the old -> images are no longer published. Your compose file and `.env` both need changes -> — see **[Upgrading](#upgrading)** before you pull. +> Dotcom Arcade is an unofficial community project, not affiliated with +> Flashpoint Archive. It's built on +> [flashpoint-web](https://github.com/darkraise/flashpoint-web) by darkraise. -## What is Flashpoint Web? - -Flashpoint Web is a self-hosted web application for accessing your local -[Flashpoint Archive](https://flashpointarchive.org/) collection. It provides a -modern, browser-based alternative to the Flashpoint Launcher with multi-user -support, play tracking, and an intuitive interface. - -### Key Features - -- **Browse & Search** - Fast search across 200,000+ games with advanced - filtering by platform, developer, tags, and more -- **Downloaded View** - A dedicated page and filter for games whose data is - already on disk, so a curated collection is one click away -- **Play in Browser** - Flash games via [Ruffle](https://ruffle.rs/) emulator, - HTML5 games natively -- **Multi-User Support** - Individual accounts with role-based permissions - (admin, moderator, user, guest) -- **Play Tracking** - Track playtime, completion status, and view play - statistics -- **Playlists & Favorites** - Organize your game collection with custom - playlists -- **22 Theme Palettes** - Customizable dark/light modes with multiple color - schemes -- **Responsive Design** - Works on desktop and mobile devices -- **Database Hot-Reload** - Automatically syncs when Flashpoint Launcher updates - metadata - -## Demo: [flash.quangtc.dev](https://flash.quangtc.dev) -Register new account and try your favorite flash games - -## Upgrading - -Routine updates need nothing beyond -`docker compose pull && docker compose up -d`. +## Before you start: hosting requirements > [!WARNING] > -> **Coming from 1.0.31 or earlier, that is not enough.** 1.0.32 replaced the -> two-container deployment with a single image, so an existing setup needs: -> -> - a new `docker-compose.yml` with one `flashpoint-web` service, mapping -> `WEB_PORT` to container port `3100` -> - `API_PORT`, `BACKEND_HOST`, and `BACKEND_PORT` removed from `.env` — they no -> longer do anything -> - a reverse proxy pointing at one upstream instead of two; any rule sending -> `/api` to port 3100 will break -> - the Flashpoint mount switched to read-write, or the Downloaded page stays -> empty -> -> Accounts, playlists, and play history are preserved. +> **Dotcom Arcade needs a lot of disk, and streaming needs a lot of CPU.** Check +> these before you download anything. -Full migration steps, every environment variable added, removed, or changed, and -per-version release notes are in the -**[Upgrade Guide](docs/09-deployment/upgrade-guide.md)**. +| Resource | Needed | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Storage** | **More than 2.3 TB** for Flashpoint Ultimate 14, and growing with each release. 4.6 TB if you keep the downloaded archives to seed them. A spare desktop drive won't do. | +| **Time** | A day or more to download and extract, depending on your connection and disks. | +| **Server** | Linux with Docker. 2 CPU cores and 4 GB of RAM serve the site and in-browser games to a household. | +| **Streaming** | Optional, and heavy: every player gets their own container running the game and encoding its video on the CPU. Plan on roughly 1 to 2 CPU cores and 1 GB of RAM per player streaming at once, and a few Mbps of upload each. | +| **HTTPS** | Required for streaming, since browsers only allow its video decoder on secure pages. Recommended anyway. | -## Getting Started +## Features -### Prerequisites +- **Plays almost everything.** + - Flash runs in [Ruffle](https://ruffle.rs/) and HTML5 runs natively. + - Games no browser can run stream from the server into the page, each player + in their own container: Java applets, Shockwave and Director games, and + Flash games that need the real Flash Player. With streaming on, any Flash + game can switch to the original Flash Player. +- **An arcade, not a file browser.** A portal-style layout with genre categories + and glossy tiles, and color schemes defined as data, so new themes are easy to + add. +- **Discovery first.** The homepage offers: + - a Game of the Day; + - a Time Machine of games released in any week, with a date picker; + - shelves from Flashpoint's curated playlists; + - your recently played games. -- [Docker](https://docs.docker.com/get-docker/) and - [Docker Compose](https://docs.docker.com/compose/install/) -- [Flashpoint Archive](https://flashpointarchive.org/) installed locally + Browse lists default to a shuffle, not A to Z. -### 1. Create a project directory +- **One page per game.** It has a click-to-play stage, details and tags, and + related games ranked by shared tags, series and developer. +- **Accounts and saves.** + - Multi-user, with roles. + - Favorites, playlists and play tracking. + - Game saves follow you between browsers, with the last 10 kept as backups. +- **Sign in through your proxy.** Authentik, Authelia, oauth2-proxy and similar + forward-auth proxies can sign users in. +- **Mature content filter.** Adult and extreme content is hidden unless a user + opts in. -```bash -mkdir flashpoint-web && cd flashpoint-web +

+ A game page + Playing a game in the page +

+ +## Getting started + +Check the [hosting requirements](#before-you-start-hosting-requirements) first. + +### 1. Get Flashpoint Ultimate + +Download two files from Flashpoint's +[downloads page](https://flashpointarchive.org/downloads): + +- the **Flashpoint Ultimate** `.torrent` file; +- **Flashpoint Infinity for Linux** (`.7z`), whose software streaming needs. + +[`scripts/setup-flashpoint.sh`](scripts/setup-flashpoint.sh) then does the rest: + +- It checks that you have the disk space, and stops before downloading anything + if you don't. +- It downloads the torrent with `aria2c`. +- It extracts each archive in turn and deletes it afterwards, so the disk never + needs room for both. +- It pulls the database, settings and playlists out of `Base_1.zip`, which + otherwise only holds Windows software. + +Re-run it to resume after an interruption. + +```sh +sudo apt install aria2 7zip tmux +tmux new -s flashpoint +scripts/setup-flashpoint.sh --torrent FlashpointUltimate14.torrent \ + --infinity fp14.0.3_lin_20251201.7z --seed-ratio 1.0 /srv/flashpoint ``` -### 2. Create `docker-compose.yml` +#### Please seed - -```yaml -services: - flashpoint-web: - image: darkraise/flashpoint-web:${IMAGE_TAG:-latest} - container_name: flashpoint-web - restart: unless-stopped - ports: - - "${WEB_PORT:-80}:3100" - volumes: - - ${FLASHPOINT_HOST_PATH:?FLASHPOINT_HOST_PATH is required}:/data/flashpoint - - ${DATA_PATH:-./data}:/app/data - - ${LOGS_PATH:-./logs}:/app/logs - environment: - - PUID=${PUID:-1000} - - PGID=${PGID:-1000} - - NODE_ENV=production - - TZ=${TZ:-UTC} - - DOMAIN=${DOMAIN:-http://localhost:${WEB_PORT:-80}} - - LOG_LEVEL=${LOG_LEVEL:-info} - - JWT_SECRET=${JWT_SECRET:?JWT_SECRET is required} - - ENABLE_LOCAL_DB_COPY=${ENABLE_LOCAL_DB_COPY:-false} - - SQLITE_CACHE_SIZE=${SQLITE_CACHE_SIZE:--64000} - - SQLITE_MMAP_SIZE=${SQLITE_MMAP_SIZE:-268435456} - - ENABLE_CACHE_PREWARM=${ENABLE_CACHE_PREWARM:-true} - healthcheck: - test: ["CMD", "curl", "--fail", "http://localhost:3100/health"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s +Flashpoint Ultimate is shared over BitTorrent, and stays available only as long +as people seed it. If you can, give back at least what you downloaded: + +- **Seed before extracting.** `--seed-ratio 1.0` keeps seeding after the + download until you've uploaded as much as you downloaded. `--seed-hours 24` + seeds for a day. With both, seeding stops at whichever comes first. This needs + no extra disk space, but extraction waits until seeding stops. +- **Seed for the long term.** `--keep-archives` keeps the archives after + extracting (about 4.6 TB in total), so a torrent client such as qBittorrent or + Transmission can go on seeding the downloads directory. +- **Let peers reach you.** The download listens on ports 6881-6999, TCP and UDP. + Forwarding them on your router and allowing them through your firewall lets + more people download from you. + +### 2. Configure + +```sh +cp .env.example .env ``` -The Flashpoint directory is mounted read-write: the app records which games are -downloaded in `flashpoint.sqlite` and saves downloaded game data under -`Data/Games`. Mount it read-only and browsing still works, but downloads and the -Downloaded page do not. +Set at least these: -### 3. Create `.env` - -Two values are required. This is a complete, working `.env`: - -```env -FLASHPOINT_HOST_PATH=/srv/Flashpoint -JWT_SECRET=paste-the-output-of-openssl-rand-hex-32-here +```sh +FLASHPOINT_HOST_PATH=/srv/flashpoint +JWT_SECRET= +PUID=1000 # your user, from `id -u` and `id -g` +PGID=1000 ``` -Generate the secret with `openssl rand -hex 32`. Everything else has a working -default; add only what you need: +### 3. Start -```env -# WEB_PORT=8080 # Host port for the app (UI + API) (default: 80) -# PUID=1000 # Host user ID (Linux — run 'id -u') -# PGID=1000 # Host group ID (Linux — run 'id -g') -# LOG_LEVEL=info # debug, info, warn, error -# TZ=UTC # Timezone -# DATA_PATH=./data # Persistent app data -# LOGS_PATH=./logs # Log files - -# Extra allowed origins, comma-separated. Not needed for a normal install: -# same-origin requests are always allowed. Set this only when the UI is served -# from a different origin, or behind a proxy that rewrites the Host header. -# DOMAIN=http://192.168.0.10,https://flashpoint.example.com - -# Database performance (network storage / large collections) -# ENABLE_LOCAL_DB_COPY=false # Copy flashpoint.sqlite to local storage -# SQLITE_CACHE_SIZE=-64000 # SQLite cache in KB (64MB default) -# SQLITE_MMAP_SIZE=268435456 # Memory-mapped I/O in bytes (256MB default) -# ENABLE_CACHE_PREWARM=true # Pre-warm common queries on startup +```sh +docker compose up -d --build ``` -### 4. Start it +Open `http:///` and create the admin account. On first start the app +indexes the collection, which takes a few minutes on a full Ultimate install. +Under **Settings → App** you can set the site name and tagline. -```bash -docker compose up -d -``` +### 4. Optional: streaming -Open **http://localhost** in your browser. The first user to register -automatically becomes the **admin**. +Streaming plays the games browsers can't. To turn it on: -A single container serves both the UI and the API on one port — `WEB_PORT` on -the host, 3100 inside the container. +1. Run the setup script with `--infinity`. +2. Add to `.env`: -### Common Commands + ```sh + COMPOSE_PROFILES=streaming + STREAMING_API_TOKEN= + ``` -```bash -docker compose up -d # Start -docker compose down # Stop -docker compose logs -f # Follow logs -docker compose restart # Restart -docker compose pull && docker compose up -d # Update to the latest image +3. Rebuild with `docker compose up -d --build`. +4. Serve the site over HTTPS. The app proxies streams itself, so your reverse + proxy only needs to pass WebSocket upgrades and allow long-lived connections. + See the [streaming guide](docs/10-features/13-streaming.md). + +The session manager starts containers through the Docker socket, which amounts +to root on the host. It publishes no ports: only the app talks to it. + +### Signing in through a reverse proxy + +If your site sits behind Authentik, Authelia or another forward-auth proxy, the +app can trust the username it passes along. See +[proxy sign-in](docs/10-features/14-proxy-sign-in.md). + +## Updating + +```sh +git pull +docker compose up -d --build ``` ## Documentation -For architecture details, API reference, development setup, contributing -guidelines, and more, visit the -**[Wiki](https://github.com/darkraise/flashpoint-web/wiki)**. +The [docs](docs/README.md) cover features, deployment, configuration, the API +and development. Useful starting points: -## Related Projects +- [Environment variables](docs/09-deployment/environment-variables.md) +- [Discovery portal](docs/10-features/12-discovery-portal.md) +- [Streaming](docs/10-features/13-streaming.md) and + [streaming internals](streaming/README.md) +- [Development setup](docs/08-development/setup-guide.md) -- [Flashpoint Archive](https://flashpointarchive.org/) - The game preservation - project -- [Flashpoint Launcher](https://github.com/FlashpointProject/launcher) - - Official desktop launcher -- [Ruffle](https://ruffle.rs/) - Flash Player emulator +## Credits + +- [Flashpoint Archive](https://flashpointarchive.org/), for preserving web games + and animations, and for the curated playlists the homepage draws on. +- [flashpoint-web](https://github.com/darkraise/flashpoint-web) by darkraise, + which this project is built on. +- [Ruffle](https://ruffle.rs/), for running Flash in modern browsers. +- [Selkies](https://github.com/selkies-project/selkies), which streams the + games. + +## Legal + +Flashpoint Archive is a preservation project whose legal standing has never been +settled. It removes games when their rights holders ask. Dotcom Arcade contains +no games and links to no game downloads. You get Flashpoint from Flashpoint's +own site, and you are responsible for complying with copyright law where you +live. Rights holders who want a game removed should contact +[Flashpoint Archive](https://flashpointarchive.org/). ## License -This project is licensed under the GNU General Public License v3.0 - see the -[LICENSE](LICENSE) file for details. - -## Acknowledgments - -- **Flashpoint Team** - For preserving web gaming history -- **Ruffle Contributors** - For making Flash playable in modern browsers -- **Community** - For testing, feedback, and contributions - ---- - -
- -**[Wiki](https://github.com/darkraise/flashpoint-web/wiki)** | -**[Report Bug](https://github.com/darkraise/flashpoint-web/issues)** | -**[Request Feature](https://github.com/darkraise/flashpoint-web/issues)** - -
+GPL-3.0, like flashpoint-web. See [LICENSE](LICENSE). diff --git a/backend/.env.example b/backend/.env.example index de0e0e3..6e79ec5 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -22,7 +22,6 @@ BCRYPT_SALT_ROUNDS=10 # ENABLE_CGI=false # Misc -HOME_RECENT_HOURS=24 # --------------------------------------------------------------------------- # Database Performance Optimization (for network storage) diff --git a/backend/package.json b/backend/package.json index d23b578..4d57c33 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,5 +1,5 @@ { - "name": "flashpoint-web-backend", + "name": "dotcom-arcade-backend", "version": "1.0.0", "description": "Backend API for Flashpoint Archive web application", "main": "dist/server.js", diff --git a/backend/src/config.ts b/backend/src/config.ts index 65726c2..b3fc24e 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -167,12 +167,12 @@ export const config = { bcryptSaltRounds: parseInt(process.env.BCRYPT_SALT_ROUNDS || '', 10) || 10, - homeRecentHours: parseInt(process.env.HOME_RECENT_HOURS || '', 10) || 24, - // Session manager that streams games which can't run in a browser, such as // Java applets (flashpoint-ultimate-server). Streaming is off unless both are set. streamingApiUrl: process.env.STREAMING_API_URL || null, streamingApiToken: process.env.STREAMING_API_TOKEN || null, + // Where /stream/ is proxied to: the session manager's stream port + streamingStreamUrl: process.env.STREAMING_STREAM_URL || null, // Sign users in from a header set by an authenticating reverse proxy, such as // Authentik forward auth. Off unless the header name and the proxy addresses @@ -208,6 +208,8 @@ export const config = { // marks the build as unreleased. `||` is deliberate: a whitespace-only value // must collapse to null, which `??` would not do. appVersion: process.env.APP_VERSION?.trim() || null, + // A GitHub- or Gitea-style "latest release" API URL; update checks are off without it + appUpdateUrl: process.env.APP_UPDATE_URL?.trim() || null, } as const; /** Resolves external image CDN URLs from Flashpoint preferences, with hardcoded fallbacks. */ diff --git a/backend/src/migrations/011_default_site_name.sql b/backend/src/migrations/011_default_site_name.sql new file mode 100644 index 0000000..c016518 --- /dev/null +++ b/backend/src/migrations/011_default_site_name.sql @@ -0,0 +1,11 @@ +-- =================================== +-- Migration 011: Default site name +-- =================================== +-- The project is now Dotcom Arcade. Installs still on the old default take the +-- new one; a name an admin chose is left alone. + +UPDATE system_settings +SET + default_value = 'Dotcom Arcade', + value = CASE WHEN value = 'Flashpoint Web' THEN 'Dotcom Arcade' ELSE value END +WHERE key = 'app.site_name'; diff --git a/backend/src/routes/errors.ts b/backend/src/routes/errors.ts index 3cbb89a..b13e197 100644 --- a/backend/src/routes/errors.ts +++ b/backend/src/routes/errors.ts @@ -61,7 +61,7 @@ const clientErrorLogger = winston.createLogger({ })(), winston.format.json() ), - defaultMeta: { service: 'flashpoint-web-client' }, + defaultMeta: { service: 'dotcom-arcade-client' }, transports: [ new winston.transports.File({ filename: ERROR_LOG_FILE, diff --git a/backend/src/routes/github.ts b/backend/src/routes/github.ts deleted file mode 100644 index a426283..0000000 --- a/backend/src/routes/github.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Router, Request, Response } from 'express'; -import { logger } from '../utils/logger'; -import { asyncHandler } from '../middleware/asyncHandler'; - -const router = Router(); - -// In-memory cache for GitHub star count (avoid hitting 60/hr unauthenticated rate limit) -let cachedStars: { count: number; fetchedAt: number } | null = null; -const CACHE_TTL_MS = 15 * 60 * 1000; - -// Proxy to avoid browser CORS issues with GitHub API -router.get( - '/stars', - asyncHandler(async (_req: Request, res: Response) => { - if (cachedStars && Date.now() - cachedStars.fetchedAt < CACHE_TTL_MS) { - return res.json({ - success: true, - data: { stars: cachedStars.count }, - }); - } - - const repoUrl = 'https://api.github.com/repos/darkraise/flashpoint-web'; - - const response = await fetch(repoUrl, { - headers: { - Accept: 'application/vnd.github.v3+json', - 'User-Agent': 'Flashpoint-Web-App', - }, - }); - - if (!response.ok) { - // If rate-limited and we have a stale cache, return it - if (cachedStars) { - logger.debug('[GitHub] Rate limited, returning stale cache'); - return res.json({ - success: true, - data: { stars: cachedStars.count }, - }); - } - throw new Error(`GitHub API error: ${response.status}`); - } - - const data = (await response.json()) as { stargazers_count: number }; - - cachedStars = { count: data.stargazers_count, fetchedAt: Date.now() }; - - res.json({ - success: true, - data: { - stars: data.stargazers_count, - }, - }); - }) -); - -export default router; diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index d8c242c..32524e9 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -25,7 +25,6 @@ import ratingsRouter from './ratings'; import gameSavesRouter from './game-saves'; import discoveryRouter from './discovery'; import ruffleRouter from './ruffle'; -import githubRouter from './github'; import domainsRouter from './domains'; import healthRouter from './health'; import cacheRouter from './cache'; @@ -40,7 +39,6 @@ export function setupRoutes(app: Express): void { app.use('/api/settings/auth', authSettingsRouter); app.use('/api/settings', systemSettingsRouter); app.use('/api/ruffle', ruffleRouter); - app.use('/api/github', githubRouter); app.use('/api/domains', domainsRouter); app.use('/api/jobs', jobsRouter); app.use('/api/users', usersRouter); diff --git a/backend/src/routes/streamProxy.test.ts b/backend/src/routes/streamProxy.test.ts new file mode 100644 index 0000000..02fb125 --- /dev/null +++ b/backend/src/routes/streamProxy.test.ts @@ -0,0 +1,87 @@ +import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'; +import http, { type Server } from 'http'; +import type { AddressInfo } from 'net'; +import express from 'express'; +import { mountStreamProxy } from './streamProxy'; + +vi.mock('../utils/logger', () => ({ + logger: { info: vi.fn(), error: vi.fn(), debug: vi.fn(), warn: vi.fn() }, +})); + +let upstream: Server; +let app: Server; +let base: string; + +function listen(server: Server): Promise { + return new Promise((resolve) => + server.listen(0, '127.0.0.1', () => resolve((server.address() as AddressInfo).port)) + ); +} + +beforeAll(async () => { + // A stand-in session manager: echoes the path, and accepts WebSocket upgrades + upstream = http.createServer((req, res) => { + res.setHeader('Content-Type', 'text/plain'); + res.end(`upstream saw ${req.method} ${req.url}`); + }); + upstream.on('upgrade', (req, socket) => { + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n' + ); + socket.write(`ws ${req.url}`); + socket.end(); + }); + const upstreamPort = await listen(upstream); + + const expressApp = express(); + const attach = mountStreamProxy(expressApp, `http://127.0.0.1:${upstreamPort}`); + expressApp.get('/other', (_req, res) => { + res.send('app'); + }); + app = http.createServer(expressApp); + attach(app); + base = `http://127.0.0.1:${await listen(app)}`; +}); + +afterAll(() => { + app.close(); + upstream.close(); +}); + +describe('stream proxy', () => { + it('forwards /stream/ requests with their full path', async () => { + const res = await fetch(`${base}/stream/abc/_fp/start`, { method: 'POST' }); + expect(await res.text()).toBe('upstream saw POST /stream/abc/_fp/start'); + }); + + it('leaves other routes to the app', async () => { + expect(await (await fetch(`${base}/other`)).text()).toBe('app'); + }); + + it('forwards WebSocket upgrades', async () => { + const received = await new Promise((resolve, reject) => { + const req = http.request(`${base}/stream/abc/websocket`, { + headers: { Connection: 'Upgrade', Upgrade: 'websocket' }, + }); + req.on('upgrade', (res, socket, head) => { + expect(res.statusCode).toBe(101); + let data = head.toString(); + socket.on('data', (chunk) => (data += chunk.toString())); + socket.on('end', () => resolve(data)); + }); + req.on('error', reject); + req.end(); + }); + expect(received).toBe('ws /stream/abc/websocket'); + }); + + it('answers 502 when the session manager is down', async () => { + const down = express(); + mountStreamProxy(down, 'http://127.0.0.1:9'); + const server = http.createServer(down); + const port = await listen(server); + const res = await fetch(`http://127.0.0.1:${port}/stream/abc/`); + server.close(); + expect(res.status).toBe(502); + }); +}); diff --git a/backend/src/routes/streamProxy.ts b/backend/src/routes/streamProxy.ts new file mode 100644 index 0000000..39c391f --- /dev/null +++ b/backend/src/routes/streamProxy.ts @@ -0,0 +1,40 @@ +import { ServerResponse, type IncomingMessage, type Server } from 'http'; +import type { Socket } from 'net'; +import type { Express } from 'express'; +import { createProxyMiddleware } from 'http-proxy-middleware'; +import { logger } from '../utils/logger'; + +/** + * Proxies /stream/ to the session manager, WebSockets included, so a reverse + * proxy in front of the app needs no route of its own for streams. Returns a + * function that wires WebSocket upgrades into the HTTP server once it exists. + */ +export function mountStreamProxy(app: Express, target: string): (server: Server) => void { + const proxy = createProxyMiddleware('/stream/', { + target, + ws: true, + logLevel: 'silent', + // Save sync is a long-lived event stream; don't let the server's idle timeout cut it + onProxyReq: (_proxyReq, req) => { + req.socket.setTimeout(0); + }, + onError: (error, req, res) => { + logger.warn('[Streaming] Stream proxy error', { url: req.url, error: error.message }); + // A failed WebSocket upgrade hands over the raw socket instead of a response + if (!(res instanceof ServerResponse)) { + (res as Socket).destroy(); + } else if (!res.headersSent) { + res.writeHead(502).end(); + } + }, + }); + app.use(proxy); + + return (server) => { + server.on('upgrade', (req: IncomingMessage, socket: Socket, head: Buffer) => { + if (!req.url?.startsWith('/stream/')) return; + socket.setTimeout(0); + proxy.upgrade?.(req as Parameters>[0], socket, head); + }); + }; +} diff --git a/backend/src/routes/system-settings.ts b/backend/src/routes/system-settings.ts index b3afd0e..1044173 100644 --- a/backend/src/routes/system-settings.ts +++ b/backend/src/routes/system-settings.ts @@ -80,11 +80,6 @@ router.get( Record > = systemSettings.getPublicSettings(); - if (!publicSettings.app) { - publicSettings.app = {}; - } - publicSettings.app.homeRecentHours = config.homeRecentHours; - // Inject edition/version from config (auto-detected from version.txt, not stored in DB) if (!publicSettings.metadata) { publicSettings.metadata = {}; diff --git a/backend/src/routes/updates.test.ts b/backend/src/routes/updates.test.ts index c51e819..1c47ed2 100644 --- a/backend/src/routes/updates.test.ts +++ b/backend/src/routes/updates.test.ts @@ -51,7 +51,7 @@ const INFO = { updateAvailable: true, isUnreleasedBuild: false, publishedAt: '2026-08-12T07:11:45Z', - releaseUrl: 'https://github.com/darkraise/flashpoint-web/releases/tag/v1.0.42', + releaseUrl: 'https://git.example.com/owner/dotcom-arcade/releases/tag/v1.0.42', changelog: '## Changes', checkedAt: '2026-08-12T08:00:00.000Z', lastCheckFailed: false, diff --git a/backend/src/server.ts b/backend/src/server.ts index 244be37..2c6217d 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -44,6 +44,8 @@ import { zipManager } from './game/zip-manager'; import { gameZipServer } from './game/gamezipserver'; import gameProxyRouter from './routes/game-proxy'; import gameZipRouter from './routes/game-zip'; +import { mountStreamProxy } from './routes/streamProxy'; +import { StreamingService } from './services/StreamingService'; /** * Compute SHA-256 CSP hashes for inline