mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-28 07:41:23 +00:00
ci: image caching, Trivy scans, edge publishing, standalone installer
- docker-build job is now a per-app matrix with GHA layer cache, a container contents smoke check, and a non-blocking Trivy scan. - New docker-publish job pushes edge + sha tags to GHCR on master. - Fixed release.yml tagging ghcr.io/<Owner>/... with a mixed-case owner — GHCR requires lowercase, so tagged releases would have failed at push time. - compose files: image: keys name local builds after the GHCR repos. - install.sh: curl|bash installer — fetches compose files, generates secrets, pulls prebuilt images (docker pull + up --no-build, since compose pull skips buildable services), falls back to a source tarball build when packages aren't public. - README: install section covering the one-liner, clone, and prebuilt paths plus common compose commands. Verified locally: all three images build, full dev-overlay stack passes scripts/smoke-e2e.sh, and install.sh produces a healthy stack in a standalone directory. Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
438ee46016
commit
fe28902c4a
5 files changed
+204
-21
No files matched your search
+70
-16
@@ -6,6 +6,9 @@ on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
backend-tests:
|
||||
name: Backend Tests
|
||||
@@ -96,38 +99,83 @@ jobs:
|
||||
run: go build -v ./cmd/server
|
||||
|
||||
docker-build:
|
||||
name: Docker Build Test
|
||||
name: Docker Build (${{ matrix.app }})
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
app: [backend, auth, frontend]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build backend image
|
||||
- name: Build ${{ matrix.app }} image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: apps/backend/Dockerfile
|
||||
file: apps/${{ matrix.app }}/Dockerfile
|
||||
push: false
|
||||
tags: primora-backend:test
|
||||
load: true
|
||||
tags: primora-${{ matrix.app }}:test
|
||||
cache-from: type=gha,scope=${{ matrix.app }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.app }}
|
||||
|
||||
- name: Build auth image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: apps/auth/Dockerfile
|
||||
push: false
|
||||
tags: primora-auth:test
|
||||
- name: Smoke check ${{ matrix.app }} image contents
|
||||
run: |
|
||||
case "${{ matrix.app }}" in
|
||||
backend) docker run --rm --entrypoint sh primora-backend:test -c 'test -x /usr/local/bin/primora-backend && test -x /usr/local/bin/dbx-mcp' ;;
|
||||
auth) docker run --rm --entrypoint sh primora-auth:test -c 'test -f /workspace/apps/auth/dist/index.js' ;;
|
||||
frontend) docker run --rm --entrypoint sh primora-frontend:test -c 'test -f /usr/share/nginx/html/index.html' ;;
|
||||
esac
|
||||
|
||||
- name: Build frontend image
|
||||
- name: Scan ${{ matrix.app }} image
|
||||
uses: aquasecurity/[email protected]
|
||||
with:
|
||||
image-ref: primora-${{ matrix.app }}:test
|
||||
severity: CRITICAL,HIGH
|
||||
ignore-unfixed: true
|
||||
exit-code: '0'
|
||||
format: table
|
||||
|
||||
docker-publish:
|
||||
name: Publish edge images
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
needs: [docker-build, e2e-smoke]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
app: [backend, auth, frontend]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
# ghcr.io requires lowercase repo paths; the owner login may be mixed-case.
|
||||
- name: Compute lowercase GHCR owner
|
||||
run: echo "GHCR_OWNER=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push edge + sha tags
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: apps/frontend/Dockerfile
|
||||
push: false
|
||||
tags: primora-frontend:test
|
||||
file: apps/${{ matrix.app }}/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ env.GHCR_OWNER }}/primora-${{ matrix.app }}:edge
|
||||
ghcr.io/${{ env.GHCR_OWNER }}/primora-${{ matrix.app }}:sha-${{ github.sha }}
|
||||
cache-from: type=gha,scope=${{ matrix.app }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.app }}
|
||||
|
||||
migrations:
|
||||
name: Migration Up/Down
|
||||
@@ -186,6 +234,12 @@ jobs:
|
||||
- name: Prepare env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Validate compose files
|
||||
run: >-
|
||||
docker compose
|
||||
-f docker-compose.yml -f docker-compose.dev.yml
|
||||
config -q
|
||||
|
||||
- name: Start stack
|
||||
run: >-
|
||||
docker compose
|
||||
|
||||
@@ -39,6 +39,9 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
# ghcr.io requires lowercase repo paths; the owner login may be mixed-case.
|
||||
- name: Compute lowercase GHCR owner
|
||||
run: echo "GHCR_OWNER=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_ENV"
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -52,8 +55,8 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/primora-${{ matrix.app }}:${{ env.VERSION }}
|
||||
ghcr.io/${{ github.repository_owner }}/primora-${{ matrix.app }}:latest
|
||||
ghcr.io/${{ env.GHCR_OWNER }}/primora-${{ matrix.app }}:${{ env.VERSION }}
|
||||
ghcr.io/${{ env.GHCR_OWNER }}/primora-${{ matrix.app }}:latest
|
||||
cache-from: type=gha,scope=${{ matrix.app }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.app }}
|
||||
|
||||
|
||||
@@ -21,11 +21,30 @@ Browser ──▶ Nginx ──▶ Frontend (SolidJS + Vite)
|
||||
- **Generated client** — the TypeScript client is generated from `apps/backend/openapi/openapi.yaml`, so the API contract is the source of truth.
|
||||
- **Demo mode** — a fully client-side workspace (`?demo=true` or `VITE_DEMO_MODE=true`) for trying the UI without a backend.
|
||||
|
||||
## Quick start (Docker)
|
||||
## Install
|
||||
|
||||
Prerequisites: Docker with the Compose plugin.
|
||||
|
||||
### One-liner (no clone)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/Dvorinka/Primora/master/install.sh | bash
|
||||
```
|
||||
|
||||
Installs into `./primora`, generates secrets, and starts the stack on port 80.
|
||||
Non-interactive — configure with env vars:
|
||||
|
||||
```bash
|
||||
DOMAIN=example.com NGINX_PORT=8080 PRIMORA_DIR=/opt/primora bash install.sh
|
||||
```
|
||||
|
||||
The installer pulls prebuilt images from GHCR when they are published;
|
||||
otherwise it downloads the source tarball and builds locally.
|
||||
|
||||
### From a clone
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Dvorinka/Primora.git && cd Primora
|
||||
./scripts/setup.sh # creates .env, generates secrets, starts the stack
|
||||
```
|
||||
|
||||
@@ -33,13 +52,31 @@ or manually:
|
||||
|
||||
```bash
|
||||
cp .env.example .env # fill JWT_SECRET and BETTER_AUTH_SECRET
|
||||
docker compose up -d --build
|
||||
docker compose up -d # builds the three app images from source
|
||||
```
|
||||
|
||||
Then open `http://localhost`. Mailpit is at `http://localhost/mailpit/` for local email.
|
||||
To run published images instead of building:
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/dvorinka/primora-{backend,auth,frontend}:latest
|
||||
docker compose up -d --no-build
|
||||
```
|
||||
|
||||
Then open `http://localhost`. For local email capture, add the dev overlay
|
||||
(`-f docker-compose.dev.yml`) and Mailpit is at `http://localhost/mailpit/`.
|
||||
|
||||
If ports `5432` or `6379` are already in use on your machine, set `POSTGRES_PORT` and `DRAGONFLY_PORT` in `.env` — only the host bindings change.
|
||||
|
||||
### Useful commands
|
||||
|
||||
```bash
|
||||
docker compose ps # service status + health
|
||||
docker compose logs -f backend # follow one service
|
||||
docker compose pull && docker compose up -d # update to latest release
|
||||
docker compose down # stop (data persists in volumes)
|
||||
docker compose down -v # stop and delete all data
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
| Check | Endpoint |
|
||||
|
||||
@@ -29,6 +29,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
backend:
|
||||
image: ghcr.io/dvorinka/primora-backend:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/backend/Dockerfile
|
||||
@@ -56,6 +57,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
auth:
|
||||
image: ghcr.io/dvorinka/primora-auth:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/auth/Dockerfile
|
||||
@@ -81,6 +83,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
image: ghcr.io/dvorinka/primora-frontend:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/frontend/Dockerfile
|
||||
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
# Primora one-line installer.
|
||||
#
|
||||
# curl -fsSL https://raw.githubusercontent.com/Dvorinka/Primora/master/install.sh | bash
|
||||
#
|
||||
# Installs into ./primora (override with PRIMORA_DIR). Non-interactive:
|
||||
# DOMAIN=example.com NGINX_PORT=8080 bash install.sh
|
||||
#
|
||||
# Pulls prebuilt GHCR images when available; otherwise downloads the source
|
||||
# tarball and builds locally. Either way the result is the same compose stack.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO="Dvorinka/Primora"
|
||||
REF="${PRIMORA_REF:-master}"
|
||||
DIR="${PRIMORA_DIR:-primora}"
|
||||
DOMAIN="${DOMAIN:-localhost}"
|
||||
NGINX_PORT="${NGINX_PORT:-80}"
|
||||
|
||||
info() { echo "==> $*"; }
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
command -v docker >/dev/null 2>&1 || die "docker is not installed"
|
||||
docker compose version >/dev/null 2>&1 || die "docker compose plugin is not installed"
|
||||
command -v curl >/dev/null 2>&1 || die "curl is not installed"
|
||||
|
||||
mkdir -p "$DIR/infra/nginx"
|
||||
cd "$DIR"
|
||||
|
||||
# The compose stack needs exactly these three files from the repo.
|
||||
info "Fetching compose files ($REF)"
|
||||
for f in docker-compose.yml .env.example infra/nginx/default.conf; do
|
||||
curl -fsSL "https://raw.githubusercontent.com/$REPO/$REF/$f" -o "$f"
|
||||
done
|
||||
|
||||
if [ ! -f .env ]; then
|
||||
info "Generating .env with random secrets"
|
||||
cp .env.example .env
|
||||
|
||||
JWT_SECRET=$(openssl rand -base64 32)
|
||||
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
||||
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||
DB_PASSWORD=$(openssl rand -hex 16)
|
||||
|
||||
# '|' delimiter: base64 output can contain '/', '+', '='.
|
||||
SED=(sed -i)
|
||||
[[ "$OSTYPE" == darwin* ]] && SED=(sed -i '')
|
||||
|
||||
"${SED[@]}" "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$DB_PASSWORD|" .env
|
||||
"${SED[@]}" "s|postgres://primora:primora@|postgres://primora:$DB_PASSWORD@|" .env
|
||||
"${SED[@]}" "s|^JWT_SECRET=.*|JWT_SECRET=$JWT_SECRET|" .env
|
||||
"${SED[@]}" "s|^BETTER_AUTH_SECRET=.*|BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET|" .env
|
||||
"${SED[@]}" "s|^PRIMORA_ENCRYPTION_KEY=.*|PRIMORA_ENCRYPTION_KEY=$ENCRYPTION_KEY|" .env
|
||||
"${SED[@]}" "s|^NGINX_PORT=.*|NGINX_PORT=$NGINX_PORT|" .env
|
||||
|
||||
if [ "$DOMAIN" != "localhost" ]; then
|
||||
"${SED[@]}" "s|http://localhost|http://$DOMAIN|g" .env
|
||||
"${SED[@]}" "s|^COOKIE_DOMAIN=.*|COOKIE_DOMAIN=$DOMAIN|" .env
|
||||
fi
|
||||
else
|
||||
info "Existing .env kept"
|
||||
fi
|
||||
|
||||
# Compose v5 skips `pull` for services that declare build:, so fetch the
|
||||
# prebuilt images directly. --no-build keeps `up` from rebuilding them.
|
||||
GHCR_OWNER=$(echo "$REPO" | cut -d/ -f1 | tr '[:upper:]' '[:lower:]')
|
||||
if docker pull "ghcr.io/$GHCR_OWNER/primora-backend:latest" \
|
||||
&& docker pull "ghcr.io/$GHCR_OWNER/primora-auth:latest" \
|
||||
&& docker pull "ghcr.io/$GHCR_OWNER/primora-frontend:latest"; then
|
||||
info "Starting prebuilt images"
|
||||
docker compose up -d --no-build
|
||||
else
|
||||
# GHCR packages are private until first published release — build from source.
|
||||
info "Prebuilt images unavailable; building from source"
|
||||
curl -fsSL "https://codeload.github.com/$REPO/tar.gz/refs/heads/$REF" | tar xz --strip-components=1
|
||||
docker compose up -d --build
|
||||
fi
|
||||
|
||||
DISPLAY_URL="http://$DOMAIN"
|
||||
[ "$NGINX_PORT" != "80" ] && DISPLAY_URL="$DISPLAY_URL:$NGINX_PORT"
|
||||
|
||||
info "Primora is starting"
|
||||
echo " Dashboard: $DISPLAY_URL"
|
||||
echo " Health: $DISPLAY_URL/api/v1/health/liveness"
|
||||
echo " Logs: cd $DIR && docker compose logs -f"
|
||||
echo " Stop: cd $DIR && docker compose down"
|
||||
Reference in new issue
Block a user