chore: prepare repo for public release — scrub internal infra, rewrite README

- Remove owner-specific / legacy deploy files (home/prod/server compose, deploy/).
  The home compose stays as a local untracked file for the maintainer's own deploy.
- Genericise infra-specific code comments (egress-proxy examples) to neutral wording.
- Replace the hardcoded contact email on the legal pages with the instance operator's
  configured admin email, served via the public /auth/config and shown with a neutral
  fallback — so each self-hosted instance shows its own contact.
- Rewrite README for the current app + a copy-paste self-hosting quick start (prebuilt
  image + first-run wizard) with a build-from-source alternative; tidy .env.example.
This commit is contained in:
npeter83 2026-07-01 12:46:50 +02:00
parent 381794d9ae
commit 0d44d3a34a
10 changed files with 149 additions and 119 deletions

View file

@ -1,10 +1,47 @@
// Shared shell for the public, login-free legal pages (/privacy, /terms). These must be
// reachable unauthenticated — Google's OAuth consent screen links to them and reviewers
// fetch them directly — so they render outside the authenticated App tree (see main.tsx).
// fetch them directly — so they render outside the authenticated App tree (see main.tsx),
// with no react-query provider: the operator contact is fetched with a plain fetch below.
import { useEffect, useState } from "react";
export const CONTACT_EMAIL = "b1fr0stmailops@gmail.com";
export const LAST_UPDATED = "June 14, 2026";
// Contact shown on the legal pages: the instance operator's configured admin email, from the
// public /auth/config. One shared fetch across the page; null when the operator set none.
let contactPromise: Promise<string | null> | null = null;
function fetchOperatorContact(): Promise<string | null> {
if (!contactPromise) {
contactPromise = fetch("/auth/config")
.then((r) => (r.ok ? r.json() : null))
.then((c) => (c?.operator_contact as string | undefined) ?? null)
.catch(() => null);
}
return contactPromise;
}
export function useOperatorContact(): string | null {
const [contact, setContact] = useState<string | null>(null);
useEffect(() => {
let alive = true;
fetchOperatorContact().then((c) => alive && setContact(c));
return () => {
alive = false;
};
}, []);
return contact;
}
// The operator's contact rendered as a mailto link, or a neutral fallback phrase when unset.
export function ContactEmail() {
const email = useOperatorContact();
if (!email) return <>your instance&apos;s administrator</>;
return (
<a href={`mailto:${email}`} className="text-accent hover:underline">
{email}
</a>
);
}
export function LegalLink({ href, children }: { href: string; children: React.ReactNode }) {
return (
<a href={href} target="_blank" rel="noreferrer" className="text-accent hover:underline">
@ -20,6 +57,7 @@ export default function LegalLayout({
title: string;
children: React.ReactNode;
}) {
const contact = useOperatorContact();
return (
<div className="min-h-screen bg-bg text-fg">
<div className="mx-auto w-[min(92vw,720px)] py-12">
@ -35,7 +73,9 @@ export default function LegalLayout({
<a href="/" className="hover:text-fg transition">Home</a>
<a href="/privacy" className="hover:text-fg transition">Privacy Policy</a>
<a href="/terms" className="hover:text-fg transition">Terms of Service</a>
<a href={`mailto:${CONTACT_EMAIL}`} className="hover:text-fg transition">Contact</a>
{contact && (
<a href={`mailto:${contact}`} className="hover:text-fg transition">Contact</a>
)}
</footer>
</div>
</div>