Merge: rebrand to Siftlode
Some checks failed
CI / frontend (push) Failing after 33s
CI / backend (push) Failing after 30s

This commit is contained in:
npeter83 2026-06-14 04:40:22 +02:00
commit 94a51b1b43
25 changed files with 90 additions and 90 deletions

View file

@ -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`
```

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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; }

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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).

View file

@ -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.

View file

@ -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**

View file

@ -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>

View file

@ -1,5 +1,5 @@
{
"name": "subfeed-frontend",
"name": "siftlode-frontend",
"private": true,
"type": "module",
"scripts": {

View file

@ -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 />

View file

@ -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

View file

@ -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.

View file

@ -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";
}}

View file

@ -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>

View file

@ -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 &amp; 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 &amp; 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>.

View file

@ -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 &amp; 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>

View file

@ -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"

View file

@ -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"

View file

@ -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" }

View file

@ -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