feat: revamp Jefflix buttons, simplify onboarding, add backlog tasks
Replace Movies/Shows/Music/Live Sports buttons with Request/Watch/Upload flow. Update request-access page with clear requests.jefflix.lol and movies.jefflix.lol instructions plus install-as-app tips. Clean up README, remove @vercel/analytics, add backlog tasks and vpn-setup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b43a72a080
commit
2c8614e01e
29
README.md
29
README.md
|
|
@ -1,30 +1 @@
|
|||
# Jefflix website
|
||||
|
||||
*Automatically synced with your [v0.app](https://v0.app) deployments*
|
||||
|
||||
[](https://vercel.com/jeff-emmetts-projects/v0-jefflix-website)
|
||||
[](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
|
||||
79
app/page.tsx
79
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() {
|
|||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="text-lg px-8 py-6 font-bold bg-red-600 hover:bg-red-700 text-white"
|
||||
className="text-lg px-8 py-6 font-bold bg-blue-600 hover:bg-blue-700 text-white"
|
||||
variant="default"
|
||||
>
|
||||
<a href="https://movies.jefflix.lol">
|
||||
<Film className="mr-2 h-5 w-5" />
|
||||
Movies
|
||||
<a href="https://requests.jefflix.lol">
|
||||
<ListPlus className="mr-2 h-5 w-5" />
|
||||
Request a Show or Movie
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="text-lg px-8 py-6 font-bold bg-blue-600 hover:bg-blue-700 text-white"
|
||||
className="text-lg px-8 py-6 font-bold bg-red-600 hover:bg-red-700 text-white"
|
||||
variant="default"
|
||||
>
|
||||
<a href="https://movies.jefflix.lol">
|
||||
<Tv className="mr-2 h-5 w-5" />
|
||||
Shows
|
||||
<Play className="mr-2 h-5 w-5" />
|
||||
Watch a Show or Movie
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="text-lg px-8 py-6 font-bold bg-green-600 hover:bg-green-700 text-white"
|
||||
variant="outline"
|
||||
variant="default"
|
||||
>
|
||||
<a href="https://music.jefflix.lol">
|
||||
<Music className="mr-2 h-5 w-5" />
|
||||
Music
|
||||
<a href="https://upload.jefflix.lol">
|
||||
<Upload className="mr-2 h-5 w-5" />
|
||||
Upload Shows or Movies
|
||||
</a>
|
||||
</Button>
|
||||
<div className="relative group">
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="text-lg px-8 py-6 font-bold bg-orange-600 hover:bg-orange-700 text-white"
|
||||
variant="default"
|
||||
>
|
||||
<a href="https://movies.jefflix.lol/web/index.html#!/livetv.html">
|
||||
<Radio className="mr-2 h-5 w-5" />
|
||||
Live Sports
|
||||
</a>
|
||||
</Button>
|
||||
<a href="/request-access" className="absolute -top-2 -right-2">
|
||||
<Badge className="bg-yellow-500 hover:bg-yellow-400 text-black text-xs px-2 py-0.5 font-bold cursor-pointer transition-colors">
|
||||
Request Access
|
||||
</Badge>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -222,20 +204,20 @@ export default function JefflixPage() {
|
|||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center pt-6">
|
||||
<Button asChild size="lg" className="text-lg px-8 py-6 font-bold bg-red-600 hover:bg-red-700 text-white">
|
||||
<a href="https://movies.jefflix.lol">
|
||||
<Film className="mr-2 h-5 w-5" />
|
||||
Movies
|
||||
<Button asChild size="lg" className="text-lg px-8 py-6 font-bold bg-blue-600 hover:bg-blue-700 text-white">
|
||||
<a href="https://requests.jefflix.lol">
|
||||
<ListPlus className="mr-2 h-5 w-5" />
|
||||
Request a Show or Movie
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="text-lg px-8 py-6 font-bold bg-blue-600 hover:bg-blue-700 text-white"
|
||||
className="text-lg px-8 py-6 font-bold bg-red-600 hover:bg-red-700 text-white"
|
||||
>
|
||||
<a href="https://movies.jefflix.lol">
|
||||
<Tv className="mr-2 h-5 w-5" />
|
||||
Shows
|
||||
<Play className="mr-2 h-5 w-5" />
|
||||
Watch a Show or Movie
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
|
|
@ -243,28 +225,11 @@ export default function JefflixPage() {
|
|||
size="lg"
|
||||
className="text-lg px-8 py-6 font-bold bg-green-600 hover:bg-green-700 text-white"
|
||||
>
|
||||
<a href="https://music.jefflix.lol">
|
||||
<Music className="mr-2 h-5 w-5" />
|
||||
Music
|
||||
<a href="https://upload.jefflix.lol">
|
||||
<Upload className="mr-2 h-5 w-5" />
|
||||
Upload Shows or Movies
|
||||
</a>
|
||||
</Button>
|
||||
<div className="relative group">
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="text-lg px-8 py-6 font-bold bg-orange-600 hover:bg-orange-700 text-white"
|
||||
>
|
||||
<a href="https://movies.jefflix.lol/web/index.html#!/livetv.html">
|
||||
<Radio className="mr-2 h-5 w-5" />
|
||||
Live Sports
|
||||
</a>
|
||||
</Button>
|
||||
<a href="/request-access" className="absolute -top-2 -right-2">
|
||||
<Badge className="bg-yellow-500 hover:bg-yellow-400 text-black text-xs px-2 py-0.5 font-bold cursor-pointer transition-colors">
|
||||
Request Access
|
||||
</Badge>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground pt-4">
|
||||
Or learn how to set up your own Jellyfin server and join the movement
|
||||
|
|
|
|||
|
|
@ -57,6 +57,14 @@ export default function RequestAccessPage() {
|
|||
<p className="text-sm text-muted-foreground">
|
||||
This usually happens within 24-48 hours.
|
||||
</p>
|
||||
<div className="text-left bg-muted/50 rounded-lg p-6 mt-4 space-y-3">
|
||||
<h3 className="font-bold text-center">Once you're approved:</h3>
|
||||
<ul className="text-sm text-muted-foreground space-y-2 list-disc list-inside">
|
||||
<li>Request movies & shows at <a href="https://requests.jefflix.lol" className="text-blue-600 hover:underline font-medium">requests.jefflix.lol</a></li>
|
||||
<li>Watch them at <a href="https://movies.jefflix.lol" className="text-red-600 hover:underline font-medium">movies.jefflix.lol</a></li>
|
||||
<li>Both sites can be installed as apps on your phone's home screen, or use the Jellyfin app on a smart TV</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Link href="/">
|
||||
<Button variant="outline" className="mt-4">
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
|
|
@ -159,14 +167,17 @@ export default function RequestAccessPage() {
|
|||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-8 p-6 bg-muted/50 rounded-lg">
|
||||
<div className="mt-8 p-6 bg-muted/50 rounded-lg space-y-4">
|
||||
<h3 className="font-bold mb-2">What happens next?</h3>
|
||||
<ol className="text-sm text-muted-foreground space-y-2 list-decimal list-inside">
|
||||
<li>Your request is sent to the admin for review</li>
|
||||
<li>Once approved, you'll get an email with your login details</li>
|
||||
<li>Log in at movies.jefflix.lol to access all content</li>
|
||||
<li>Live Sports requires an active Sportsnet subscription</li>
|
||||
<li>Request movies & shows at <a href="https://requests.jefflix.lol" className="text-blue-600 hover:underline font-medium">requests.jefflix.lol</a></li>
|
||||
<li>Watch them at <a href="https://movies.jefflix.lol" className="text-red-600 hover:underline font-medium">movies.jefflix.lol</a></li>
|
||||
</ol>
|
||||
<p className="text-xs text-muted-foreground border-t border-border pt-3">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@ auto_commit: false
|
|||
bypass_git_hooks: false
|
||||
check_active_branches: true
|
||||
active_branch_days: 30
|
||||
task_prefix: "task"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
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)
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #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
|
||||
<!-- AC:END -->
|
||||
|
|
@ -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
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
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?
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #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
|
||||
<!-- AC:END -->
|
||||
|
|
@ -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
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
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
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #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
|
||||
<!-- AC:END -->
|
||||
|
|
@ -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
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
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
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #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
|
||||
<!-- AC:END -->
|
||||
|
|
@ -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
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
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}/
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #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
|
||||
<!-- AC:END -->
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
@ -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"
|
||||
|
|
@ -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 <username>"
|
||||
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 <<MSG
|
||||
Hey! Here's how to connect to Jefflix:
|
||||
|
||||
1. Install Tailscale on your device:
|
||||
- Windows/Mac/Linux: https://tailscale.com/download
|
||||
- iOS: Search "Tailscale" in the App Store
|
||||
- Android: Search "Tailscale" in Google Play
|
||||
|
||||
2. Connect to the Jefflix VPN:
|
||||
|
||||
Desktop (open a terminal and run):
|
||||
tailscale up --login-server=https://vpn.jeffemmett.com --authkey=$PREAUTH_KEY
|
||||
|
||||
iOS: Settings → tap account → "..." → Use custom coordination server
|
||||
Server: https://vpn.jeffemmett.com
|
||||
Then log in with this key: $PREAUTH_KEY
|
||||
|
||||
Android: Settings → tap account → "..." → Use alternate server
|
||||
Server: https://vpn.jeffemmett.com
|
||||
Then log in with this key: $PREAUTH_KEY
|
||||
|
||||
3. Once connected, open your browser and go to:
|
||||
- http://movies.jefflix.lol (Watch movies & shows)
|
||||
- http://requests.jefflix.lol (Request new content)
|
||||
- http://upload.jefflix.lol (Upload your own content)
|
||||
- http://music.jefflix.lol (Listen to music)
|
||||
|
||||
Tailscale runs in the background — you only need to set it up once!
|
||||
|
||||
NOTE: This invite key expires in 7 days. Let me know if you need a new one.
|
||||
MSG
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " Key: $PREAUTH_KEY"
|
||||
echo " Expires: 7 days"
|
||||
echo " User: jefflix"
|
||||
echo "============================================================"
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Jefflix VPN Rollback Script
|
||||
# Restores public access to *.jefflix.lol
|
||||
# ============================================================
|
||||
|
||||
echo "========================================"
|
||||
echo " Jefflix VPN Rollback"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Restore Cloudflare tunnel config
|
||||
if [ -f /root/cloudflared/config.yml.backup-jefflix-vpn ]; then
|
||||
cp /root/cloudflared/config.yml.backup-jefflix-vpn /root/cloudflared/config.yml
|
||||
docker restart cloudflared
|
||||
echo "✓ Cloudflare tunnel config restored and restarted"
|
||||
else
|
||||
echo "⚠ No backup found at /root/cloudflared/config.yml.backup-jefflix-vpn"
|
||||
fi
|
||||
|
||||
# Restore Headscale config
|
||||
if [ -f /opt/apps/headscale-deploy/config/config.yaml.backup-jefflix-vpn ]; then
|
||||
cp /opt/apps/headscale-deploy/config/config.yaml.backup-jefflix-vpn /opt/apps/headscale-deploy/config/config.yaml
|
||||
cd /opt/apps/headscale-deploy && docker compose restart headscale
|
||||
echo "✓ Headscale config restored and restarted"
|
||||
else
|
||||
echo "⚠ No Headscale backup found"
|
||||
fi
|
||||
|
||||
# Stop CoreDNS (optional — it doesn't hurt to leave it running)
|
||||
if docker ps --format '{{.Names}}' | grep -q jefflix-dns; then
|
||||
cd /opt/apps/jefflix-dns && docker compose down
|
||||
echo "✓ CoreDNS stopped"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Rollback complete. Public access to *.jefflix.lol should be restored."
|
||||
echo "Verify: curl -I https://movies.jefflix.lol"
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================
|
||||
# Jefflix VPN Setup Script
|
||||
# Run this on the Netcup RS 8000 server (ssh netcup)
|
||||
# ============================================================
|
||||
|
||||
echo "========================================"
|
||||
echo " Jefflix VPN Setup (Headscale/Tailscale)"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# --- Phase 1: Backups ---
|
||||
echo "[Phase 1] Creating backups..."
|
||||
cp /root/cloudflared/config.yml /root/cloudflared/config.yml.backup-jefflix-vpn
|
||||
cp /opt/apps/headscale-deploy/config/config.yaml /opt/apps/headscale-deploy/config/config.yaml.backup-jefflix-vpn
|
||||
cp -r /root/traefik/config/ /root/traefik/config-backup-jefflix-vpn/ 2>/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 <<COREFILE_EOF
|
||||
jefflix.lol {
|
||||
template IN A {
|
||||
answer "{{ .Name }} 60 IN A ${TAILSCALE_IP}"
|
||||
}
|
||||
log
|
||||
}
|
||||
COREFILE_EOF
|
||||
|
||||
cat > /opt/apps/jefflix-dns/docker-compose.yml <<COMPOSE_EOF
|
||||
services:
|
||||
coredns:
|
||||
image: coredns/coredns:latest
|
||||
container_name: jefflix-dns
|
||||
restart: unless-stopped
|
||||
command: -conf /etc/coredns/Corefile
|
||||
volumes:
|
||||
- ./Corefile:/etc/coredns/Corefile:ro
|
||||
ports:
|
||||
- "${TAILSCALE_IP}:53:53/udp"
|
||||
- "${TAILSCALE_IP}:53:53/tcp"
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
COMPOSE_EOF
|
||||
|
||||
cd /opt/apps/jefflix-dns && docker compose up -d
|
||||
echo " ✓ CoreDNS deployed"
|
||||
|
||||
# Test DNS
|
||||
echo ""
|
||||
echo " Testing DNS resolution..."
|
||||
sleep 2
|
||||
DIG_RESULT=$(dig +short @${TAILSCALE_IP} movies.jefflix.lol 2>/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 ""
|
||||
Loading…
Reference in New Issue