This commit is contained in:
commit
479e3d5cf9
10 changed files with 466 additions and 0 deletions
16
.env.example
Normal file
16
.env.example
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Storage stack — Postgres + MinIO
|
||||||
|
# Copy to .env for local use. Production .env is regenerated by the deploy
|
||||||
|
# workflow from Forgejo secrets.
|
||||||
|
|
||||||
|
# Postgres superuser (used only by init + admin tasks)
|
||||||
|
POSTGRES_PASSWORD=change-me
|
||||||
|
|
||||||
|
# MinIO root account (used only by init + admin tasks)
|
||||||
|
MINIO_ROOT_USER=storage-admin
|
||||||
|
MINIO_ROOT_PASSWORD=change-me-32-chars-min
|
||||||
|
|
||||||
|
# Per-app credentials provisioned by the init containers.
|
||||||
|
# These are also added as secrets on every consuming app's repo.
|
||||||
|
ASXPIO_DB_PASSWORD=change-me
|
||||||
|
ASXPIO_S3_ACCESS_KEY=change-me
|
||||||
|
ASXPIO_S3_SECRET_KEY=change-me
|
||||||
64
.forgejo/workflows/deploy.yaml
Normal file
64
.forgejo/workflows/deploy.yaml
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
name: Deploy storage stack
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: arch-latest
|
||||||
|
steps:
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pacman -Syu --noconfirm nodejs openssh rsync
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Copy compose + init scripts
|
||||||
|
uses: easingthemes/ssh-deploy@v5.1.0
|
||||||
|
with:
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
REMOTE_HOST: ${{ secrets.DEPLOY_IP }}
|
||||||
|
REMOTE_USER: ${{ secrets.DEPLOY_USER }}
|
||||||
|
SOURCE: "docker-compose.yml init/"
|
||||||
|
TARGET: "/opt/storage/"
|
||||||
|
|
||||||
|
- name: Write .env from secrets and bring stack up
|
||||||
|
uses: appleboy/ssh-action@v1.2.3
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||||
|
MINIO_ROOT_USER: ${{ secrets.MINIO_ROOT_USER }}
|
||||||
|
MINIO_ROOT_PASSWORD: ${{ secrets.MINIO_ROOT_PASSWORD }}
|
||||||
|
ASXPIO_DB_PASSWORD: ${{ secrets.ASXPIO_DB_PASSWORD }}
|
||||||
|
ASXPIO_S3_ACCESS_KEY: ${{ secrets.ASXPIO_S3_ACCESS_KEY }}
|
||||||
|
ASXPIO_S3_SECRET_KEY: ${{ secrets.ASXPIO_S3_SECRET_KEY }}
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_IP }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
envs: POSTGRES_PASSWORD,MINIO_ROOT_USER,MINIO_ROOT_PASSWORD,ASXPIO_DB_PASSWORD,ASXPIO_S3_ACCESS_KEY,ASXPIO_S3_SECRET_KEY
|
||||||
|
script_stop: true
|
||||||
|
script: |
|
||||||
|
umask 077
|
||||||
|
mkdir -p /opt/storage
|
||||||
|
cat > /opt/storage/.env <<EOF
|
||||||
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
MINIO_ROOT_USER=${MINIO_ROOT_USER}
|
||||||
|
MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
||||||
|
ASXPIO_DB_PASSWORD=${ASXPIO_DB_PASSWORD}
|
||||||
|
ASXPIO_S3_ACCESS_KEY=${ASXPIO_S3_ACCESS_KEY}
|
||||||
|
ASXPIO_S3_SECRET_KEY=${ASXPIO_S3_SECRET_KEY}
|
||||||
|
EOF
|
||||||
|
chmod 600 /opt/storage/.env
|
||||||
|
chmod +x /opt/storage/init/postgres-init.sh /opt/storage/init/minio-bootstrap.sh
|
||||||
|
|
||||||
|
cd /opt/storage
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d postgres minio
|
||||||
|
# Run init containers (idempotent) and wait for them to exit.
|
||||||
|
docker compose up --exit-code-from postgres-init postgres-init
|
||||||
|
docker compose up --exit-code-from minio-init minio-init
|
||||||
|
|
||||||
|
echo "Storage stack deployed."
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
.env
|
||||||
|
.direnv/
|
||||||
|
.envrc.local
|
||||||
|
*.log
|
||||||
57
README.md
Normal file
57
README.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# storage
|
||||||
|
|
||||||
|
Postgres + MinIO for asxp.io and any future small services that need a database
|
||||||
|
or object storage. Single-host, no clustering. Deploys via Forgejo Actions to
|
||||||
|
the same box that runs the apps.
|
||||||
|
|
||||||
|
## What's here
|
||||||
|
|
||||||
|
- `docker-compose.yml` — `postgres`, `minio`, and one-shot init containers.
|
||||||
|
- `init/postgres-init.sh` — idempotent role/DB bootstrap.
|
||||||
|
- `init/minio-bootstrap.sh` — idempotent bucket + per-app service account.
|
||||||
|
- `.forgejo/workflows/deploy.yaml` — pushes compose + `.env` to the prod host
|
||||||
|
and runs `docker compose up -d`.
|
||||||
|
|
||||||
|
## Networking
|
||||||
|
|
||||||
|
Two Docker networks:
|
||||||
|
|
||||||
|
- `storage` — internal-only; apps join this as an external network to reach
|
||||||
|
`postgres:5432` and `minio:9000`.
|
||||||
|
- `traefik` — existing reverse-proxy network. Only MinIO joins it, to expose
|
||||||
|
the S3 API at `https://s3.asxp.io`.
|
||||||
|
|
||||||
|
The MinIO console (`:9001`) is **not** exposed externally. To use it, SSH-port-forward:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ssh -L 9001:storage-minio:9001 deploy@prod
|
||||||
|
# then open http://localhost:9001
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding a new app
|
||||||
|
|
||||||
|
1. Pick an app name, e.g. `foo`.
|
||||||
|
2. Pick a DB password, S3 access key, S3 secret key for it (any random hex).
|
||||||
|
3. Add Forgejo secrets `FOO_DB_PASSWORD`, `FOO_S3_ACCESS_KEY`, `FOO_S3_SECRET_KEY`
|
||||||
|
to this repo *and* to the foo repo.
|
||||||
|
4. Extend `init/postgres-init.sh` with `ensure_role "foo" ...` and `ensure_db "foo" "foo"`.
|
||||||
|
5. Extend `init/minio-bootstrap.sh` with `ensure_bucket "foo-..."`,
|
||||||
|
`write_policy "foo-...-rw" "foo-..."`, `ensure_user ... "foo-...-rw"`.
|
||||||
|
6. Pass the new envs through in `docker-compose.yml` and the deploy workflow.
|
||||||
|
7. Push.
|
||||||
|
|
||||||
|
## State and backups
|
||||||
|
|
||||||
|
Two named Docker volumes are stateful:
|
||||||
|
|
||||||
|
- `pg_data` (Postgres data dir) — `/var/lib/docker/volumes/storage_pg_data/_data`
|
||||||
|
- `minio_data` (S3 object bytes) — `/var/lib/docker/volumes/storage_minio_data/_data`
|
||||||
|
|
||||||
|
Server-level backup covers both. If you ever move off whole-server backup,
|
||||||
|
the minimum is `pg_dumpall` for Postgres and `mc mirror` for MinIO.
|
||||||
|
|
||||||
|
## Secrets
|
||||||
|
|
||||||
|
See [`SECRETS.md`](SECRETS.md) for the full list of Forgejo secrets this repo
|
||||||
|
needs. Apps that consume storage (asxpio, future) also need the per-app
|
||||||
|
credentials added on *their* repo so they can connect.
|
||||||
84
SECRETS.md
Normal file
84
SECRETS.md
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# Forgejo secrets — storage repo
|
||||||
|
|
||||||
|
Add these to `git.czsk.it/asxpi/storage` → Settings → Actions → Secrets before
|
||||||
|
the first deploy. All values are opaque to the storage stack; nothing is
|
||||||
|
domain-specific.
|
||||||
|
|
||||||
|
## How to generate a value
|
||||||
|
|
||||||
|
```sh
|
||||||
|
openssl rand -hex 32 # 64-char hex, good for any password/key
|
||||||
|
openssl rand -base64 24 # 32-char base64, fine for MinIO root password
|
||||||
|
```
|
||||||
|
|
||||||
|
For MinIO **access keys** prefer alphanumeric, 20 chars:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
LC_ALL=C tr -dc 'A-Z0-9' </dev/urandom | head -c 20; echo
|
||||||
|
```
|
||||||
|
|
||||||
|
For MinIO **secret keys**, 40+ chars:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 40; echo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required secrets
|
||||||
|
|
||||||
|
### Deploy access (same pattern as the asxpio repo)
|
||||||
|
|
||||||
|
| Secret | Value |
|
||||||
|
|------------------|------------------------------------------------------------|
|
||||||
|
| `DEPLOY_IP` | Production host IP/hostname. |
|
||||||
|
| `DEPLOY_USER` | SSH user on the prod host. |
|
||||||
|
| `DEPLOY_SSH_KEY` | Private SSH key authorised on the prod host. |
|
||||||
|
|
||||||
|
Reuse the same key the asxpio repo uses.
|
||||||
|
|
||||||
|
### Postgres
|
||||||
|
|
||||||
|
| Secret | Value |
|
||||||
|
|---------------------|--------------------------------------------------------------------------------|
|
||||||
|
| `POSTGRES_PASSWORD` | Postgres superuser password. `openssl rand -hex 32`. Used only by init/admin. |
|
||||||
|
|
||||||
|
### MinIO
|
||||||
|
|
||||||
|
| Secret | Value |
|
||||||
|
|-------------------------|----------------------------------------------------------------------|
|
||||||
|
| `MINIO_ROOT_USER` | Root username, e.g. `storage-admin`. Any string ≥ 3 chars. |
|
||||||
|
| `MINIO_ROOT_PASSWORD` | Root password. ≥ 8 chars; use `openssl rand -base64 24`. |
|
||||||
|
|
||||||
|
### Per-app credentials provisioned for asxpio
|
||||||
|
|
||||||
|
These are also added (with the same values) on the **asxpio repo** so the app
|
||||||
|
can connect.
|
||||||
|
|
||||||
|
| Secret | Value |
|
||||||
|
|-------------------------|----------------------------------------------------------------|
|
||||||
|
| `ASXPIO_DB_PASSWORD` | Postgres password for the `asxpio` role. `openssl rand -hex 32`.|
|
||||||
|
| `ASXPIO_S3_ACCESS_KEY` | 20-char alphanumeric, see snippet above. |
|
||||||
|
| `ASXPIO_S3_SECRET_KEY` | 40-char alphanumeric, see snippet above. |
|
||||||
|
|
||||||
|
## After adding secrets
|
||||||
|
|
||||||
|
1. Push to `main`. The Forgejo workflow brings Postgres and MinIO up, then runs
|
||||||
|
`postgres-init` (creates `asxpio` role + DB) and `minio-init` (creates
|
||||||
|
`asxpio-invoices` bucket and an `asxpio`-scoped user).
|
||||||
|
2. Confirm both init containers exited 0:
|
||||||
|
```sh
|
||||||
|
ssh deploy@prod 'docker compose -f /opt/storage/docker-compose.yml ps'
|
||||||
|
ssh deploy@prod 'docker logs storage-postgres-init storage-minio-init'
|
||||||
|
```
|
||||||
|
3. Add the matching `ASXPIO_DB_PASSWORD`, `ASXPIO_S3_ACCESS_KEY`,
|
||||||
|
`ASXPIO_S3_SECRET_KEY` (plus `ADMIN_USER`, `ADMIN_PASSWORD`) on the asxpio
|
||||||
|
repo — see `asxpio/SECRETS.md`.
|
||||||
|
4. Deploy asxpio.
|
||||||
|
|
||||||
|
## Rotation
|
||||||
|
|
||||||
|
- **`POSTGRES_PASSWORD` / `MINIO_ROOT_PASSWORD`**: update secret, push. Init
|
||||||
|
scripts only touch *per-app* roles, so the root passwords are applied on
|
||||||
|
container restart (Postgres) or via the env on next boot (MinIO). For MinIO
|
||||||
|
root rotation, recreating the container is enough.
|
||||||
|
- **Per-app password / keys**: update both repos' secrets, push storage first
|
||||||
|
(re-provisions the role/user with the new password), then push the consumer.
|
||||||
93
docker-compose.yml
Normal file
93
docker-compose.yml
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: storage-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
volumes:
|
||||||
|
- pg_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- storage
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
postgres-init:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: storage-postgres-init
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
PGHOST: postgres
|
||||||
|
PGUSER: postgres
|
||||||
|
PGPASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
ASXPIO_DB_PASSWORD: ${ASXPIO_DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- ./init/postgres-init.sh:/init.sh:ro
|
||||||
|
entrypoint: ["/bin/sh", "/init.sh"]
|
||||||
|
networks:
|
||||||
|
- storage
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
container_name: storage-minio
|
||||||
|
restart: unless-stopped
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||||
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||||
|
MINIO_BROWSER_REDIRECT_URL: https://s3.asxp.io
|
||||||
|
volumes:
|
||||||
|
- minio_data:/data
|
||||||
|
networks:
|
||||||
|
- storage
|
||||||
|
- traefik
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=traefik"
|
||||||
|
- "traefik.http.routers.minio-s3.rule=Host(`s3.asxp.io`)"
|
||||||
|
- "traefik.http.routers.minio-s3.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.minio-s3.tls=true"
|
||||||
|
- "traefik.http.routers.minio-s3.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.routers.minio-s3.service=minio-s3"
|
||||||
|
- "traefik.http.services.minio-s3.loadbalancer.server.port=9000"
|
||||||
|
|
||||||
|
minio-init:
|
||||||
|
image: minio/mc:latest
|
||||||
|
container_name: storage-minio-init
|
||||||
|
depends_on:
|
||||||
|
minio:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||||
|
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||||
|
ASXPIO_S3_ACCESS_KEY: ${ASXPIO_S3_ACCESS_KEY}
|
||||||
|
ASXPIO_S3_SECRET_KEY: ${ASXPIO_S3_SECRET_KEY}
|
||||||
|
volumes:
|
||||||
|
- ./init/minio-bootstrap.sh:/bootstrap.sh:ro
|
||||||
|
entrypoint: ["/bin/sh", "/bootstrap.sh"]
|
||||||
|
networks:
|
||||||
|
- storage
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pg_data:
|
||||||
|
minio_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
storage:
|
||||||
|
name: storage
|
||||||
|
traefik:
|
||||||
|
external: true
|
||||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779560665,
|
||||||
|
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
36
flake.nix
Normal file
36
flake.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
description = "storage — Postgres + MinIO for asxp.io";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.postgresql_17
|
||||||
|
pkgs.minio-client
|
||||||
|
pkgs.docker-compose
|
||||||
|
pkgs.openssl
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
if [ -f .env ]; then
|
||||||
|
set -a
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "storage dev shell ready"
|
||||||
|
echo " docker compose up -d - bring stack up"
|
||||||
|
echo " psql -h localhost ... - if you forward 5432"
|
||||||
|
echo " mc alias set local ... - point mc at MinIO"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
56
init/minio-bootstrap.sh
Normal file
56
init/minio-bootstrap.sh
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Idempotent MinIO bootstrap: bucket + per-app service account scoped to that
|
||||||
|
# bucket. Re-running just refreshes the policy and resets the access key.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
mc alias set local http://minio:9000 "${MINIO_ROOT_USER}" "${MINIO_ROOT_PASSWORD}"
|
||||||
|
|
||||||
|
ensure_bucket() {
|
||||||
|
bucket="$1"
|
||||||
|
if ! mc ls "local/${bucket}" >/dev/null 2>&1; then
|
||||||
|
mc mb "local/${bucket}"
|
||||||
|
fi
|
||||||
|
mc anonymous set none "local/${bucket}" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
write_policy() {
|
||||||
|
name="$1"
|
||||||
|
bucket="$2"
|
||||||
|
cat > "/tmp/${name}.json" <<EOF
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": ["s3:ListBucket", "s3:GetBucketLocation"],
|
||||||
|
"Resource": ["arn:aws:s3:::${bucket}"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
|
||||||
|
"Resource": ["arn:aws:s3:::${bucket}/*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
mc admin policy create local "${name}" "/tmp/${name}.json" 2>/dev/null \
|
||||||
|
|| mc admin policy update local "${name}" "/tmp/${name}.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_user() {
|
||||||
|
access="$1"
|
||||||
|
secret="$2"
|
||||||
|
policy="$3"
|
||||||
|
if mc admin user info local "${access}" >/dev/null 2>&1; then
|
||||||
|
# Rotate secret to match desired state.
|
||||||
|
mc admin user remove local "${access}" >/dev/null
|
||||||
|
fi
|
||||||
|
mc admin user add local "${access}" "${secret}"
|
||||||
|
mc admin policy attach local "${policy}" --user "${access}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_bucket "asxpio-invoices"
|
||||||
|
write_policy "asxpio-invoices-rw" "asxpio-invoices"
|
||||||
|
ensure_user "${ASXPIO_S3_ACCESS_KEY}" "${ASXPIO_S3_SECRET_KEY}" "asxpio-invoices-rw"
|
||||||
|
|
||||||
|
echo "minio-init: done"
|
||||||
29
init/postgres-init.sh
Normal file
29
init/postgres-init.sh
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Idempotent bootstrap: create per-app roles and databases.
|
||||||
|
# Re-running is safe — checks before creating, updates passwords if changed.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ensure_role() {
|
||||||
|
role="$1"
|
||||||
|
password="$2"
|
||||||
|
exists=$(psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${role}'")
|
||||||
|
if [ "${exists}" = "1" ]; then
|
||||||
|
psql -c "ALTER ROLE \"${role}\" WITH LOGIN PASSWORD '${password}';"
|
||||||
|
else
|
||||||
|
psql -c "CREATE ROLE \"${role}\" WITH LOGIN PASSWORD '${password}';"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_db() {
|
||||||
|
db="$1"
|
||||||
|
owner="$2"
|
||||||
|
exists=$(psql -tAc "SELECT 1 FROM pg_database WHERE datname='${db}'")
|
||||||
|
if [ "${exists}" != "1" ]; then
|
||||||
|
psql -c "CREATE DATABASE \"${db}\" OWNER \"${owner}\";"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_role "asxpio" "${ASXPIO_DB_PASSWORD}"
|
||||||
|
ensure_db "asxpio" "asxpio"
|
||||||
|
|
||||||
|
echo "postgres-init: done"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue