mirror of
https://github.com/myronblair/kino-app
synced 2026-07-28 05:23:45 -05:00
Rebrand StreamHoard, strip Emergent.sh platform artifacts
- Remove .emergent/ state dir, tracked .gitconfig, @emergentbase/visual-edits dep + craco wiring, emergent.sh script/meta tags, PostHog telemetry snippet - Rename UI/docs branding Kino -> StreamHoard (README, DEPLOY.md, Navbar, Login, Register, AdminShowEpisodes, FastAPI title, health check response) - Rename docker-compose container/network names to streamhoard-* - Default admin email domain -> admin@streamhoard.local
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"env_image_name": "fastapi_react_mongo_shadcn_base_image_cloud_arm:release-17042026-1",
|
||||
"job_id": "08d9a7a1-2a0c-4502-9939-fc5d643c04c4",
|
||||
"created_at": "2026-07-23T22:57:32.873830+00:00Z"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
[user]
|
||||
email = github@emergent.sh
|
||||
name = emergent-agent-e1
|
||||
@@ -61,7 +61,6 @@ logs/
|
||||
|
||||
# --- Test reports / agent runtime ---
|
||||
test_reports/
|
||||
.emergent/
|
||||
tests/__pycache__/
|
||||
|
||||
# --- Misc ---
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Kino — Deployment on a Proxmox LAMP host
|
||||
# StreamHoard — Deployment on a Proxmox LAMP host
|
||||
|
||||
Kino is FastAPI + MongoDB + React. It runs happily alongside your existing
|
||||
StreamHoard is FastAPI + MongoDB + React. It runs happily alongside your existing
|
||||
LAMP stack (Apache/MySQL/PHP) because we run everything in Docker containers
|
||||
on their own network. Apache keeps port 80/443, MySQL is untouched, Kino
|
||||
on their own network. Apache keeps port 80/443, MySQL is untouched, StreamHoard
|
||||
uses whatever port you pick.
|
||||
|
||||
---
|
||||
@@ -54,7 +54,7 @@ Log in with the admin credentials from `.env`.
|
||||
|
||||
## Running behind your existing Apache (optional, recommended)
|
||||
|
||||
If you already have Apache on port 80/443 with virtual hosts, add a Kino vhost
|
||||
If you already have Apache on port 80/443 with virtual hosts, add a StreamHoard vhost
|
||||
that reverse-proxies to the container. This gives you `https://kino.example.com`
|
||||
instead of `http://ip:8080`.
|
||||
|
||||
@@ -130,7 +130,7 @@ To reset everything: `docker compose down -v` (deletes the mongo-data volume).
|
||||
|
||||
## Coexistence with your LAMP stack
|
||||
|
||||
| Kino uses | LAMP uses | Conflict? |
|
||||
| StreamHoard uses | LAMP uses | Conflict? |
|
||||
| ------------------------ | ----------------------- | --------- |
|
||||
| Port `KINO_HTTP_PORT` | Apache 80/443 | No — different ports |
|
||||
| MongoDB (internal only) | MySQL | No — different DBs, Mongo not exposed to host |
|
||||
@@ -144,16 +144,16 @@ same box.
|
||||
|
||||
## Radarr integration
|
||||
|
||||
If Radarr is running on the same Proxmox host (native or container), point Kino
|
||||
If Radarr is running on the same Proxmox host (native or container), point StreamHoard
|
||||
at it via **Admin → Settings → Radarr**:
|
||||
|
||||
- **URL**: `http://<host-ip>:7878` (use the host IP, not `localhost` — containers
|
||||
can't reach the host's `localhost` unless you add `host.docker.internal`)
|
||||
- **API key**: from Radarr → Settings → General → Security
|
||||
|
||||
**Important**: For Kino to actually play the imported movies, the path Radarr
|
||||
**Important**: For StreamHoard to actually play the imported movies, the path Radarr
|
||||
reports (e.g. `/movies/Inception (2010)/Inception.mkv`) must be **readable from
|
||||
inside the Kino backend container**. Mount Radarr's media path into Kino:
|
||||
inside the StreamHoard backend container**. Mount Radarr's media path into StreamHoard:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml — extend the backend volumes
|
||||
@@ -163,7 +163,7 @@ backend:
|
||||
- /mnt/radarr-movies:/mnt/radarr-movies:ro # <— add this
|
||||
```
|
||||
|
||||
Radarr's file paths must be identical inside both Radarr and Kino containers
|
||||
Radarr's file paths must be identical inside both Radarr and StreamHoard containers
|
||||
(easiest: match the paths exactly).
|
||||
|
||||
---
|
||||
@@ -172,14 +172,14 @@ Radarr's file paths must be identical inside both Radarr and Kino containers
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:${KINO_HTTP_PORT:-8080}/api/ | jq
|
||||
# {"app":"Kino","status":"ok"}
|
||||
# {"app":"StreamHoard","status":"ok"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Firewalling
|
||||
|
||||
Kino has no built-in rate limiting or IP allowlist. Since you confirmed
|
||||
StreamHoard has no built-in rate limiting or IP allowlist. Since you confirmed
|
||||
**internal network only**, keep it that way:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Kino — Personal Media Server (Netflix Clone)
|
||||
# StreamHoard — Personal Media Server (Netflix Clone)
|
||||
|
||||
A self-hosted Netflix-style streaming app for movies you legally own.
|
||||
Built with FastAPI + React + MongoDB.
|
||||
@@ -41,7 +41,7 @@ See **[DEPLOY.md](./DEPLOY.md)** for LAMP-coexistence, Apache reverse proxy, Rad
|
||||
*(Legal alternative to auto-downloading — auto-download of copyrighted material is not implemented.)*
|
||||
|
||||
## Default admin
|
||||
- Email: `admin@kino.local`
|
||||
- Email: `admin@streamhoard.local`
|
||||
- Password: `kino-admin-2026`
|
||||
|
||||
Change `ADMIN_EMAIL` / `ADMIN_PASSWORD` in `backend/.env` before first start
|
||||
@@ -118,7 +118,7 @@ Movie media files should live on your Proxmox storage, not Git.
|
||||
# Login
|
||||
curl -X POST $URL/api/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"email":"admin@kino.local","password":"kino-admin-2026"}'
|
||||
-d '{"email":"admin@streamhoard.local","password":"kino-admin-2026"}'
|
||||
|
||||
# List movies
|
||||
curl $URL/api/movies
|
||||
@@ -130,7 +130,7 @@ curl $URL/api/stream/<movie_id>?auth=<token>
|
||||
## Why no auto-download?
|
||||
|
||||
Auto-downloading copyrighted movies from the internet is illegal in most
|
||||
jurisdictions (DMCA, EU copyright directive, etc.). Kino is built as a
|
||||
jurisdictions (DMCA, EU copyright directive, etc.). StreamHoard is built as a
|
||||
**personal media server** for content you legally own or that is in the
|
||||
public domain (Internet Archive, Blender Open Movies, etc.).
|
||||
|
||||
|
||||
+3
-3
@@ -57,7 +57,7 @@ for d in (VIDEOS_DIR, POSTERS_DIR, SUBS_DIR, HLS_DIR):
|
||||
|
||||
CHUNK_SIZE = 1024 * 1024
|
||||
|
||||
app = FastAPI(title="Kino")
|
||||
app = FastAPI(title="StreamHoard")
|
||||
api = APIRouter(prefix="/api")
|
||||
bearer = HTTPBearer(auto_error=False)
|
||||
|
||||
@@ -114,7 +114,7 @@ async def on_startup():
|
||||
await db.profiles.create_index("user_id")
|
||||
await db.subtitles.create_index("movie_id")
|
||||
|
||||
admin_email = os.environ.get("ADMIN_EMAIL", "admin@kino.local")
|
||||
admin_email = os.environ.get("ADMIN_EMAIL", "admin@streamhoard.local")
|
||||
if not await db.users.find_one({"email": admin_email}):
|
||||
admin_user = {
|
||||
"id": str(uuid.uuid4()),
|
||||
@@ -1482,7 +1482,7 @@ async def _auto_fetch_subtitle(content_type: str, content_id: str, tmdb_id: Opti
|
||||
# ============ HEALTH ============
|
||||
@api.get("/")
|
||||
async def root():
|
||||
return {"app": "Kino", "status": "ok"}
|
||||
return {"app": "StreamHoard", "status": "ok"}
|
||||
|
||||
|
||||
app.include_router(api)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Backend pytest suite for Kino Personal Media Server.
|
||||
Backend pytest suite for StreamHoard Personal Media Server.
|
||||
Tests auth, movies, watchlist, progress, requests, and stream auth.
|
||||
"""
|
||||
import os
|
||||
@@ -8,8 +8,8 @@ import uuid
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
BASE_URL = os.environ.get("REACT_APP_BACKEND_URL", "https://streamhoard.preview.emergentagent.com").rstrip("/")
|
||||
ADMIN_EMAIL = "admin@kino.local"
|
||||
BASE_URL = os.environ.get("REACT_APP_BACKEND_URL", "http://localhost:8001").rstrip("/")
|
||||
ADMIN_EMAIL = "admin@streamhoard.local"
|
||||
ADMIN_PASSWORD = "kino-admin-2026"
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ def admin_token(api):
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def member_token(api):
|
||||
email = f"TEST_user_{uuid.uuid4().hex[:8]}@kino.local"
|
||||
email = f"TEST_user_{uuid.uuid4().hex[:8]}@streamhoard.local"
|
||||
r = api.post(f"{BASE_URL}/api/auth/register", json={"email": email, "password": "pass1234", "name": "Test User"})
|
||||
assert r.status_code == 200, f"register failed: {r.text}"
|
||||
data = r.json()
|
||||
@@ -73,7 +73,7 @@ class TestAuth:
|
||||
assert r.status_code == 401
|
||||
|
||||
def test_register_and_me(self, api):
|
||||
email = f"TEST_reg_{uuid.uuid4().hex[:8]}@kino.local"
|
||||
email = f"TEST_reg_{uuid.uuid4().hex[:8]}@streamhoard.local"
|
||||
r = api.post(f"{BASE_URL}/api/auth/register", json={"email": email, "password": "pass1234", "name": "Reg User"})
|
||||
assert r.status_code == 200
|
||||
d = r.json()
|
||||
@@ -86,7 +86,7 @@ class TestAuth:
|
||||
assert r2.json()["email"] == email.lower()
|
||||
|
||||
def test_register_duplicate(self, api):
|
||||
email = f"TEST_dup_{uuid.uuid4().hex[:8]}@kino.local"
|
||||
email = f"TEST_dup_{uuid.uuid4().hex[:8]}@streamhoard.local"
|
||||
api.post(f"{BASE_URL}/api/auth/register", json={"email": email, "password": "pass1234", "name": "U"})
|
||||
r = api.post(f"{BASE_URL}/api/auth/register", json={"email": email, "password": "pass1234", "name": "U"})
|
||||
assert r.status_code == 400
|
||||
|
||||
@@ -9,7 +9,7 @@ import pytest
|
||||
import requests
|
||||
|
||||
BASE_URL = os.environ.get("REACT_APP_BACKEND_URL", "").rstrip("/")
|
||||
ADMIN_EMAIL = "admin@kino.local"
|
||||
ADMIN_EMAIL = "admin@streamhoard.local"
|
||||
ADMIN_PASSWORD = "kino-admin-2026"
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ def admin_headers(admin_token):
|
||||
@pytest.fixture(scope="module")
|
||||
def member(api):
|
||||
"""Create a fresh member for profile-scoped tests."""
|
||||
email = f"TEST_p2_{uuid.uuid4().hex[:8]}@kino.local"
|
||||
email = f"TEST_p2_{uuid.uuid4().hex[:8]}@streamhoard.local"
|
||||
r = api.post(f"{BASE_URL}/api/auth/register",
|
||||
json={"email": email, "password": "pass1234", "name": "P2 User"})
|
||||
assert r.status_code == 200
|
||||
@@ -101,7 +101,7 @@ class TestProfiles:
|
||||
|
||||
def test_cannot_delete_last_profile(self, api):
|
||||
# fresh user with single profile
|
||||
email = f"TEST_solo_{uuid.uuid4().hex[:6]}@kino.local"
|
||||
email = f"TEST_solo_{uuid.uuid4().hex[:6]}@streamhoard.local"
|
||||
d = api.post(f"{BASE_URL}/api/auth/register",
|
||||
json={"email": email, "password": "pass1234", "name": "Solo"}).json()
|
||||
h = {"Authorization": f"Bearer {d['access_token']}"}
|
||||
|
||||
@@ -17,7 +17,7 @@ import pytest
|
||||
import requests
|
||||
|
||||
BASE_URL = os.environ["REACT_APP_BACKEND_URL"].rstrip("/")
|
||||
ADMIN_EMAIL = "admin@kino.local"
|
||||
ADMIN_EMAIL = "admin@streamhoard.local"
|
||||
ADMIN_PASSWORD = "kino-admin-2026"
|
||||
QTEST_VIDEO = "/tmp/qtest.mp4"
|
||||
|
||||
@@ -40,7 +40,7 @@ def admin_token(api):
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def member_token(api):
|
||||
email = f"TEST_q_{uuid.uuid4().hex[:8]}@kino.local"
|
||||
email = f"TEST_q_{uuid.uuid4().hex[:8]}@streamhoard.local"
|
||||
r = api.post(f"{BASE_URL}/api/auth/register",
|
||||
json={"email": email, "password": "pass1234", "name": "QTest"},
|
||||
headers={"Content-Type": "application/json"})
|
||||
|
||||
+13
-13
@@ -1,9 +1,9 @@
|
||||
# Kino — Docker Compose deployment
|
||||
# StreamHoard — Docker Compose deployment
|
||||
#
|
||||
# Runs alongside existing LAMP stacks without conflicts:
|
||||
# - Apache keeps port 80/443
|
||||
# - MySQL is untouched (Kino uses its own MongoDB in a container)
|
||||
# - Kino exposes a single configurable port (default 8080)
|
||||
# - MySQL is untouched (StreamHoard uses its own MongoDB in a container)
|
||||
# - StreamHoard exposes a single configurable port (default 8080)
|
||||
#
|
||||
# Usage:
|
||||
# cp .env.compose.example .env
|
||||
@@ -14,19 +14,19 @@
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:4.4
|
||||
container_name: kino-mongo
|
||||
container_name: streamhoard-mongo
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
networks:
|
||||
- kino
|
||||
# No ports exposed to host — only reachable inside kino network
|
||||
- streamhoard
|
||||
# No ports exposed to host — only reachable inside streamhoard network
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: kino-backend
|
||||
container_name: streamhoard-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mongo
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env}
|
||||
JWT_ALG: HS256
|
||||
JWT_EXPIRE_HOURS: ${JWT_EXPIRE_HOURS:-720}
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@kino.local}
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@streamhoard.local}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?set ADMIN_PASSWORD in .env}
|
||||
ADMIN_NAME: ${ADMIN_NAME:-Admin}
|
||||
MEDIA_ROOT: /media
|
||||
@@ -45,25 +45,25 @@ services:
|
||||
# Host media directory (movies, HLS, subtitles) — point at your NAS/ZFS mount
|
||||
- ${MEDIA_ROOT_HOST:-./media}:/media
|
||||
networks:
|
||||
- kino
|
||||
- streamhoard
|
||||
# No direct host port — frontend container reverse-proxies /api → backend:8001
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
container_name: kino-frontend
|
||||
container_name: streamhoard-frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
# Only Kino's external port. Change KINO_HTTP_PORT to avoid clashes with Apache.
|
||||
# Only StreamHoard's external port. Change KINO_HTTP_PORT to avoid clashes with Apache.
|
||||
- "${KINO_HTTP_PORT:-8080}:80"
|
||||
networks:
|
||||
- kino
|
||||
- streamhoard
|
||||
|
||||
networks:
|
||||
kino:
|
||||
streamhoard:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
const path = require("path");
|
||||
require("dotenv").config();
|
||||
|
||||
// Check if we're in development/preview mode (not production build)
|
||||
// Craco sets NODE_ENV=development for start, NODE_ENV=production for build
|
||||
const isDevServer = process.env.NODE_ENV !== "production";
|
||||
|
||||
// Environment variable overrides
|
||||
const config = {
|
||||
enableHealthCheck: process.env.ENABLE_HEALTH_CHECK === "true",
|
||||
@@ -81,20 +77,4 @@ webpackConfig.devServer = (devServerConfig) => {
|
||||
return devServerConfig;
|
||||
};
|
||||
|
||||
// Wrap with visual edits (automatically adds babel plugin, dev server, and overlay in dev mode)
|
||||
if (isDevServer) {
|
||||
try {
|
||||
const { withVisualEdits } = require("@emergentbase/visual-edits/craco");
|
||||
webpackConfig = withVisualEdits(webpackConfig);
|
||||
} catch (err) {
|
||||
if (err.code === 'MODULE_NOT_FOUND' && err.message.includes('@emergentbase/visual-edits/craco')) {
|
||||
console.warn(
|
||||
"[visual-edits] @emergentbase/visual-edits not installed — visual editing disabled."
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = webpackConfig;
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@emergentbase/visual-edits": "https://assets.emergent.sh/npm/emergentbase-visual-edits-1.0.8.tgz",
|
||||
"@eslint/js": "9.23.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "9.23.0",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="A product of emergent.sh" />
|
||||
<meta name="description" content="StreamHoard - self-hosted personal media server" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@600&display=swap" rel="stylesheet" />
|
||||
@@ -21,9 +21,8 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Emergent | Fullstack App</title>
|
||||
<title>StreamHoard</title>
|
||||
<script>window.addEventListener("error",function(e){if(e.error instanceof DOMException&&e.error.name==="DataCloneError"&&e.message&&e.message.includes("PerformanceServerTiming")){e.stopImmediatePropagation();e.preventDefault()}},true);</script>
|
||||
<script src="https://assets.emergent.sh/scripts/emergent-main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
@@ -38,76 +37,5 @@
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<script>
|
||||
!(function (t, e) {
|
||||
var o, n, p, r;
|
||||
e.__SV ||
|
||||
((window.posthog = e),
|
||||
(e._i = []),
|
||||
(e.init = function (i, s, a) {
|
||||
function g(t, e) {
|
||||
var o = e.split(".");
|
||||
2 == o.length && ((t = t[o[0]]), (e = o[1])),
|
||||
(t[e] = function () {
|
||||
t.push(
|
||||
[e].concat(
|
||||
Array.prototype.slice.call(
|
||||
arguments,
|
||||
0,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
((p = t.createElement("script")).type =
|
||||
"text/javascript"),
|
||||
(p.crossOrigin = "anonymous"),
|
||||
(p.async = !0),
|
||||
(p.src =
|
||||
s.api_host.replace(
|
||||
".i.posthog.com",
|
||||
"-assets.i.posthog.com",
|
||||
) + "/static/array.js"),
|
||||
(r =
|
||||
t.getElementsByTagName(
|
||||
"script",
|
||||
)[0]).parentNode.insertBefore(p, r);
|
||||
var u = e;
|
||||
for (
|
||||
void 0 !== a ? (u = e[a] = []) : (a = "posthog"),
|
||||
u.people = u.people || [],
|
||||
u.toString = function (t) {
|
||||
var e = "posthog";
|
||||
return (
|
||||
"posthog" !== a && (e += "." + a),
|
||||
t || (e += " (stub)"),
|
||||
e
|
||||
);
|
||||
},
|
||||
u.people.toString = function () {
|
||||
return u.toString(1) + ".people (stub)";
|
||||
},
|
||||
o =
|
||||
"init me ws ys ps bs capture je Di ks register register_once register_for_session unregister unregister_for_session Ps getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty Es $s createPersonProfile Is opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing Ss debug xs getPageViewId captureTraceFeedback captureTraceMetric".split(
|
||||
" ",
|
||||
),
|
||||
n = 0;
|
||||
n < o.length;
|
||||
n++
|
||||
)
|
||||
g(u, o[n]);
|
||||
e._i.push([i, s, a]);
|
||||
}),
|
||||
(e.__SV = 1));
|
||||
})(document, window.posthog || []);
|
||||
posthog.init("phc_xAvL2Iq4tFmANRE7kzbKwaSqp1HJjN7x48s3vr0CMjs", {
|
||||
api_host: "https://us.i.posthog.com",
|
||||
person_profiles: "identified_only", // or 'always' to create profiles for anonymous users as well,
|
||||
session_recording: {
|
||||
recordCrossOriginIframes: true,
|
||||
capturePerformance: false,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const Navbar = () => {
|
||||
<div className="px-6 md:px-12 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-10">
|
||||
<Link to="/browse" className="flex items-center gap-2" data-testid="nav-logo">
|
||||
<span className="font-display text-2xl font-black tracking-tighter text-white">Kino</span>
|
||||
<span className="font-display text-2xl font-black tracking-tighter text-white">StreamHoard</span>
|
||||
<span className="text-[#D9381E] text-2xl leading-none">.</span>
|
||||
</Link>
|
||||
{user && (
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function AdminShowEpisodes() {
|
||||
const { data } = await api.post("/episodes/bulk-hide", { episode_ids: ids, hidden: false });
|
||||
msg = `${data.modified} unhidden`;
|
||||
} else if (action === "delete") {
|
||||
if (!window.confirm(`Delete ${ids.length} episode(s) permanently? Files on disk are kept, but Kino entries and HLS output are removed.`)) return;
|
||||
if (!window.confirm(`Delete ${ids.length} episode(s) permanently? Files on disk are kept, but StreamHoard entries and HLS output are removed.`)) return;
|
||||
const { data } = await api.post("/episodes/bulk-delete", { episode_ids: ids });
|
||||
msg = `${data.deleted} deleted`;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function Login() {
|
||||
<div className="flex items-center justify-center px-6 md:px-12 py-12">
|
||||
<form onSubmit={onSubmit} className="w-full max-w-sm fade-up" data-testid="login-form">
|
||||
<Link to="/" className="block mb-12" data-testid="login-logo">
|
||||
<span className="font-display text-3xl font-black tracking-tighter text-white">Kino</span>
|
||||
<span className="font-display text-3xl font-black tracking-tighter text-white">StreamHoard</span>
|
||||
<span className="text-[#D9381E] text-3xl">.</span>
|
||||
</Link>
|
||||
<h2 className="font-display text-3xl font-bold tracking-tight text-white">Sign in</h2>
|
||||
@@ -93,7 +93,7 @@ export default function Login() {
|
||||
|
||||
<div className="mt-8 p-4 border border-[#222] text-xs text-[#8A8A8A]" data-testid="login-demo-credentials">
|
||||
<span className="text-[10px] uppercase tracking-[0.3em] text-[#D9381E] block mb-2">Demo Admin</span>
|
||||
admin@kino.local / kino-admin-2026
|
||||
admin@streamhoard.local / kino-admin-2026
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function Register() {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await register(email, password, name);
|
||||
toast.success("Welcome to Kino");
|
||||
toast.success("Welcome to StreamHoard");
|
||||
nav("/browse", { replace: true });
|
||||
} catch (err) {
|
||||
toast.error(err.response?.data?.detail || "Could not register");
|
||||
@@ -49,7 +49,7 @@ export default function Register() {
|
||||
<div className="flex items-center justify-center px-6 md:px-12 py-12">
|
||||
<form onSubmit={onSubmit} className="w-full max-w-sm fade-up" data-testid="register-form">
|
||||
<Link to="/" className="block mb-12">
|
||||
<span className="font-display text-3xl font-black tracking-tighter text-white">Kino</span>
|
||||
<span className="font-display text-3xl font-black tracking-tighter text-white">StreamHoard</span>
|
||||
<span className="text-[#D9381E] text-3xl">.</span>
|
||||
</Link>
|
||||
<h2 className="font-display text-3xl font-bold tracking-tight text-white">Create account</h2>
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Kino — one-line installer
|
||||
# StreamHoard — one-line installer
|
||||
#
|
||||
# Usage (interactive):
|
||||
# curl -fsSL https://raw.githubusercontent.com/YOU/kino-media-server/main/install.sh | sudo bash
|
||||
@@ -25,7 +25,7 @@ KINO_DIR="${KINO_DIR:-/opt/kino}"
|
||||
KINO_HTTP_PORT="${KINO_HTTP_PORT:-8080}"
|
||||
MEDIA_ROOT_HOST="${MEDIA_ROOT_HOST:-${KINO_DIR}/media}"
|
||||
DB_NAME="${DB_NAME:-kino}"
|
||||
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@kino.local}"
|
||||
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@streamhoard.local}"
|
||||
ADMIN_NAME="${ADMIN_NAME:-Admin}"
|
||||
ADMIN_PASSWORD="${ADMIN_PASSWORD:-}"
|
||||
JWT_SECRET="${JWT_SECRET:-}"
|
||||
@@ -172,7 +172,7 @@ fi
|
||||
# ---------- Done ----------
|
||||
cat <<EOF
|
||||
|
||||
$(c_grn '━━━ Kino is up ━━━')
|
||||
$(c_grn '━━━ StreamHoard is up ━━━')
|
||||
|
||||
URL : $(c_cyn "$URL")
|
||||
Admin : $(c_cyn "$ADMIN_EMAIL")
|
||||
|
||||
Reference in New Issue
Block a user