Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b86863e6f0 | ||
|
|
45b02a85af |
@@ -0,0 +1,29 @@
|
|||||||
|
# supaspin - rules for AI agents
|
||||||
|
|
||||||
|
Read and follow the global rules first. They are in Igor's global CLAUDE.md, which in turn pulls in
|
||||||
|
`~/.agents/AGENTS.md`:
|
||||||
|
|
||||||
|
@~/.claude/CLAUDE.md
|
||||||
|
|
||||||
|
The rules below are specific to this repository and add to the global ones.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
The number is `MAJOR.MINOR.PATCH` (semantic versioning: `1.1.1` is major 1, minor 1, patch 1).
|
||||||
|
|
||||||
|
1. **Pick the number.** A bug fix or wording fix raises PATCH (1.1.0 -> 1.1.1). A new feature raises MINOR and resets
|
||||||
|
PATCH (1.1.1 -> 1.2.0). MAJOR is Igor's decision only: never raise it, never write `!` after the commit type
|
||||||
|
(`feat!:`) and never write a `BREAKING CHANGE:` line without asking him first (see the global rules).
|
||||||
|
2. **Write it in all three places, in the same commit as the change.** The file `VERSION`, the `VERSION = "x.y.z"`
|
||||||
|
line near the top of `supaspin`, and the line `Version x.y.z` at the top of `README.md`. A unit test
|
||||||
|
(`tests/test_supaspin.py`, class `Version`) fails if they differ. `supaspin --version` prints `supaspin x.y.z`.
|
||||||
|
3. **One plan = one branch = one commit = one pull request.** The version bump is part of that one commit.
|
||||||
|
4. **Tag every release.** After the pull request is merged, create the git tag `vX.Y.Z` (for example `v1.1.1`) on
|
||||||
|
the commit on `main` that contains that number in `VERSION`. The tag name must equal `VERSION` with a `v` in
|
||||||
|
front. A version without a tag is not released. Never move or delete a tag that was already published; if a
|
||||||
|
release is wrong, ship a new PATCH instead. With the Gitea API: `POST /api/v1/repos/igor/supaspin/tags` with
|
||||||
|
`tag_name`, `target` (the commit) and `message`.
|
||||||
|
5. **Check the result.** Reinstall from the merged `main` (`sudo bash install.sh`), then run `supaspin --version` and
|
||||||
|
compare it with `VERSION` and the tag.
|
||||||
|
6. If you find a version in `VERSION` that has no tag, or a tag that does not match `VERSION`, tell Igor before doing
|
||||||
|
anything else.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# supaspin
|
# supaspin
|
||||||
|
|
||||||
Version 1.1.0
|
Version 1.1.2
|
||||||
|
|
||||||
Create, list, show and delete throwaway [Supabase](https://supabase.com) instances on a
|
Create, list, show and delete throwaway [Supabase](https://supabase.com) instances on a
|
||||||
single-node [microk8s](https://microk8s.io) server, from a terminal menu.
|
single-node [microk8s](https://microk8s.io) server, from a terminal menu.
|
||||||
@@ -17,7 +17,7 @@ endangering them. Nothing in it is specific to any one app.
|
|||||||
| Postgres | `<LAN IP>:544N` (instance 1 → 5441, 2 → 5442, …), bound on the LAN address only |
|
| Postgres | `<LAN IP>:544N` (instance 1 → 5441, 2 → 5442, …), bound on the LAN address only |
|
||||||
| Studio (admin UI) | `http://<LAN IP>:300N` (3001, 3002, …), not on the public hostname |
|
| Studio (admin UI) | `http://<LAN IP>:300N` (3001, 3002, …), not on the public hostname |
|
||||||
| Disk | fixed-size ext4 image files, mounted at boot: **db 2 GB, storage 1 GB, every other volume 256 MB** |
|
| Disk | fixed-size ext4 image files, mounted at boot: **db 2 GB, storage 1 GB, every other volume 256 MB** |
|
||||||
| Memory | limits add up to **5.625 GiB** per instance, requests to 1.875 GiB (table below) |
|
| Memory | limits add up to **5.875 GiB** per instance, requests to 1.875 GiB (table below) |
|
||||||
| Priority | pods run at priority -100 (`supaspin-low`); when the server runs short of memory they are evicted first and never evict anyone else |
|
| Priority | pods run at priority -100 (`supaspin-low`); when the server runs short of memory they are evicted first and never evict anyone else |
|
||||||
| Secrets | generated fresh per instance: JWT secret, anon and service keys, Postgres password, Studio password, and the internal keys |
|
| Secrets | generated fresh per instance: JWT secret, anon and service keys, Postgres password, Studio password, and the internal keys |
|
||||||
| Uploads | stored in the instance's own storage image (no MinIO); deleted with the instance |
|
| Uploads | stored in the instance's own storage image (no MinIO); deleted with the instance |
|
||||||
@@ -108,7 +108,7 @@ sudo supaspin push-env N TARGET [--port P] [--key FILE] [--dry-run] # see "Cop
|
|||||||
sudo supaspin delete N [--yes]
|
sudo supaspin delete N [--yes]
|
||||||
sudo supaspin mail # change the saved default mail settings
|
sudo supaspin mail # change the saved default mail settings
|
||||||
sudo supaspin setup # change settings
|
sudo supaspin setup # change settings
|
||||||
supaspin --version # prints "supaspin 1.1.0"
|
supaspin --version # prints "supaspin 1.1.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Connecting your app
|
### Connecting your app
|
||||||
@@ -215,12 +215,15 @@ run `supaspin delete N` to clean up.
|
|||||||
| analytics | 128 MiB | 768 MiB |
|
| analytics | 128 MiB | 768 MiB |
|
||||||
| kong | 128 MiB | 512 MiB |
|
| kong | 128 MiB | 512 MiB |
|
||||||
| realtime | 128 MiB | 384 MiB |
|
| realtime | 128 MiB | 384 MiB |
|
||||||
| auth, functions, imgproxy, meta, rest, storage, studio, vector | 128 MiB each | 256 MiB each |
|
| vector | 128 MiB | 512 MiB |
|
||||||
| **Total** | **1.875 GiB** | **5.625 GiB** |
|
| auth, functions, imgproxy, meta, rest, storage, studio | 128 MiB each | 256 MiB each |
|
||||||
|
| **Total** | **1.875 GiB** | **5.875 GiB** |
|
||||||
|
|
||||||
Most limits are half of what a production setup would use. Analytics and realtime are higher: at
|
Most limits are half of what a production setup would use. Analytics, realtime and vector are higher:
|
||||||
half of production they were killed for running out of memory in a loop (measured on a fresh
|
at half of production they were killed for running out of memory in a loop (measured on a fresh
|
||||||
instance: analytics peaks at 624 MiB, realtime at 280 MiB). Image resizing (imgproxy) reads the
|
instance: analytics peaks at 624 MiB, realtime at 280 MiB). Vector is the log collector: it reads the
|
||||||
|
log files of every pod on the server, not only its own instance's, so its memory need follows the
|
||||||
|
server's total log volume; at 256 MiB it was killed in a loop on one instance and used 89% on another. Image resizing (imgproxy) reads the
|
||||||
uploads from the storage volume, so it has no image of its own.
|
uploads from the storage volume, so it has no image of its own.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|||||||
+1
-1
@@ -64,6 +64,6 @@ install -m 0644 "$src/templates/values.yaml.tpl" /usr/share/supaspin/templates/v
|
|||||||
install -m 0755 "$src/uninstall.sh" /usr/share/supaspin/uninstall.sh
|
install -m 0755 "$src/uninstall.sh" /usr/share/supaspin/uninstall.sh
|
||||||
install -d -m 0700 /etc/supaspin /var/lib/supaspin
|
install -d -m 0700 /etc/supaspin /var/lib/supaspin
|
||||||
|
|
||||||
echo "Installed supaspin $(/usr/local/bin/supaspin --version)."
|
echo "Installed $(/usr/local/bin/supaspin --version)."
|
||||||
echo "Next: run 'sudo supaspin' - the first run asks for your settings."
|
echo "Next: run 'sudo supaspin' - the first run asks for your settings."
|
||||||
echo "To remove it later: sudo bash /usr/share/supaspin/uninstall.sh"
|
echo "To remove it later: sudo bash /usr/share/supaspin/uninstall.sh"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import tty
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
VERSION = "1.1.0"
|
VERSION = "1.1.2"
|
||||||
CONFIG_PATH = os.environ.get("SUPASPIN_CONFIG", "/etc/supaspin/config.yaml")
|
CONFIG_PATH = os.environ.get("SUPASPIN_CONFIG", "/etc/supaspin/config.yaml")
|
||||||
STATE_DIR = os.environ.get("SUPASPIN_STATE", "/var/lib/supaspin")
|
STATE_DIR = os.environ.get("SUPASPIN_STATE", "/var/lib/supaspin")
|
||||||
CHART_REPO = "https://supabase-community.github.io/supabase-kubernetes"
|
CHART_REPO = "https://supabase-community.github.io/supabase-kubernetes"
|
||||||
@@ -53,12 +53,14 @@ VOLUMES = {"db": 2048, "storage": 1024, "functions": 256,
|
|||||||
DB_OWNED = ("db", "pgsodium")
|
DB_OWNED = ("db", "pgsodium")
|
||||||
DB_UID, DB_GID = 100, 101
|
DB_UID, DB_GID = 100, 101
|
||||||
|
|
||||||
# Memory per service: (request MiB, limit MiB). Half of maralago.ru production, except analytics
|
# Memory per service: (request MiB, limit MiB). Half of maralago.ru production, except analytics,
|
||||||
# and realtime: at half of production they were killed for running out of memory in a loop
|
# realtime and vector: at half of production they were killed for running out of memory in a loop
|
||||||
# (measured on a fresh instance: analytics peaks at 624 MiB, realtime at 280 MiB).
|
# (measured on a fresh instance: analytics peaks at 624 MiB, realtime at 280 MiB; vector reads the log
|
||||||
|
# files of every pod on the node, not only its own instance's, and was killed at 256 MiB on 2026-09-19).
|
||||||
SERVICES = ["db", "analytics", "kong", "auth", "functions", "imgproxy", "meta",
|
SERVICES = ["db", "analytics", "kong", "auth", "functions", "imgproxy", "meta",
|
||||||
"realtime", "rest", "storage", "studio", "vector"]
|
"realtime", "rest", "storage", "studio", "vector"]
|
||||||
MEMORY = {"db": (512, 2048), "analytics": (128, 768), "kong": (128, 512), "realtime": (128, 384)}
|
MEMORY = {"db": (512, 2048), "analytics": (128, 768), "kong": (128, 512), "realtime": (128, 384),
|
||||||
|
"vector": (128, 512)}
|
||||||
DEFAULT_MEMORY = (128, 256)
|
DEFAULT_MEMORY = (128, 256)
|
||||||
|
|
||||||
DEFAULT_MAIL = {"host": "smtp.example.com", "port": 587, "user": "", "password": "",
|
DEFAULT_MAIL = {"host": "smtp.example.com", "port": 587, "user": "", "password": "",
|
||||||
|
|||||||
@@ -90,13 +90,14 @@ class Values(unittest.TestCase):
|
|||||||
|
|
||||||
def test_memory_caps_add_up(self):
|
def test_memory_caps_add_up(self):
|
||||||
req, lim = sp.total_memory_mib()
|
req, lim = sp.total_memory_mib()
|
||||||
# db 2048, analytics 768, kong 512, realtime 384, the other eight 256 each
|
# db 2048, analytics 768, kong 512, realtime 384, vector 512, the other seven 256 each
|
||||||
self.assertEqual(lim, 2048 + 768 + 512 + 384 + 8 * 256)
|
self.assertEqual(lim, 2048 + 768 + 512 + 384 + 512 + 7 * 256)
|
||||||
self.assertEqual(lim / 1024, 5.625)
|
self.assertEqual(lim / 1024, 5.875)
|
||||||
self.assertEqual(req, 512 + 11 * 128) # 1.875 GiB
|
self.assertEqual(req, 512 + 11 * 128) # 1.875 GiB
|
||||||
# analytics and realtime were killed for running out of memory at half of production
|
# analytics, realtime and vector were killed for running out of memory at half of production
|
||||||
self.assertGreaterEqual(sp.MEMORY["analytics"][1], 768)
|
self.assertGreaterEqual(sp.MEMORY["analytics"][1], 768)
|
||||||
self.assertGreaterEqual(sp.MEMORY["realtime"][1], 384)
|
self.assertGreaterEqual(sp.MEMORY["realtime"][1], 384)
|
||||||
|
self.assertGreaterEqual(sp.MEMORY["vector"][1], 512)
|
||||||
self.assertEqual(req / 1024, 1.875)
|
self.assertEqual(req / 1024, 1.875)
|
||||||
for svc in sp.SERVICES:
|
for svc in sp.SERVICES:
|
||||||
self.assertEqual(self.v["deployment"][svc]["priorityClassName"], "supaspin-low")
|
self.assertEqual(self.v["deployment"][svc]["priorityClassName"], "supaspin-low")
|
||||||
|
|||||||
Reference in New Issue
Block a user