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.
|
||||
DEV_MODE=false
|
||||
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
|
||||
secrets.json
|
||||
|
||||
## DB backups (never commit dumps)
|
||||
backups/
|
||||
|
||||
## Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
+1
-3
@@ -29,9 +29,7 @@ reverse proxy.
|
||||
mitigate (a third party reading the DB files) reduces to "someone with access to your
|
||||
machine" — mitigate it at the layer that actually works:
|
||||
- **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
|
||||
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`.
|
||||
- **Encrypt backups** of the `pgdata` volume the same way.
|
||||
- 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).
|
||||
2. **DB connection is not TLS** — Postgres is only reachable on the compose-internal network /
|
||||
|
||||
@@ -22,37 +22,6 @@ services:
|
||||
timeout: 5s
|
||||
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:
|
||||
build:
|
||||
context: .
|
||||
|
||||
+19
-8
@@ -1,25 +1,35 @@
|
||||
import React from 'react';
|
||||
import React, { Suspense, lazy } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||
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 DesignSystem from './pages/DesignSystem.jsx';
|
||||
import { ToastProvider, TooltipProvider } from './components/ui';
|
||||
import '@fontsource-variable/inter';
|
||||
import './index.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(
|
||||
<React.StrictMode>
|
||||
<ToastProvider>
|
||||
<TooltipProvider delayDuration={200}>
|
||||
<BrowserRouter>
|
||||
<Suspense fallback={<RouteFallback />}>
|
||||
<Routes>
|
||||
{/* Public landing page */}
|
||||
<Route path="/" element={<Landing />} />
|
||||
@@ -37,6 +47,7 @@ ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</BrowserRouter>
|
||||
</TooltipProvider>
|
||||
</ToastProvider>
|
||||
|
||||
Reference in New Issue
Block a user