diff --git a/README.md b/README.md index b0369ac..f683788 100644 --- a/README.md +++ b/README.md @@ -1,30 +1 @@ # Jefflix website - -*Automatically synced with your [v0.app](https://v0.app) deployments* - -[![Deployed on Vercel](https://img.shields.io/badge/Deployed%20on-Vercel-black?style=for-the-badge&logo=vercel)](https://vercel.com/jeff-emmetts-projects/v0-jefflix-website) -[![Built with v0](https://img.shields.io/badge/Built%20with-v0.app-black?style=for-the-badge)](https://v0.app/chat/rSGm1BAgi15) - -## Overview - -This repository will stay in sync with your deployed chats on [v0.app](https://v0.app). -Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app). - -## Deployment - -Your project is live at: - -**[https://vercel.com/jeff-emmetts-projects/v0-jefflix-website](https://vercel.com/jeff-emmetts-projects/v0-jefflix-website)** - -## Build your app - -Continue building your app on: - -**[https://v0.app/chat/rSGm1BAgi15](https://v0.app/chat/rSGm1BAgi15)** - -## How It Works - -1. Create and modify your project using [v0.app](https://v0.app) -2. Deploy your chats from the v0 interface -3. Changes are automatically pushed to this repository -4. Vercel deploys the latest version from this repository \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 3daf808..ef2e8f4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" -import { Film, Music, Server, Users, Heart, Rocket, Tv, Home, FolderGitIcon as SolidarityFistIcon, HandHeart, Landmark, Palette, Radio } from "lucide-react" +import { Film, Music, Server, Users, Heart, Rocket, Tv, Home, FolderGitIcon as SolidarityFistIcon, HandHeart, Landmark, Palette, Radio, ListPlus, Play, Upload } from "lucide-react" import { JefflixLogo } from "@/components/jefflix-logo" export default function JefflixPage() { @@ -48,54 +48,36 @@ export default function JefflixPage() { -
- - - - Request Access - - -
@@ -222,20 +204,20 @@ export default function JefflixPage() {

- -
- - - - Request Access - - -

Or learn how to set up your own Jellyfin server and join the movement diff --git a/app/request-access/page.tsx b/app/request-access/page.tsx index dad32a7..2f3c673 100644 --- a/app/request-access/page.tsx +++ b/app/request-access/page.tsx @@ -57,6 +57,14 @@ export default function RequestAccessPage() {

This usually happens within 24-48 hours.

+
+

Once you're approved:

+ +
-
+

What happens next?

  1. Your request is sent to the admin for review
  2. Once approved, you'll get an email with your login details
  3. -
  4. Log in at movies.jefflix.lol to access all content
  5. -
  6. Live Sports requires an active Sportsnet subscription
  7. +
  8. Request movies & shows at requests.jefflix.lol
  9. +
  10. Watch them at movies.jefflix.lol
+

+ Both sites can be installed as apps on your phone's home screen. On a smart TV, use the Jellyfin app and connect to movies.jefflix.lol. +

diff --git a/backlog/config.yml b/backlog/config.yml index 263b8a2..5f50f6a 100644 --- a/backlog/config.yml +++ b/backlog/config.yml @@ -13,3 +13,4 @@ auto_commit: false bypass_git_hooks: false check_active_branches: true active_branch_days: 30 +task_prefix: "task" diff --git a/backlog/tasks/task-3 - Set-up-Navidrome-mobile-access-Android-iOS.md b/backlog/tasks/task-3 - Set-up-Navidrome-mobile-access-Android-iOS.md new file mode 100644 index 0000000..eb8ea0f --- /dev/null +++ b/backlog/tasks/task-3 - Set-up-Navidrome-mobile-access-Android-iOS.md @@ -0,0 +1,47 @@ +--- +id: TASK-3 +title: Set up Navidrome mobile access (Android + iOS) +status: To Do +assignee: [] +created_date: '2026-02-24 07:13' +labels: + - soulsync + - mobile + - navidrome +dependencies: [] +references: + - 'https://soulsync.jefflix.lol' + - 'https://music.jefflix.lol' + - 'https://soulseek.jefflix.lol' + - /home/jeffe/Github/jefflix-website/soulsync-docker-compose.yml +priority: high +--- + +## Description + + +Configure Navidrome (music.jefflix.lol) for mobile streaming on both Android and Apple devices using Subsonic-compatible apps. + +Navidrome exposes the Subsonic API, so no custom app development is needed - just configure a native mobile client on each platform. + +**Recommended Apps:** +- **Android:** Symfonium (paid, best UX), Subtracks (free/open-source), DSub, Ultrasonic +- **iOS/Apple:** play:Sub, Amperfy (free/open-source), SubStreamer, iSub + +**Server URL:** https://music.jefflix.lol + +**Prerequisites:** +- Navidrome must be accessible externally (verify Cloudflare tunnel routing) +- User account(s) created in Navidrome +- Subsonic API enabled in Navidrome settings (usually on by default) + + +## Acceptance Criteria + +- [ ] #1 Navidrome is accessible externally at music.jefflix.lol +- [ ] #2 Subsonic API endpoint responds (music.jefflix.lol/rest/ping) +- [ ] #3 Android app installed and streaming music successfully +- [ ] #4 iOS app installed and streaming music successfully +- [ ] #5 Offline download/caching tested on at least one platform +- [ ] #6 Document the setup (app name, settings) for future reference + diff --git a/backlog/tasks/task-4 - Verify-SoulSync-playlist-sync-pipeline-end-to-end.md b/backlog/tasks/task-4 - Verify-SoulSync-playlist-sync-pipeline-end-to-end.md new file mode 100644 index 0000000..a1dbb2b --- /dev/null +++ b/backlog/tasks/task-4 - Verify-SoulSync-playlist-sync-pipeline-end-to-end.md @@ -0,0 +1,40 @@ +--- +id: TASK-4 +title: Verify SoulSync playlist sync pipeline end-to-end +status: To Do +assignee: [] +created_date: '2026-02-24 07:13' +labels: + - soulsync + - maintenance +dependencies: [] +priority: medium +--- + +## Description + + +Ensure the full SoulSync pipeline is working: Spotify playlist → SoulSync orchestration → Soulseek download → Navidrome library update. + +Spotify API was configured (task-1), but we should verify the full flow works reliably before relying on mobile access. + +**Services to check:** +- SoulSync web UI: https://soulsync.jefflix.lol +- slskd (Soulseek): https://soulseek.jefflix.lol +- Navidrome: https://music.jefflix.lol + +**Key checks:** +- Are Spotify playlists syncing to SoulSync? +- Are downloads completing via Soulseek? +- Is Navidrome picking up new files from the music directory? +- Are there any stale/failed downloads to clean up? + + +## Acceptance Criteria + +- [ ] #1 SoulSync shows Spotify playlists synced +- [ ] #2 At least one playlist successfully downloads tracks via Soulseek +- [ ] #3 Downloaded tracks appear in Navidrome library +- [ ] #4 No stuck/failed jobs in the queue +- [ ] #5 slskd Soulseek connection is healthy and sharing + diff --git a/backlog/tasks/task-5 - Audit-games-platform-deployment-on-Netcup.md b/backlog/tasks/task-5 - Audit-games-platform-deployment-on-Netcup.md new file mode 100644 index 0000000..ba384b8 --- /dev/null +++ b/backlog/tasks/task-5 - Audit-games-platform-deployment-on-Netcup.md @@ -0,0 +1,44 @@ +--- +id: TASK-5 +title: Audit games platform deployment on Netcup +status: To Do +assignee: [] +created_date: '2026-02-24 07:13' +labels: + - games + - infrastructure + - audit +dependencies: [] +references: + - 'https://games.jeffemmett.com' + - /home/jeffe/Github/games-platform/docker-compose.yml + - /home/jeffe/Github/games-platform/DEPLOYMENT.md +priority: high +--- + +## Description + + +Check the current state of the games platform deployment at games.jeffemmett.com on Netcup RS 8000. + +**What to check:** +- Are all Docker containers running? (postgres, redis, backend, worker, frontend, nginx) +- Is the site accessible at https://games.jeffemmett.com? +- What games (if any) are currently in the library? +- Check /data/games/ directories for existing ROMs +- Review database for any game entries +- Check if the auto-deploy webhook is working +- Verify EmulatorJS loads correctly in browser + +**Location on server:** /opt/apps/games-platform + + +## Acceptance Criteria + +- [ ] #1 All 6 Docker containers verified running (or restarted) +- [ ] #2 games.jeffemmett.com loads in browser +- [ ] #3 Inventory of existing games documented +- [ ] #4 Database health confirmed +- [ ] #5 Gitea webhook auto-deploy verified +- [ ] #6 EmulatorJS emulator loads on a game page + diff --git a/backlog/tasks/task-6 - Catalog-desired-retro-games-for-games-platform.md b/backlog/tasks/task-6 - Catalog-desired-retro-games-for-games-platform.md new file mode 100644 index 0000000..f61faf3 --- /dev/null +++ b/backlog/tasks/task-6 - Catalog-desired-retro-games-for-games-platform.md @@ -0,0 +1,45 @@ +--- +id: TASK-6 +title: Catalog desired retro games for games platform +status: To Do +assignee: [] +created_date: '2026-02-24 07:14' +labels: + - games + - content +dependencies: [] +priority: medium +--- + +## Description + + +Create a wishlist of old/retro games to add to the games platform. Go through memories of classic games across all supported platforms and build a catalog of what to source. + +**Supported platforms:** +- PlayStation 1 (.iso, .bin, .cue, .pbp) +- Nintendo 64 (.z64, .n64, .v64) +- Super Nintendo (.smc, .sfc) +- Game Boy Advance (.gba) +- Game Boy Color (.gbc, .gb) +- NES (.nes) +- Sega Genesis (.md, .bin, .gen) +- Sega Dreamcast (.cdi, .gdi, .chd) +- PSP (.iso, .cso, .pbp) + +**Process:** +1. List out all desired games by platform +2. Note which ones are personal favorites / must-haves +3. Research ROM availability and file sizes +4. Prioritize which to add first +5. Source ROM files (user handles this manually) +6. Upload via the games platform API or direct file copy + + +## Acceptance Criteria + +- [ ] #1 Game wishlist created with at least 10 games across multiple platforms +- [ ] #2 Games prioritized by platform and personal preference +- [ ] #3 File size estimates noted for storage planning +- [ ] #4 Top 5 must-have games identified for first batch + diff --git a/backlog/tasks/task-7 - Add-ROM-files-and-populate-games-library.md b/backlog/tasks/task-7 - Add-ROM-files-and-populate-games-library.md new file mode 100644 index 0000000..06d3aa9 --- /dev/null +++ b/backlog/tasks/task-7 - Add-ROM-files-and-populate-games-library.md @@ -0,0 +1,43 @@ +--- +id: TASK-7 +title: Add ROM files and populate games library +status: To Do +assignee: [] +created_date: '2026-02-24 07:14' +labels: + - games + - content +dependencies: + - TASK-5 + - TASK-4 +priority: medium +--- + +## Description + + +Once games are sourced (from task: Catalog desired retro games), upload ROMs to the games platform and register them in the database. + +**Methods to add games:** +1. **Direct file copy:** SCP ROMs to /data/games/{platform}/ on Netcup, then register via API +2. **Upload API:** POST to /api/upload with ROM file +3. **Web UI:** Upload through the games platform interface + +**For each game added:** +- Copy ROM to correct platform directory +- Add cover art if available +- Register in database with metadata (title, year, description, genre) +- Test that it loads in EmulatorJS +- Verify save states work + +**Storage location on Netcup:** /data/games/{ps1,n64,snes,gba,gbc,nes,genesis,dreamcast,psp}/ + + +## Acceptance Criteria + +- [ ] #1 At least 5 games uploaded and playable +- [ ] #2 Each game has cover art and metadata +- [ ] #3 EmulatorJS loads and runs each game +- [ ] #4 Save states work for at least one game per platform +- [ ] #5 Games appear correctly in the library browse page + diff --git a/package.json b/package.json index a83fad5..2347872 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "@radix-ui/react-toggle": "1.1.1", "@radix-ui/react-toggle-group": "1.1.1", "@radix-ui/react-tooltip": "1.1.6", - "@vercel/analytics": "latest", "autoprefixer": "^10.4.20", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/vpn-setup/README.md b/vpn-setup/README.md new file mode 100644 index 0000000..bcbbd9b --- /dev/null +++ b/vpn-setup/README.md @@ -0,0 +1,37 @@ +# Jefflix VPN Setup — Headscale + Tailscale + +Protects all `*.jefflix.lol` services behind the existing Headscale VPN at `vpn.jeffemmett.com`. + +## How It Works + +``` +Before (public): + Browser → Cloudflare → Tunnel → Traefik → Jellyfin/etc + +After (VPN-only): + Browser → Tailscale (WireGuard) → Traefik → Jellyfin/etc + (Only works if connected to the tailnet) +``` + +Traefik still routes by Host header — the only change is how traffic reaches it. + +## Quick Start + +SSH into the server and follow the phases in order: + +```bash +ssh netcup +``` + +Then run `setup.sh` (or follow the manual steps below). + +## Files + +| File | Purpose | +|------|---------| +| `setup.sh` | Full setup script (run on Netcup) | +| `coredns/Corefile` | CoreDNS config — resolves *.jefflix.lol to Tailscale IP | +| `coredns/docker-compose.yml` | CoreDNS container definition | +| `headscale-config-patch.yaml` | Split DNS addition for Headscale config | +| `cloudflared-config-clean.yml` | Cloudflare tunnel config with jefflix entries removed | +| `rollback.sh` | Emergency rollback script | diff --git a/vpn-setup/cutover.sh b/vpn-setup/cutover.sh new file mode 100755 index 0000000..35c44ca --- /dev/null +++ b/vpn-setup/cutover.sh @@ -0,0 +1,99 @@ +#!/bin/bash +set -euo pipefail + +# ============================================================ +# Jefflix VPN Cutover Script +# Removes public access to *.jefflix.lol +# Run ONLY after setup.sh and testing VPN access works +# ============================================================ + +echo "========================================" +echo " Jefflix Public Access Cutover" +echo "========================================" +echo "" + +# Pre-flight check: verify VPN access works +TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "") +if [ -z "$TAILSCALE_IP" ]; then + echo "ERROR: Tailscale not running on this server. Run setup.sh first." + exit 1 +fi + +echo "Server Tailscale IP: $TAILSCALE_IP" +echo "" + +# Check DNS works +DIG_RESULT=$(dig +short @${TAILSCALE_IP} movies.jefflix.lol 2>/dev/null || echo "FAILED") +if [ "$DIG_RESULT" != "$TAILSCALE_IP" ]; then + echo "ERROR: CoreDNS not resolving correctly. Got: $DIG_RESULT (expected $TAILSCALE_IP)" + echo "Fix CoreDNS before proceeding." + exit 1 +fi +echo "✓ CoreDNS resolving correctly" + +echo "" +echo "This will REMOVE public access to all *.jefflix.lol services." +echo "Users will need Tailscale connected to vpn.jeffemmett.com to access Jefflix." +echo "" +read -p "Continue? [y/N] " -n 1 -r +echo "" + +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 0 +fi + +# --- Remove jefflix entries from Cloudflare tunnel --- +echo "" +echo "[Cutover] Removing *.jefflix.lol from Cloudflare tunnel..." + +TUNNEL_CONFIG="/root/cloudflared/config.yml" + +# Backup current config (timestamped) +cp "$TUNNEL_CONFIG" "${TUNNEL_CONFIG}.pre-cutover-$(date +%Y%m%d-%H%M%S)" + +# Remove all jefflix.lol hostname entries (hostname + service lines) +# This removes the "- hostname: *.jefflix.lol" and its " service:" line +python3 -c " +import yaml, sys + +with open('$TUNNEL_CONFIG', 'r') as f: + config = yaml.safe_load(f) + +original_count = len(config.get('ingress', [])) + +# Filter out jefflix.lol entries +config['ingress'] = [ + entry for entry in config.get('ingress', []) + if not (isinstance(entry.get('hostname', ''), str) and 'jefflix.lol' in entry.get('hostname', '')) +] + +removed = original_count - len(config['ingress']) + +with open('$TUNNEL_CONFIG', 'w') as f: + yaml.dump(config, f, default_flow_style=False, sort_keys=False) + +print(f' Removed {removed} jefflix.lol entries from tunnel config') +" + +# Restart cloudflared +echo " Restarting cloudflared..." +docker restart cloudflared +echo " ✓ Cloudflared restarted" + +# Wait and verify +sleep 5 +echo "" +echo "========================================" +echo " Cutover Complete" +echo "========================================" +echo "" +echo "Public access to *.jefflix.lol is now REMOVED." +echo "" +echo "Verify from a non-VPN device:" +echo " curl -I https://movies.jefflix.lol (should fail/404)" +echo "" +echo "Verify from a VPN device:" +echo " curl http://movies.jefflix.lol (should work)" +echo "" +echo "To ROLLBACK: run rollback.sh" diff --git a/vpn-setup/onboard-user.sh b/vpn-setup/onboard-user.sh new file mode 100755 index 0000000..bfcf026 --- /dev/null +++ b/vpn-setup/onboard-user.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -euo pipefail + +# ============================================================ +# Generate a Jefflix VPN invite for a new user +# Usage: ./onboard-user.sh [username] +# ============================================================ + +USERNAME="${1:-}" + +if [ -z "$USERNAME" ]; then + echo "Usage: ./onboard-user.sh " + echo " Creates a pre-auth key for the user and prints setup instructions." + exit 1 +fi + +# Generate a single-use pre-auth key (7 day expiry) +echo "Generating pre-auth key for user: $USERNAME" +PREAUTH_KEY=$(docker exec headscale headscale preauthkeys create \ + --user jefflix \ + --reusable=false \ + --expiration 168h 2>&1 | tail -1) + +echo "" +echo "============================================================" +echo " Send the following to $USERNAME:" +echo "============================================================" +echo "" +cat </dev/null || true +echo " ✓ Backups created" + +# --- Phase 1b: Create Headscale users --- +echo "" +echo "[Phase 1b] Creating Headscale users..." +docker exec headscale headscale users create server 2>/dev/null || echo " (user 'server' already exists)" +docker exec headscale headscale users create jefflix 2>/dev/null || echo " (user 'jefflix' already exists)" +echo " ✓ Users ready" + +# --- Phase 2: Install Tailscale --- +echo "" +echo "[Phase 2] Installing Tailscale..." +if command -v tailscale &>/dev/null; then + echo " Tailscale already installed: $(tailscale version)" +else + curl -fsSL https://tailscale.com/install.sh | sh + echo " ✓ Tailscale installed" +fi + +# Generate pre-auth key and join tailnet +echo "" +echo " Generating pre-auth key..." +PREAUTH_KEY=$(docker exec headscale headscale preauthkeys create --user server --reusable=false --expiration 1h 2>&1 | tail -1) +echo " Pre-auth key: $PREAUTH_KEY" + +echo "" +echo " Joining tailnet..." +tailscale up \ + --login-server=https://vpn.jeffemmett.com \ + --authkey="$PREAUTH_KEY" \ + --hostname=netcup-rs8000 \ + --accept-dns=false + +# Get the Tailscale IP +TAILSCALE_IP=$(tailscale ip -4) +echo " ✓ Joined tailnet with IP: $TAILSCALE_IP" + +# --- Phase 3: Deploy CoreDNS --- +echo "" +echo "[Phase 3] Deploying CoreDNS for *.jefflix.lol..." +mkdir -p /opt/apps/jefflix-dns + +# Write Corefile with the actual Tailscale IP +cat > /opt/apps/jefflix-dns/Corefile < /opt/apps/jefflix-dns/docker-compose.yml </dev/null || echo "FAILED") +if [ "$DIG_RESULT" = "$TAILSCALE_IP" ]; then + echo " ✓ DNS test passed: movies.jefflix.lol -> $TAILSCALE_IP" +else + echo " ⚠ DNS test returned: $DIG_RESULT (expected $TAILSCALE_IP)" + echo " Check CoreDNS logs: docker logs jefflix-dns" +fi + +# --- Phase 4: Configure Headscale split DNS --- +echo "" +echo "[Phase 4] Configuring Headscale split DNS..." + +HEADSCALE_CONFIG="/opt/apps/headscale-deploy/config/config.yaml" + +# Check if split DNS is already configured +if grep -q "jefflix.lol" "$HEADSCALE_CONFIG"; then + echo " Split DNS for jefflix.lol already in config, skipping" +else + # Add split DNS section to the nameservers block + # This uses sed to add the split DNS config after the global nameservers + sed -i '/nameservers:/,/^[^ ]/ { + /global:/,/^ [^ ]/ { + /- 1.0.0.1/a\ split:\n jefflix.lol:\n - '"${TAILSCALE_IP}"' + } + }' "$HEADSCALE_CONFIG" + echo " ✓ Split DNS added to Headscale config" +fi + +# Restart Headscale +cd /opt/apps/headscale-deploy && docker compose restart headscale +echo " ✓ Headscale restarted" +sleep 3 + +# Verify Headscale is healthy +docker exec headscale headscale nodes list >/dev/null 2>&1 && echo " ✓ Headscale healthy" || echo " ⚠ Headscale may need attention" + +echo "" +echo "========================================" +echo " Setup Complete (Phases 1-4)" +echo "========================================" +echo "" +echo "Server Tailscale IP: $TAILSCALE_IP" +echo "CoreDNS: running on $TAILSCALE_IP:53" +echo "Split DNS: jefflix.lol -> $TAILSCALE_IP" +echo "" +echo "NEXT STEPS:" +echo " 1. Connect YOUR device to the tailnet and test:" +echo " tailscale up --login-server=https://vpn.jeffemmett.com" +echo " dig movies.jefflix.lol (should return $TAILSCALE_IP)" +echo " curl http://movies.jefflix.lol (should return Jellyfin)" +echo "" +echo " 2. Once confirmed working, run cutover.sh to remove public access" +echo " 3. Then onboard users with pre-auth keys" +echo ""