chore: rebrand Subfeed -> Siftlode
Rename all user-facing references (UI wordmark Sift+lode, titles, app name, legal pages, onboarding wizard, emails, README/docs) and infra paths (/srv/subfeed -> /srv/siftlode, image tag, deploy script, backup filenames). Internal identifiers kept on purpose: Postgres user/db "subfeed", logger namespace, localStorage keys, and the subfeed_pgdata volume (renaming would orphan the migrated production data).
This commit is contained in:
parent
cbd0234a8d
commit
898a6ae338
25 changed files with 90 additions and 90 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# Subfeed
|
||||
# Siftlode
|
||||
|
||||
Self-hosted, multi-user web app for browsing **your own YouTube subscriptions** the way you
|
||||
actually want: precise filtering and sorting (by language, topic, length, age, watch state…),
|
||||
|
|
@ -66,7 +66,7 @@ server) does **not** require re-fetching from YouTube. Copy your `.env` (keep th
|
|||
`TOKEN_ENCRYPTION_KEY` and Google client so stored tokens stay valid), then:
|
||||
|
||||
```sh
|
||||
./scripts/backup.sh # -> backups/subfeed-<timestamp>.dump (Windows: scripts\backup.ps1)
|
||||
./scripts/backup.sh # -> backups/siftlode-<timestamp>.dump (Windows: scripts\backup.ps1)
|
||||
./scripts/restore.sh backups/<file> # on the new host after `docker compose up`
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ _DEFAULT_SECRET_KEY = "change-me-session-key"
|
|||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||
|
||||
app_name: str = "Subfeed"
|
||||
app_name: str = "Siftlode"
|
||||
|
||||
database_url: str = "postgresql+psycopg://subfeed:subfeed@db:5432/subfeed"
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ class Settings(BaseSettings):
|
|||
smtp_port: int = 587
|
||||
smtp_user: str = ""
|
||||
smtp_password: str = ""
|
||||
smtp_from: str = "" # e.g. "Subfeed <addr@gmail.com>"; falls back to smtp_user
|
||||
smtp_from: str = "" # e.g. "Siftlode <addr@gmail.com>"; falls back to smtp_user
|
||||
|
||||
# --- Sync / YouTube Data API ---
|
||||
# Optional API key for public reads (channels/videos/playlistItems). When set it is
|
||||
|
|
|
|||
|
|
@ -56,20 +56,20 @@ def send_access_approved(email: str) -> bool:
|
|||
admin = _admin_contact()
|
||||
body = (
|
||||
"Hi,\n\n"
|
||||
"Your request to use Subfeed has been approved — welcome aboard.\n\n"
|
||||
"Just open Subfeed and sign in with this Google account, and your YouTube\n"
|
||||
"Your request to use Siftlode has been approved — welcome aboard.\n\n"
|
||||
"Just open Siftlode and sign in with this Google account, and your YouTube\n"
|
||||
"subscriptions feed will be ready.\n\n"
|
||||
"If you weren't expecting this, you can ignore the message.\n\n"
|
||||
"— Subfeed"
|
||||
"— Siftlode"
|
||||
+ (f"\n\nQuestions? Just reply to this email ({admin})." if admin else "")
|
||||
)
|
||||
return _send([email], "You're in — your Subfeed access is approved", body, reply_to=admin)
|
||||
return _send([email], "You're in — your Siftlode access is approved", body, reply_to=admin)
|
||||
|
||||
|
||||
def send_admin_new_request(admins: list[str], requester: str) -> bool:
|
||||
body = (
|
||||
f"{requester} just requested access to Subfeed.\n\n"
|
||||
"Open Subfeed → Settings → Account → Access requests to approve or deny it.\n\n"
|
||||
f"{requester} just requested access to Siftlode.\n\n"
|
||||
"Open Siftlode → Settings → Account → Access requests to approve or deny it.\n\n"
|
||||
"(Reply to this email to reach the requester directly.)\n"
|
||||
)
|
||||
return _send(admins, f"Subfeed access request from {requester}", body, reply_to=requester)
|
||||
return _send(admins, f"Siftlode access request from {requester}", body, reply_to=requester)
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ from app.scheduler import shutdown_scheduler, start_scheduler
|
|||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
log.info("Subfeed starting up")
|
||||
log.info("Siftlode starting up")
|
||||
start_scheduler()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
log.info("Subfeed shutting down")
|
||||
log.info("Siftlode shutting down")
|
||||
shutdown_scheduler()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Subfeed</title>
|
||||
<title>Siftlode</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
* { box-sizing: border-box; }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ RSS_URL = "https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}"
|
|||
def fetch_channel_feed(channel_id: str) -> list[dict]:
|
||||
url = RSS_URL.format(channel_id=channel_id)
|
||||
try:
|
||||
resp = httpx.get(url, timeout=20.0, headers={"User-Agent": "Subfeed/1.0"})
|
||||
resp = httpx.get(url, timeout=20.0, headers={"User-Agent": "Siftlode/1.0"})
|
||||
except httpx.HTTPError:
|
||||
return []
|
||||
if resp.status_code != 200:
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ set -e
|
|||
echo "Applying database migrations..."
|
||||
alembic upgrade head
|
||||
|
||||
echo "Starting Subfeed API..."
|
||||
echo "Starting Siftlode API..."
|
||||
exec uvicorn app.main:app --host 0.0.0.0 --port 8000 --log-config log_config.json
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# Deploying Subfeed to the public VPS (asgard)
|
||||
# Deploying Siftlode to the public VPS (asgard)
|
||||
|
||||
The public test instance runs on the asgard VPS at **https://subfeed.b1fr0st.eu**, behind
|
||||
The public test instance runs on the asgard VPS at **https://siftlode.b1fr0st.eu**, behind
|
||||
Caddy (which terminates TLS and reverse-proxies to the app on `127.0.0.1:8080`).
|
||||
|
||||
## Layout on asgard
|
||||
|
||||
```
|
||||
/srv/subfeed/
|
||||
/srv/siftlode/
|
||||
src/ git clone of this repo (build context + compose file)
|
||||
.env secrets, mode 0600 — NEVER committed
|
||||
```
|
||||
|
|
@ -17,16 +17,16 @@ the small VPS.
|
|||
|
||||
## First-time setup
|
||||
|
||||
1. DNS `A`/`AAAA` for `subfeed` → asgard (done via the porkbun CLI).
|
||||
2. Caddy vhost block `subfeed.b1fr0st.eu { reverse_proxy 127.0.0.1:8080 }` (+ the shared
|
||||
1. DNS `A`/`AAAA` for `siftlode` → asgard (done via the porkbun CLI).
|
||||
2. Caddy vhost block `siftlode.b1fr0st.eu { reverse_proxy 127.0.0.1:8080 }` (+ the shared
|
||||
security-headers snippet), then reload Caddy.
|
||||
3. `git clone` this repo to `/srv/subfeed/src`.
|
||||
4. Create `/srv/subfeed/.env` (mode 0600) with the required keys — see
|
||||
3. `git clone` this repo to `/srv/siftlode/src`.
|
||||
4. Create `/srv/siftlode/.env` (mode 0600) with the required keys — see
|
||||
[`.env.example`](../.env.example). Generate the secrets:
|
||||
- `SECRET_KEY`: `python -c "import secrets; print(secrets.token_urlsafe(48))"`
|
||||
- `TOKEN_ENCRYPTION_KEY`: `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`
|
||||
- `POSTGRES_PASSWORD`: any long random string.
|
||||
- `OAUTH_REDIRECT_URL=https://subfeed.b1fr0st.eu/auth/callback`
|
||||
- `OAUTH_REDIRECT_URL=https://siftlode.b1fr0st.eu/auth/callback`
|
||||
- `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` / `YOUTUBE_API_KEY` from Google Cloud Console.
|
||||
- `ALLOWED_EMAILS` / `ADMIN_EMAILS` = the invited Google accounts.
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ the small VPS.
|
|||
## Rolling out a new version
|
||||
|
||||
```bash
|
||||
/srv/subfeed/src/deploy/deploy.sh
|
||||
/srv/siftlode/src/deploy/deploy.sh
|
||||
```
|
||||
|
||||
It pulls `main`, rebuilds the image on the host, and restarts the stack (migrations run
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
# Roll out the latest main branch to the public asgard instance.
|
||||
#
|
||||
# Layout on asgard:
|
||||
# /srv/subfeed/src git clone of this repo (build context + compose file)
|
||||
# /srv/subfeed/.env secrets, mode 0600 (never in git)
|
||||
# /srv/siftlode/src git clone of this repo (build context + compose file)
|
||||
# /srv/siftlode/.env secrets, mode 0600 (never in git)
|
||||
#
|
||||
# Build runs on the host Docker daemon, so it isn't constrained by the CI runner's memory.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=/srv/subfeed
|
||||
ROOT=/srv/siftlode
|
||||
cd "$ROOT/src"
|
||||
|
||||
git fetch --quiet origin
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
# has a hard memory/CPU cap so the stack can't exhaust the small VPS.
|
||||
#
|
||||
# Secrets live OUTSIDE the repo, in an env file on the host. Deploy with:
|
||||
# docker compose -f docker-compose.prod.yml --env-file /srv/subfeed/.env up -d --build
|
||||
# docker compose -f docker-compose.prod.yml --env-file /srv/siftlode/.env up -d --build
|
||||
#
|
||||
# Required keys in that env file: POSTGRES_PASSWORD, SECRET_KEY (>=32 chars),
|
||||
# TOKEN_ENCRYPTION_KEY (Fernet), GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
|
||||
# OAUTH_REDIRECT_URL=https://subfeed.b1fr0st.eu/auth/callback, ALLOWED_EMAILS, ADMIN_EMAILS.
|
||||
# OAUTH_REDIRECT_URL=https://siftlode.b1fr0st.eu/auth/callback, ALLOWED_EMAILS, ADMIN_EMAILS.
|
||||
|
||||
services:
|
||||
db:
|
||||
|
|
@ -37,9 +37,9 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: subfeed-api:local
|
||||
image: siftlode-api:local
|
||||
env_file:
|
||||
- /srv/subfeed/.env
|
||||
- /srv/siftlode/.env
|
||||
environment:
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-subfeed}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-subfeed}
|
||||
# The public instance owns the background scheduler (single writer).
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Server (Proxmox LXC) deployment of the full Subfeed stack.
|
||||
# Server (Proxmox LXC) deployment of the full Siftlode stack.
|
||||
#
|
||||
# This is the "central" instance: it owns the single shared Postgres database and
|
||||
# runs the background scheduler (RSS poll, enrichment, recent/deep backfill) 24/7.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Subfeed — Public Deployment Plan (v1)
|
||||
# Siftlode — Public Deployment Plan (v1)
|
||||
|
||||
Status: **approved (planning)** — 2026-06-13. Goal: share the current Subfeed
|
||||
Status: **approved (planning)** — 2026-06-13. Goal: share the current Siftlode
|
||||
build with one trusted tester (both users in Hungary), on safe public
|
||||
infrastructure, with a clean and trustworthy Google sign-in experience.
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ Therefore:
|
|||
"unverified app" screen and how to proceed (Advanced → Continue). Button →
|
||||
incremental consent. Skippable.
|
||||
- **Step — write access (`youtube`)**: optional; needed to unsubscribe from
|
||||
within Subfeed; same pre-warning. Button → incremental consent. Skippable.
|
||||
within Siftlode; same pre-warning. Button → incremental consent. Skippable.
|
||||
3. App **degrades gracefully**: no read scope → no feed (UI explains why); no
|
||||
write scope → no unsubscribe button.
|
||||
|
||||
|
|
@ -78,32 +78,32 @@ future verification submission cleaner.
|
|||
## 3. Infrastructure — all on asgard
|
||||
|
||||
```
|
||||
User browser ──HTTPS──> Caddy (asgard :443, subfeed.b1fr0st.eu)
|
||||
User browser ──HTTPS──> Caddy (asgard :443, siftlode.b1fr0st.eu)
|
||||
│ reverse_proxy 127.0.0.1:8080
|
||||
▼
|
||||
subfeed-api (FastAPI + built SPA, Docker)
|
||||
siftlode-api (FastAPI + built SPA, Docker)
|
||||
│ internal docker network only
|
||||
▼
|
||||
subfeed-db (Postgres 16 — never published)
|
||||
siftlode-db (Postgres 16 — never published)
|
||||
```
|
||||
|
||||
- **DNS**: `porkbun add b1fr0st.eu A subfeed 88.218.78.254` (+ AAAA). Agent-owned
|
||||
- **DNS**: `porkbun add b1fr0st.eu A siftlode 88.218.78.254` (+ AAAA). Agent-owned
|
||||
via `/usr/local/bin/porkbun` on the PVE host.
|
||||
- **TLS**: Caddy DNS-01 (porkbun plugin), same as forge/pages — auto cert for
|
||||
`subfeed.b1fr0st.eu`.
|
||||
- **Caddy**: new vhost block `subfeed.b1fr0st.eu { reverse_proxy 127.0.0.1:8080 }`
|
||||
`siftlode.b1fr0st.eu`.
|
||||
- **Caddy**: new vhost block `siftlode.b1fr0st.eu { reverse_proxy 127.0.0.1:8080 }`
|
||||
inheriting the existing security-header set (HSTS, X-Frame-Options, etc.).
|
||||
- **OAuth redirect URI**: `https://subfeed.b1fr0st.eu/auth/callback` →
|
||||
- **OAuth redirect URI**: `https://siftlode.b1fr0st.eu/auth/callback` →
|
||||
`OAUTH_REDIRECT_URL`. Registered in the Google OAuth client.
|
||||
- **Stack**: dedicated prod compose at `/srv/subfeed/`; app bound to
|
||||
- **Stack**: dedicated prod compose at `/srv/siftlode/`; app bound to
|
||||
**127.0.0.1:8080 only** (behind Caddy, never a public port); Postgres on the
|
||||
internal docker network only (no host port).
|
||||
|
||||
### Resource limits (critical on the 3.3 GB box)
|
||||
- `subfeed-api`: `mem_limit: 768m`, `cpus: 1.0`, `pids_limit: 256`,
|
||||
- `siftlode-api`: `mem_limit: 768m`, `cpus: 1.0`, `pids_limit: 256`,
|
||||
`no-new-privileges`, `cap_drop: ALL`, read-only rootfs + tmpfs `/tmp`,
|
||||
non-root user.
|
||||
- `subfeed-db`: `mem_limit: 512m`, `cpus: 0.75`, tuned
|
||||
- `siftlode-db`: `mem_limit: 512m`, `cpus: 0.75`, tuned
|
||||
`shared_buffers`/`work_mem`.
|
||||
|
||||
---
|
||||
|
|
@ -124,9 +124,9 @@ off, icc off, log caps), auditd, key-only SSH, swap.
|
|||
| **AI-written code flaws** | Dedicated security review of the codebase (§5). |
|
||||
|
||||
- **Backups**: extend the existing `asgard-snapshot` timer with a `pg_dump` of
|
||||
the subfeed DB (rides the nightly tar.zst; pulled to the home array by
|
||||
the siftlode DB (rides the nightly tar.zst; pulled to the home array by
|
||||
`pull-asgard-backups`).
|
||||
- **Monitoring**: Prometheus blackbox probe for `subfeed.b1fr0st.eu` +
|
||||
- **Monitoring**: Prometheus blackbox probe for `siftlode.b1fr0st.eu` +
|
||||
container-up alert, mirroring the Forgejo probes.
|
||||
|
||||
---
|
||||
|
|
@ -148,11 +148,11 @@ off, icc off, log caps), auditd, key-only SSH, swap.
|
|||
|
||||
The runner already runs **on the same asgard host** as the app:
|
||||
|
||||
1. Push to a **private Forgejo repo** at `forge.b1fr0st.eu` (Subfeed is
|
||||
1. Push to a **private Forgejo repo** at `forge.b1fr0st.eu` (Siftlode is
|
||||
currently local-git only).
|
||||
2. **Actions workflow** on tag push: multi-stage Docker build → push to the
|
||||
built-in registry (`forge.b1fr0st.eu/peter/subfeed:<tag>`).
|
||||
3. **Deploy step** in the same workflow: `cd /srv/subfeed && docker compose pull
|
||||
built-in registry (`forge.b1fr0st.eu/peter/siftlode:<tag>`).
|
||||
3. **Deploy step** in the same workflow: `cd /srv/siftlode && docker compose pull
|
||||
&& docker compose up -d` (local, since the runner is on asgard); migrations
|
||||
run via the entrypoint's `alembic upgrade head`.
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ Result: `git tag vX && git push --tags` → live in minutes.
|
|||
authorized domain `b1fr0st.eu`, privacy/ToS/homepage links), declare scopes,
|
||||
**Publish app → Production**, verify domain in Search Console (TXT added via
|
||||
the porkbun CLI by the agent).
|
||||
5. Infra: DNS record, Caddy vhost, `/srv/subfeed/` compose stack with limits,
|
||||
5. Infra: DNS record, Caddy vhost, `/srv/siftlode/` compose stack with limits,
|
||||
backup hook, monitoring probe. Forgejo repo + Actions pipeline.
|
||||
|
||||
**Phase B — focused security review before publish**
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Subfeed</title>
|
||||
<title>Siftlode</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "subfeed-frontend",
|
||||
"name": "siftlode-frontend",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default function Header({
|
|||
className="text-xl font-bold tracking-tight select-none"
|
||||
title="Feed"
|
||||
>
|
||||
Sub<span className="text-accent">feed</span>
|
||||
Sift<span className="text-accent">lode</span>
|
||||
</button>
|
||||
|
||||
<SyncStatus />
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export default function Login() {
|
|||
<div className="min-h-screen grid place-items-center bg-bg text-fg">
|
||||
<div className="glass w-[min(92vw,420px)] rounded-2xl p-10 text-center">
|
||||
<div className="text-3xl font-bold tracking-tight">
|
||||
Sub<span className="text-accent">feed</span>
|
||||
Sift<span className="text-accent">lode</span>
|
||||
</div>
|
||||
<p className="text-muted my-5 leading-relaxed">
|
||||
A self-hosted, filterable feed of your YouTube subscriptions — sort, tag, and
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ function ConsentHeadsUp() {
|
|||
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5 text-amber-400" />
|
||||
<p className="text-xs leading-relaxed text-fg/80">
|
||||
Google will show a <span className="font-medium">"Google hasn't verified this app"</span>{" "}
|
||||
screen — that's expected, because Subfeed hasn't gone through Google's full review.
|
||||
screen — that's expected, because Siftlode hasn't gone through Google's full review.
|
||||
It's safe to continue: click <span className="font-medium">Advanced</span> →{" "}
|
||||
<span className="font-medium">Go to Subfeed</span>. You can revoke access anytime from
|
||||
<span className="font-medium">Go to Siftlode</span>. You can revoke access anytime from
|
||||
your{" "}
|
||||
<a
|
||||
href="https://myaccount.google.com/permissions"
|
||||
|
|
@ -90,7 +90,7 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: ()
|
|||
</div>
|
||||
<h2 className="text-lg font-semibold">Connect your YouTube subscriptions</h2>
|
||||
<p className="text-sm text-muted leading-relaxed mt-2 mb-4">
|
||||
You're signed in. To build your feed, Subfeed needs{" "}
|
||||
You're signed in. To build your feed, Siftlode needs{" "}
|
||||
<span className="text-fg/90">read-only</span> access to the channels you're
|
||||
subscribed to on YouTube. It never posts, deletes, or changes anything with this.
|
||||
</p>
|
||||
|
|
@ -119,7 +119,7 @@ export default function OnboardingWizard({ me, onClose }: { me: Me; onClose: ()
|
|||
</div>
|
||||
<h2 className="text-lg font-semibold">You're connected</h2>
|
||||
<p className="text-sm text-muted leading-relaxed mt-2 mb-4">
|
||||
Your feed is ready. Optionally, you can let Subfeed{" "}
|
||||
Your feed is ready. Optionally, you can let Siftlode{" "}
|
||||
<span className="text-fg/90">unsubscribe</span> from channels on YouTube for you —
|
||||
this needs an extra write permission. Skip it to stay read-only; you can always
|
||||
enable it later in Settings.
|
||||
|
|
|
|||
|
|
@ -492,7 +492,7 @@ function AccessRow({
|
|||
Granted
|
||||
</span>
|
||||
) : (
|
||||
<Tooltip hint="Redirects to Google to grant the permission. Google shows an 'unverified app' notice — that's expected; click Advanced → Go to Subfeed.">
|
||||
<Tooltip hint="Redirects to Google to grant the permission. Google shows an 'unverified app' notice — that's expected; click Advanced → Go to Siftlode.">
|
||||
<button
|
||||
onClick={onEnable}
|
||||
className="shrink-0 glass-card glass-hover px-3 py-1.5 rounded-xl text-sm transition"
|
||||
|
|
@ -532,8 +532,8 @@ function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) {
|
|||
<AccessRow
|
||||
title="Read subscriptions (your feed)"
|
||||
granted={me.can_read}
|
||||
grantedHint="Granted. Subfeed reads the channels you follow to build your feed. It never changes your YouTube account with this."
|
||||
enableHint="Read-only access to your subscriptions — required to build your feed. Subfeed can't modify anything with it."
|
||||
grantedHint="Granted. Siftlode reads the channels you follow to build your feed. It never changes your YouTube account with this."
|
||||
enableHint="Read-only access to your subscriptions — required to build your feed. Siftlode can't modify anything with it."
|
||||
onEnable={() => {
|
||||
window.location.href = "/auth/upgrade?access=read";
|
||||
}}
|
||||
|
|
@ -541,8 +541,8 @@ function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) {
|
|||
<AccessRow
|
||||
title="Unsubscribe (write)"
|
||||
granted={me.can_write}
|
||||
grantedHint="Granted. You can unsubscribe from channels on YouTube from inside Subfeed. It only writes when you ask it to."
|
||||
enableHint="Lets Subfeed unsubscribe from channels on YouTube for you. Optional — skip it to stay read-only."
|
||||
grantedHint="Granted. You can unsubscribe from channels on YouTube from inside Siftlode. It only writes when you ask it to."
|
||||
enableHint="Lets Siftlode unsubscribe from channels on YouTube for you. Optional — skip it to stay read-only."
|
||||
onEnable={() => {
|
||||
window.location.href = "/auth/upgrade?access=write";
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export default function LegalLayout({
|
|||
<div className="min-h-screen bg-bg text-fg">
|
||||
<div className="mx-auto w-[min(92vw,720px)] py-12">
|
||||
<a href="/" className="inline-block text-2xl font-bold tracking-tight mb-8">
|
||||
Sub<span className="text-accent">feed</span>
|
||||
Sift<span className="text-accent">lode</span>
|
||||
</a>
|
||||
<div className="glass rounded-2xl p-8">
|
||||
<h1 className="text-2xl font-semibold">{title}</h1>
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ export default function PrivacyPolicy() {
|
|||
return (
|
||||
<LegalLayout title="Privacy Policy">
|
||||
<p>
|
||||
Subfeed is a personal, non-commercial project operated by an individual in Hungary
|
||||
Siftlode is a personal, non-commercial project operated by an individual in Hungary
|
||||
("we", "us"). It lets you view and organize your own YouTube subscriptions as a
|
||||
filterable feed. This policy explains what data Subfeed accesses, how it is used, and
|
||||
the choices you have. By using Subfeed you agree to this policy.
|
||||
filterable feed. This policy explains what data Siftlode accesses, how it is used, and
|
||||
the choices you have. By using Siftlode you agree to this policy.
|
||||
</p>
|
||||
|
||||
<H2>What we access</H2>
|
||||
<p>Subfeed only accesses data you explicitly authorize, in two separate steps:</p>
|
||||
<p>Siftlode only accesses data you explicitly authorize, in two separate steps:</p>
|
||||
<ul className="list-disc pl-5 space-y-1">
|
||||
<li>
|
||||
<span className="text-fg font-medium">Sign-in (always):</span> your basic Google
|
||||
|
|
@ -31,13 +31,13 @@ export default function PrivacyPolicy() {
|
|||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
You can use Subfeed with sign-in alone, and grant or decline each YouTube permission
|
||||
You can use Siftlode with sign-in alone, and grant or decline each YouTube permission
|
||||
independently.
|
||||
</p>
|
||||
|
||||
<H2>How we use it</H2>
|
||||
<p>
|
||||
Your data is used exclusively to provide Subfeed's features to you: building and
|
||||
Your data is used exclusively to provide Siftlode's features to you: building and
|
||||
displaying your personalized subscription feed, and carrying out actions (such as
|
||||
unsubscribing) that you initiate. We do not use it for advertising, profiling, or any
|
||||
purpose unrelated to the app.
|
||||
|
|
@ -45,7 +45,7 @@ export default function PrivacyPolicy() {
|
|||
|
||||
<H2>Limited Use disclosure</H2>
|
||||
<p>
|
||||
Subfeed's use and transfer of information received from Google APIs to any other app
|
||||
Siftlode's use and transfer of information received from Google APIs to any other app
|
||||
will adhere to the{" "}
|
||||
<LegalLink href="https://developers.google.com/terms/api-services-user-data-policy">
|
||||
Google API Services User Data Policy
|
||||
|
|
@ -65,7 +65,7 @@ export default function PrivacyPolicy() {
|
|||
|
||||
<H2>Retention & deletion</H2>
|
||||
<p>
|
||||
We keep your data while your account is active. You can revoke Subfeed's access to your
|
||||
We keep your data while your account is active. You can revoke Siftlode's access to your
|
||||
Google data at any time at{" "}
|
||||
<LegalLink href="https://myaccount.google.com/permissions">
|
||||
myaccount.google.com/permissions
|
||||
|
|
@ -80,13 +80,13 @@ export default function PrivacyPolicy() {
|
|||
|
||||
<H2>Cookies</H2>
|
||||
<p>
|
||||
Subfeed sets a single, signed session cookie to keep you logged in. It is not used for
|
||||
Siftlode sets a single, signed session cookie to keep you logged in. It is not used for
|
||||
tracking or advertising, and there are no third-party analytics cookies.
|
||||
</p>
|
||||
|
||||
<H2>YouTube & Google</H2>
|
||||
<p>
|
||||
Subfeed uses YouTube API Services. By using Subfeed you also agree to the{" "}
|
||||
Siftlode uses YouTube API Services. By using Siftlode you also agree to the{" "}
|
||||
<LegalLink href="https://www.youtube.com/t/terms">YouTube Terms of Service</LegalLink>,
|
||||
and your use of Google data is subject to the{" "}
|
||||
<LegalLink href="https://policies.google.com/privacy">Google Privacy Policy</LegalLink>.
|
||||
|
|
|
|||
|
|
@ -4,21 +4,21 @@ export default function Terms() {
|
|||
return (
|
||||
<LegalLayout title="Terms of Service">
|
||||
<p>
|
||||
Subfeed is a personal, non-commercial project operated by an individual in Hungary. It
|
||||
Siftlode is a personal, non-commercial project operated by an individual in Hungary. It
|
||||
provides a filterable view of your own YouTube subscriptions. By accessing or using
|
||||
Subfeed, you agree to these Terms. If you do not agree, do not use the service.
|
||||
Siftlode, you agree to these Terms. If you do not agree, do not use the service.
|
||||
</p>
|
||||
|
||||
<H2>The service</H2>
|
||||
<p>
|
||||
Subfeed is offered on an invite-only basis, free of charge, and "as is", without
|
||||
Siftlode is offered on an invite-only basis, free of charge, and "as is", without
|
||||
warranties of any kind. It is a hobby project: features may change, and the service may
|
||||
be slowed, interrupted, or discontinued at any time without notice.
|
||||
</p>
|
||||
|
||||
<H2>Your account & acceptable use</H2>
|
||||
<p>
|
||||
You sign in with your Google account and may only access Subfeed with an email that has
|
||||
You sign in with your Google account and may only access Siftlode with an email that has
|
||||
been invited. You agree to use the service lawfully, not to abuse, disrupt, probe, or
|
||||
overload it, and not to attempt to access other users' data. We may suspend or revoke
|
||||
access at our discretion, for example in case of misuse.
|
||||
|
|
@ -26,7 +26,7 @@ export default function Terms() {
|
|||
|
||||
<H2>Third-party services</H2>
|
||||
<p>
|
||||
Subfeed uses YouTube API Services. By using Subfeed you agree to be bound by the{" "}
|
||||
Siftlode uses YouTube API Services. By using Siftlode you agree to be bound by the{" "}
|
||||
<LegalLink href="https://www.youtube.com/t/terms">YouTube Terms of Service</LegalLink>.
|
||||
Your data is handled as described in our{" "}
|
||||
<a href="/privacy" className="text-accent hover:underline">Privacy Policy</a> and, for
|
||||
|
|
@ -38,7 +38,7 @@ export default function Terms() {
|
|||
<p>
|
||||
To the maximum extent permitted by law, the service is provided without warranty, and
|
||||
the operator is not liable for any indirect, incidental, or consequential damages, or
|
||||
for any loss of data, arising from your use of Subfeed. Your sole remedy if you are
|
||||
for any loss of data, arising from your use of Siftlode. Your sole remedy if you are
|
||||
dissatisfied is to stop using the service and revoke its access to your account.
|
||||
</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# Dumps the Subfeed Postgres database to backups\subfeed-<timestamp>.dump (custom format).
|
||||
# Dumps the Siftlode Postgres database to backupssiftlode-<timestamp>.dump (custom format).
|
||||
# Run from the project root: .\scripts\backup.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$user = if ($env:POSTGRES_USER) { $env:POSTGRES_USER } else { "subfeed" }
|
||||
$dbname = if ($env:POSTGRES_DB) { $env:POSTGRES_DB } else { "subfeed" }
|
||||
New-Item -ItemType Directory -Force -Path "backups" | Out-Null
|
||||
$ts = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$out = "backups\subfeed-$ts.dump"
|
||||
$out = "backups\siftlode-$ts.dump"
|
||||
docker compose exec -T db pg_dump -U $user -Fc $dbname | Set-Content -NoNewline -Encoding Byte $out
|
||||
Write-Output "Wrote $out"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#!/bin/sh
|
||||
# Dumps the Subfeed Postgres database to backups/subfeed-<timestamp>.dump (custom format).
|
||||
# Dumps the Siftlode Postgres database to backups/siftlode-<timestamp>.dump (custom format).
|
||||
# Run from the project root: ./scripts/backup.sh
|
||||
set -e
|
||||
USER_NAME="${POSTGRES_USER:-subfeed}"
|
||||
DB_NAME="${POSTGRES_DB:-subfeed}"
|
||||
mkdir -p backups
|
||||
TS=$(date +%Y%m%d-%H%M%S)
|
||||
OUT="backups/subfeed-$TS.dump"
|
||||
OUT="backups/siftlode-$TS.dump"
|
||||
docker compose exec -T db pg_dump -U "$USER_NAME" -Fc "$DB_NAME" > "$OUT"
|
||||
echo "Wrote $OUT"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Restores a Subfeed Postgres dump created by backup.ps1 into the running db container.
|
||||
# Usage: .\scripts\restore.ps1 backups\subfeed-YYYYMMDD-HHMMSS.dump
|
||||
# Restores a Siftlode Postgres dump created by backup.ps1 into the running db container.
|
||||
# Usage: .\scripts\restore.ps1 backupssiftlode-YYYYMMDD-HHMMSS.dump
|
||||
param([Parameter(Mandatory = $true)][string]$DumpFile)
|
||||
$ErrorActionPreference = "Stop"
|
||||
$user = if ($env:POSTGRES_USER) { $env:POSTGRES_USER } else { "subfeed" }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/sh
|
||||
# Restores a Subfeed Postgres dump created by backup.sh into the running db container.
|
||||
# Usage: ./scripts/restore.sh backups/subfeed-YYYYMMDD-HHMMSS.dump
|
||||
# Restores a Siftlode Postgres dump created by backup.sh into the running db container.
|
||||
# Usage: ./scripts/restore.sh backups/siftlode-YYYYMMDD-HHMMSS.dump
|
||||
set -e
|
||||
FILE="$1"
|
||||
if [ -z "$FILE" ]; then echo "Usage: restore.sh <dumpfile>"; exit 1; fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue