aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md45
1 files changed, 38 insertions, 7 deletions
diff --git a/README.md b/README.md
index d8c11f2..fbf7412 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,9 @@ Server-rendered HTML, no JavaScript required, works on old browsers and phones.
- Changes are live immediately: save, then refresh the blog tab.
- A **superadmin** creates bloggers, resets passwords, disables or deletes accounts.
- The **root domain is itself a blog**, owned by the superadmin and managed like any other.
-- Images are stored in Postgres; everything is in one database.
+- **Each blog is its own Postgres database** (`blog_<name>`), images included, so one
+ `pg_dump` is a complete backup of a blog and one `psql` restores it. A small control
+ database holds the users and the list of blogs.
## Local development
@@ -56,13 +58,13 @@ Go changes need a restart.
| `BASE_DOMAIN` | `blogspace.localhost` | Root domain; blogs are `<name>.BASE_DOMAIN` |
| `ADDR` | `:8080` | Listen address |
| `PUBLIC_PORT` | — | Appended to generated blog links (dev only; unset behind a proxy on :80/:443) |
-| `DATABASE_URL` | local dev DSN | Postgres connection string |
+| `DATABASE_URL` | local dev DSN | Connection string of the **control** database; blog databases are created next to it by the same role |
| `JWT_SECRET` | — | **Required** outside dev; long random string (`openssl rand -hex 32`) |
| `SUPERADMIN_USERNAME` / `SUPERADMIN_PASSWORD` | `admin` / — | Created on first start if no superadmin exists |
| `MAX_UPLOAD_MB` | `5` | Image upload limit |
| `DEV` | `false` | Hot-reload templates, allow missing secrets |
-Migrations run automatically at startup.
+Migrations run automatically at startup, for the control database and for every blog database.
## Deployment (Docker)
@@ -95,16 +97,45 @@ server {
Caddy: `example.com, *.example.com { reverse_proxy 127.0.0.1:8080 }` (wildcard
certificates need the DNS challenge).
-Backups: dump the Postgres volume (`docker compose exec db pg_dump -U blogspace blogspace > backup.sql`).
-Images live in the database, so that one dump is everything.
+### Backups and restores
+
+Every blog lives in its own database, `blog_<name>` (dashes become underscores:
+`my-blog` → `blog_my_blog`). The control database (`blogspace`) holds the users
+and the blog registry. Images are in the blog database, so one dump is the whole blog.
+
+```sh
+# one blog
+docker compose exec db pg_dump -U blogspace blog_alice > alice.sql
+# users and the blog list
+docker compose exec db pg_dump -U blogspace blogspace > control.sql
+# everything at once
+docker compose exec db pg_dumpall -U blogspace > all.sql
+```
+
+Restoring a blog, with the app running:
+
+```sh
+docker compose exec db dropdb -U blogspace --force blog_alice
+docker compose exec db createdb -U blogspace blog_alice
+docker compose exec -T db psql -U blogspace -q blog_alice < alice.sql
+```
+
+The registry row must exist: on a fresh install first create the user with that
+subdomain in `/admin/` (which makes an empty `blog_alice`), then overwrite it as
+above. A dump taken with an older version of Blogspace is upgraded at the next
+start (or with `blogspace migrate`).
+
+Deleting a user in `/admin/` drops their blog database — take a dump first if
+you may want it back. Blog pools are small (4 connections each, closed when
+idle); with many blogs busy at once, raise `max_connections` on the `db` service.
## Layout
```
cmd/blogspace/ main (serve | seed | migrate)
internal/config/ environment → Config
-internal/db/ pgx pool + goose migrations (embedded SQL)
-internal/store/ models and queries (users, blogs, pages, posts, images, sections)
+internal/db/ control + per-blog pools, goose migrations (control/ and blog/)
+internal/store/ Store (users, blog registry) and BlogStore (one blog's content)
internal/auth/ bcrypt, JWT cookie sessions, CSRF tokens
internal/markdown/ goldmark + bluemonday
internal/slug/ title → slug