commit 479e3d5cf96ed0c48bb98feada5547c0fa45e14f Author: Sergei Poljanski Date: Tue May 26 17:13:14 2026 +0300 Initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..70c4331 --- /dev/null +++ b/.env.example @@ -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 diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml new file mode 100644 index 0000000..bedb216 --- /dev/null +++ b/.forgejo/workflows/deploy.yaml @@ -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 </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" </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" diff --git a/init/postgres-init.sh b/init/postgres-init.sh new file mode 100644 index 0000000..10b35e4 --- /dev/null +++ b/init/postgres-init.sh @@ -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"