Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 733bfc9bae |
@@ -15,6 +15,3 @@ FRONTEND_ORIGIN=http://localhost:8081
|
|||||||
# Set DEV_MODE=true and MAX_MESSAGES=1000 to test against a large mailbox.
|
# Set DEV_MODE=true and MAX_MESSAGES=1000 to test against a large mailbox.
|
||||||
DEV_MODE=false
|
DEV_MODE=false
|
||||||
MAX_MESSAGES=0
|
MAX_MESSAGES=0
|
||||||
|
|
||||||
# Nightly DB backup rotation (days of dumps to keep in ./backups)
|
|
||||||
BACKUP_KEEP_DAYS=7
|
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ frontend/.vite/
|
|||||||
appsettings.*.local.json
|
appsettings.*.local.json
|
||||||
secrets.json
|
secrets.json
|
||||||
|
|
||||||
## DB backups (never commit dumps)
|
|
||||||
backups/
|
|
||||||
|
|
||||||
## Logs
|
## Logs
|
||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
+1
-3
@@ -29,9 +29,7 @@ reverse proxy.
|
|||||||
mitigate (a third party reading the DB files) reduces to "someone with access to your
|
mitigate (a third party reading the DB files) reduces to "someone with access to your
|
||||||
machine" — mitigate it at the layer that actually works:
|
machine" — mitigate it at the layer that actually works:
|
||||||
- **Use full-disk or volume encryption** on the host (BitLocker/LUKS) — strongly recommended.
|
- **Use full-disk or volume encryption** on the host (BitLocker/LUKS) — strongly recommended.
|
||||||
- **Encrypt backups**: nightly `pg_dump` rotation runs via the compose `backup` service
|
- **Encrypt backups** of the `pgdata` volume the same way.
|
||||||
into `./backups/` (git-ignored) — keep that directory on an encrypted disk and copy it
|
|
||||||
off-machine. Restore: `docker compose exec -T postgres psql -U inboxintel -d inboxintel < backups/<file>.sql`.
|
|
||||||
- Before any **multi-user** deployment, revisit per the multi-provider security design
|
- Before any **multi-user** deployment, revisit per the multi-provider security design
|
||||||
(host admins must not be able to read members' mail — plaintext bodies break that promise).
|
(host admins must not be able to read members' mail — plaintext bodies break that promise).
|
||||||
2. **DB connection is not TLS** — Postgres is only reachable on the compose-internal network /
|
2. **DB connection is not TLS** — Postgres is only reachable on the compose-internal network /
|
||||||
|
|||||||
@@ -22,37 +22,6 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
# Nightly logical backups (RECOMMENDATIONS #3 — previously there were NONE). Dumps
|
|
||||||
# rotate after BACKUP_KEEP_DAYS. The ./backups host directory should live on an
|
|
||||||
# encrypted disk and be included in your off-machine backup regime (see SECURITY.md).
|
|
||||||
# Restore: docker compose exec -T postgres psql -U inboxintel -d inboxintel < backups/<file>.sql
|
|
||||||
backup:
|
|
||||||
image: pgvector/pgvector:pg16
|
|
||||||
entrypoint: /bin/sh
|
|
||||||
command:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
while true; do
|
|
||||||
ts=$$(date -u +%Y%m%d-%H%M%S)
|
|
||||||
if pg_dump -h postgres -U inboxintel -d inboxintel > /backups/inboxintel-$$ts.sql.tmp; then
|
|
||||||
mv /backups/inboxintel-$$ts.sql.tmp /backups/inboxintel-$$ts.sql
|
|
||||||
echo "backup OK: inboxintel-$$ts.sql"
|
|
||||||
else
|
|
||||||
rm -f /backups/inboxintel-$$ts.sql.tmp
|
|
||||||
echo "backup FAILED at $$ts" >&2
|
|
||||||
fi
|
|
||||||
find /backups -name 'inboxintel-*.sql' -mtime +$${BACKUP_KEEP_DAYS:-7} -delete
|
|
||||||
sleep 86400
|
|
||||||
done
|
|
||||||
environment:
|
|
||||||
PGPASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in deploy/.env}
|
|
||||||
BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS:-7}
|
|
||||||
volumes:
|
|
||||||
- ./backups:/backups
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
api:
|
api:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
|
|||||||
+34
-23
@@ -1,42 +1,53 @@
|
|||||||
import React from 'react';
|
import React, { Suspense, lazy } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import Landing from './pages/Landing.jsx';
|
import Landing from './pages/Landing.jsx';
|
||||||
import Dashboard from './pages/Dashboard.jsx';
|
|
||||||
import Senders from './pages/Senders.jsx';
|
|
||||||
import Cleanup from './pages/Cleanup.jsx';
|
|
||||||
import Unsubscribe from './pages/Unsubscribe.jsx';
|
|
||||||
import FolderView from './pages/FolderView.jsx';
|
|
||||||
import SearchResults from './pages/SearchResults.jsx';
|
|
||||||
import Layout from './components/Layout.jsx';
|
import Layout from './components/Layout.jsx';
|
||||||
import DesignSystem from './pages/DesignSystem.jsx';
|
|
||||||
import { ToastProvider, TooltipProvider } from './components/ui';
|
import { ToastProvider, TooltipProvider } from './components/ui';
|
||||||
import '@fontsource-variable/inter';
|
import '@fontsource-variable/inter';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
|
// Route-level code splitting: each page loads its own chunk on first visit, so the
|
||||||
|
// initial bundle no longer carries Chart.js / grid-layout / every page at once.
|
||||||
|
// Landing + Layout stay eager (they're the first paint).
|
||||||
|
const Dashboard = lazy(() => import('./pages/Dashboard.jsx'));
|
||||||
|
const Senders = lazy(() => import('./pages/Senders.jsx'));
|
||||||
|
const Cleanup = lazy(() => import('./pages/Cleanup.jsx'));
|
||||||
|
const Unsubscribe = lazy(() => import('./pages/Unsubscribe.jsx'));
|
||||||
|
const FolderView = lazy(() => import('./pages/FolderView.jsx'));
|
||||||
|
const SearchResults = lazy(() => import('./pages/SearchResults.jsx'));
|
||||||
|
const DesignSystem = lazy(() => import('./pages/DesignSystem.jsx'));
|
||||||
|
|
||||||
|
// Minimal, theme-correct route fallback (skeleton-style, per the design system).
|
||||||
|
const RouteFallback = () => (
|
||||||
|
<div className="p-6 text-sm text-muted-foreground" aria-busy="true">Loading…</div>
|
||||||
|
);
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<TooltipProvider delayDuration={200}>
|
<TooltipProvider delayDuration={200}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Suspense fallback={<RouteFallback />}>
|
||||||
{/* Public landing page */}
|
<Routes>
|
||||||
<Route path="/" element={<Landing />} />
|
{/* Public landing page */}
|
||||||
|
<Route path="/" element={<Landing />} />
|
||||||
|
|
||||||
{/* Authenticated app */}
|
{/* Authenticated app */}
|
||||||
<Route path="/app" element={<Layout />}>
|
<Route path="/app" element={<Layout />}>
|
||||||
<Route index element={<Dashboard />} />
|
<Route index element={<Dashboard />} />
|
||||||
<Route path="senders" element={<Senders />} />
|
<Route path="senders" element={<Senders />} />
|
||||||
<Route path="cleanup" element={<Cleanup />} />
|
<Route path="cleanup" element={<Cleanup />} />
|
||||||
<Route path="unsubscribe" element={<Unsubscribe />} />
|
<Route path="unsubscribe" element={<Unsubscribe />} />
|
||||||
<Route path="folder/:slug" element={<FolderView />} />
|
<Route path="folder/:slug" element={<FolderView />} />
|
||||||
<Route path="search" element={<SearchResults />} />
|
<Route path="search" element={<SearchResults />} />
|
||||||
<Route path="design" element={<DesignSystem />} />
|
<Route path="design" element={<DesignSystem />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user