Add a stage branch deploy beside production on z00.
Pushing stage runs CI and deploys to stage.transcribe.z00.nu with its own data, Reverb credentials, and Caddy network.
This commit is contained in:
+9
-1
@@ -66,7 +66,7 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
# Laravel Reverb (WebSockets). Browser uses VITE_*; server publish uses REVERB_HOST.
|
||||
# Laravel Reverb (WebSockets). Browser uses Blade meta (reverb.client) with VITE_* fallback.
|
||||
REVERB_APP_ID=andytranscribe
|
||||
REVERB_APP_KEY=andytranscribe-key
|
||||
REVERB_APP_SECRET=andytranscribe-secret
|
||||
@@ -75,11 +75,19 @@ REVERB_PORT=8080
|
||||
REVERB_SCHEME=http
|
||||
REVERB_SERVER_HOST=0.0.0.0
|
||||
REVERB_SERVER_PORT=8080
|
||||
# Public Echo target (z00 stage/prod set these in the instance .env).
|
||||
# REVERB_PUBLIC_HOST=reverb.transcribe.z00.nu
|
||||
# REVERB_PUBLIC_PORT=443
|
||||
# REVERB_PUBLIC_SCHEME=https
|
||||
|
||||
# Host ports for docker compose (FrankenPHP app, Reverb WS, Whisper)
|
||||
APP_HOST_PORT=8080
|
||||
REVERB_HOST_PORT=8081
|
||||
WHISPER_HOST_PORT=8090
|
||||
# Unique per Compose instance on a shared Docker host (prod default keeps existing names).
|
||||
# CONTAINER_PREFIX=andytranscribe
|
||||
# CADDY_NETWORK=caddy-proxy-manager-test_caddy-test-network
|
||||
# PUBLIC_APP_URL=https://transcribe.z00.nu
|
||||
|
||||
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
||||
VITE_REVERB_HOST=localhost
|
||||
|
||||
+55
-3
@@ -4,12 +4,13 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stage
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: gitea.z00.nu
|
||||
# Baked into the Vite client bundle for production WebSockets.
|
||||
# Fallback baked into the Vite bundle; public pages prefer Blade meta tags.
|
||||
VITE_REVERB_HOST: reverb.transcribe.z00.nu
|
||||
VITE_REVERB_PORT: "443"
|
||||
VITE_REVERB_SCHEME: https
|
||||
@@ -47,6 +48,18 @@ jobs:
|
||||
APP_KEY="base64:dGVzdC1hcHAta2V5LWZvci1jaS1jb21wb3NlLXZhbGlkYXRpb24=" \
|
||||
docker compose -f docker-compose.yml -f compose.z00.yaml config --quiet
|
||||
|
||||
- name: Validate stage compose
|
||||
run: |
|
||||
set -euo pipefail
|
||||
APP_IMAGE="${REGISTRY}/$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]'):test" \
|
||||
APP_KEY="base64:dGVzdC1hcHAta2V5LWZvci1jaS1jb21wb3NlLXZhbGlkYXRpb24=" \
|
||||
PUBLIC_APP_URL="https://stage.transcribe.z00.nu" \
|
||||
APP_URL="https://stage.transcribe.z00.nu" \
|
||||
REVERB_PUBLIC_HOST="reverb.stage.transcribe.z00.nu" \
|
||||
CONTAINER_PREFIX="andytranscribe-stage" \
|
||||
CADDY_NETWORK="andytranscribe-stage-caddy" \
|
||||
docker compose -f docker-compose.yml -f compose.z00.yaml config --quiet
|
||||
|
||||
build-and-push:
|
||||
if: gitea.event_name != 'pull_request'
|
||||
needs: test
|
||||
@@ -70,6 +83,7 @@ jobs:
|
||||
REPO_LC="$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')"
|
||||
TAG_SHA="${REGISTRY}/${REPO_LC}:${{ gitea.sha }}"
|
||||
TAG_LATEST="${REGISTRY}/${REPO_LC}:latest"
|
||||
TAG_STAGE="${REGISTRY}/${REPO_LC}:stage"
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "${REGISTRY}" -u "${{ gitea.actor }}" --password-stdin
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
--build-arg VITE_APP_NAME=AndyTranscribe \
|
||||
@@ -78,10 +92,15 @@ jobs:
|
||||
--build-arg "VITE_REVERB_PORT=${VITE_REVERB_PORT}" \
|
||||
--build-arg "VITE_REVERB_SCHEME=${VITE_REVERB_SCHEME}" \
|
||||
-t "${TAG_SHA}" \
|
||||
-t "${TAG_LATEST}" \
|
||||
.
|
||||
docker push "${TAG_SHA}"
|
||||
docker push "${TAG_LATEST}"
|
||||
if [ "${{ gitea.ref }}" = "refs/heads/main" ]; then
|
||||
docker tag "${TAG_SHA}" "${TAG_LATEST}"
|
||||
docker push "${TAG_LATEST}"
|
||||
elif [ "${{ gitea.ref }}" = "refs/heads/stage" ]; then
|
||||
docker tag "${TAG_SHA}" "${TAG_STAGE}"
|
||||
docker push "${TAG_STAGE}"
|
||||
fi
|
||||
|
||||
deploy:
|
||||
if: gitea.ref == 'refs/heads/main' && gitea.event_name != 'pull_request'
|
||||
@@ -115,3 +134,36 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
./scripts/deploy-production.sh
|
||||
|
||||
deploy-stage:
|
||||
if: gitea.ref == 'refs/heads/stage' && gitea.event_name != 'pull_request'
|
||||
needs: build-and-push
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
set -euo pipefail
|
||||
HOST="${{ gitea.server_url }}"
|
||||
HOST="${HOST#https://}"
|
||||
HOST="${HOST#http://}"
|
||||
git clone --depth 1 \
|
||||
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@${HOST}/${{ gitea.repository }}.git" \
|
||||
.
|
||||
git fetch --depth 1 origin "${{ gitea.sha }}"
|
||||
git checkout --force "${{ gitea.sha }}"
|
||||
|
||||
- name: Deploy stage
|
||||
env:
|
||||
DEPLOY_PATHS: ${{ secrets.STAGE_DEPLOY_PATHS }}
|
||||
DEPLOY_SHA: ${{ gitea.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REPO_LC="$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')"
|
||||
export APP_IMAGE="${REGISTRY}/${REPO_LC}:${{ gitea.sha }}"
|
||||
chmod +x scripts/deploy-production.sh
|
||||
if [ -z "${DEPLOY_PATHS:-}" ]; then
|
||||
echo "STAGE_DEPLOY_PATHS secret is not set; skipping deploy."
|
||||
echo "Built image: ${APP_IMAGE}"
|
||||
exit 0
|
||||
fi
|
||||
./scripts/deploy-production.sh
|
||||
|
||||
@@ -157,24 +157,47 @@ Open [http://localhost:8080](http://localhost:8080) as usual. After changing Com
|
||||
|
||||
## CI/CD (Gitea Actions)
|
||||
|
||||
On push to `main`, Gitea Actions (host runner on z00):
|
||||
Gitea Actions (host runner on z00) on push:
|
||||
|
||||
1. Runs PHPUnit (+ compose config check)
|
||||
2. Builds and pushes `gitea.z00.nu/ben/andytranscribe:<sha>` (+ `:latest`) — Vite assets are baked in the image build
|
||||
3. Deploys by hard-resetting `~/andyTranscibe` to that SHA and pulling the image (`docker-compose.yml` + `compose.z00.yaml`)
|
||||
| Branch | Image tags | Deploy target |
|
||||
| --- | --- | --- |
|
||||
| `main` | `<sha>` and `:latest` | `~/andyTranscibe` → https://transcribe.z00.nu |
|
||||
| `stage` | `<sha>` and `:stage` | `~/andyTranscibe-stage` → https://stage.transcribe.z00.nu |
|
||||
|
||||
Do not hot-patch production containers or the deploy checkout. Fix in git and push to `main` so CI deploys.
|
||||
Both runs:
|
||||
|
||||
1. PHPUnit (+ compose config check for prod and stage env)
|
||||
2. Build and push `gitea.z00.nu/ben/andytranscribe:<sha>` — Vite assets are baked in; browser Reverb host/key come from Blade meta at runtime
|
||||
3. Hard-reset the matching checkout and pull the image (`docker-compose.yml` + `compose.z00.yaml`)
|
||||
|
||||
Stage is a separate Compose project: own SQLite, uploads, `APP_KEY`, Reverb credentials, container names (`andytranscribe-stage-*`), host ports, and Caddy Docker network (`andytranscribe-stage-caddy`). Do not copy prod `.env` or data into the stage directory.
|
||||
|
||||
Do not hot-patch production or staging containers or their deploy checkouts. Fix in git; push `stage` to preview, then `main` to ship.
|
||||
|
||||
One-time server bootstrap (secrets + registry login):
|
||||
|
||||
```bash
|
||||
./scripts/setup-gitea-ci.sh
|
||||
./scripts/setup-stage.sh
|
||||
```
|
||||
|
||||
`setup-stage.sh` clones `$HOME/andyTranscibe-stage`, writes a unique `.env`, creates `andytranscribe-stage-caddy`, and attaches Caddy Proxy Manager to that network. You still need DNS for `stage.transcribe.z00.nu` and `reverb.stage.transcribe.z00.nu`, plus Proxy Manager hosts:
|
||||
|
||||
- `stage.transcribe.z00.nu` → `andytranscribe-stage-app:80`
|
||||
- `reverb.stage.transcribe.z00.nu` → `andytranscribe-stage-reverb:8080`
|
||||
|
||||
Create and push the branch after the workflow file is on the default branch:
|
||||
|
||||
```bash
|
||||
git checkout -b stage
|
||||
git push -u origin stage
|
||||
```
|
||||
|
||||
Manual deploy of an already-built tag:
|
||||
|
||||
```bash
|
||||
APP_IMAGE=gitea.z00.nu/ben/andytranscribe:<sha> DEPLOY_PATHS=$HOME/andyTranscibe ./scripts/deploy-production.sh
|
||||
APP_IMAGE=gitea.z00.nu/ben/andytranscribe:<sha> DEPLOY_PATHS=$HOME/andyTranscibe-stage ./scripts/deploy-production.sh
|
||||
```
|
||||
|
||||
## Services and ports
|
||||
@@ -209,17 +232,17 @@ Edit `.env` before `docker compose up` when you need different ports or models:
|
||||
| `APP_HOST_PORT` | Host port for the web app | `8080` |
|
||||
| `REVERB_HOST_PORT` | Host port for WebSockets | `8081` |
|
||||
| `WHISPER_HOST_PORT` | Host port for Whisper | `8090` |
|
||||
| `REVERB_PUBLIC_HOST` | Browser WebSocket host (Blade meta) | `REVERB_HOST` |
|
||||
| `CONTAINER_PREFIX` | Docker `container_name` prefix | `andytranscribe` |
|
||||
| `CADDY_NETWORK` | External Caddy network (z00 overlay) | `caddy-proxy-manager-test_caddy-test-network` |
|
||||
| `PUBLIC_APP_URL` | Public URL injected by the z00 overlay (unset = prod) | `https://transcribe.z00.nu` |
|
||||
| `LOCAL_WHISPER_MODEL` | Whisper model id | `Systran/faster-whisper-base` |
|
||||
| `TRANSCRIPTION_TIMEOUT` | Job/HTTP timeout (seconds). Hung Whisper calls fail the job; UI can restart. | `600` |
|
||||
| `DB_QUEUE_RETRY_AFTER` | Must exceed `TRANSCRIPTION_TIMEOUT` | `660` |
|
||||
|
||||
Inside Compose, Laravel talks to Whisper at `http://whisper:8000/v1` and publishes broadcasts to the `reverb` service. The browser connects to Reverb on `localhost:8081`.
|
||||
Inside Compose, Laravel talks to Whisper at `http://whisper:8000/v1` and publishes broadcasts to the `reverb` service. The browser connects using Reverb settings from the HTML meta tags (with Vite `VITE_REVERB_*` as a local fallback).
|
||||
|
||||
If you change `REVERB_APP_KEY` or browser-facing Reverb host/port settings, rebuild so Vite embeds the new values:
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
If you change `REVERB_APP_KEY` or browser-facing Reverb host/port settings, restart the app container so Blade picks up the new values. An image rebuild is only needed when Vite-baked fallbacks must change.
|
||||
|
||||
### GPU Whisper (optional)
|
||||
|
||||
|
||||
+16
-6
@@ -1,4 +1,5 @@
|
||||
# z00 production overlay for AndyTranscribe (behind Caddy Proxy Manager)
|
||||
# z00 overlay for AndyTranscribe (behind Caddy Proxy Manager).
|
||||
# Instance .env supplies PUBLIC_APP_URL, REVERB_PUBLIC_HOST, CADDY_NETWORK, CONTAINER_PREFIX.
|
||||
services:
|
||||
app:
|
||||
networks:
|
||||
@@ -7,30 +8,39 @@ services:
|
||||
environment:
|
||||
APP_ENV: production
|
||||
APP_DEBUG: "false"
|
||||
APP_URL: https://transcribe.z00.nu
|
||||
APP_URL: ${PUBLIC_APP_URL:-https://transcribe.z00.nu}
|
||||
TRUSTED_PROXIES: "*"
|
||||
REVERB_HOST: reverb
|
||||
REVERB_PORT: "8080"
|
||||
REVERB_SCHEME: http
|
||||
REVERB_PUBLIC_HOST: ${REVERB_PUBLIC_HOST:-reverb.transcribe.z00.nu}
|
||||
REVERB_PUBLIC_PORT: "443"
|
||||
REVERB_PUBLIC_SCHEME: https
|
||||
reverb:
|
||||
networks:
|
||||
- default
|
||||
- caddy
|
||||
environment:
|
||||
APP_URL: https://transcribe.z00.nu
|
||||
REVERB_HOST: reverb.transcribe.z00.nu
|
||||
APP_URL: ${PUBLIC_APP_URL:-https://transcribe.z00.nu}
|
||||
REVERB_HOST: ${REVERB_PUBLIC_HOST:-reverb.transcribe.z00.nu}
|
||||
REVERB_PORT: "443"
|
||||
REVERB_SCHEME: https
|
||||
REVERB_PUBLIC_HOST: ${REVERB_PUBLIC_HOST:-reverb.transcribe.z00.nu}
|
||||
REVERB_PUBLIC_PORT: "443"
|
||||
REVERB_PUBLIC_SCHEME: https
|
||||
queue:
|
||||
networks:
|
||||
- default
|
||||
environment:
|
||||
APP_ENV: production
|
||||
APP_DEBUG: "false"
|
||||
APP_URL: https://transcribe.z00.nu
|
||||
APP_URL: ${PUBLIC_APP_URL:-https://transcribe.z00.nu}
|
||||
REVERB_HOST: reverb
|
||||
REVERB_PORT: "8080"
|
||||
REVERB_SCHEME: http
|
||||
REVERB_PUBLIC_HOST: ${REVERB_PUBLIC_HOST:-reverb.transcribe.z00.nu}
|
||||
REVERB_PUBLIC_PORT: "443"
|
||||
REVERB_PUBLIC_SCHEME: https
|
||||
whisper:
|
||||
networks:
|
||||
- default
|
||||
@@ -38,4 +48,4 @@ services:
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
||||
name: caddy-proxy-manager-test_caddy-test-network
|
||||
name: ${CADDY_NETWORK:-caddy-proxy-manager-test_caddy-test-network}
|
||||
|
||||
@@ -15,6 +15,24 @@ return [
|
||||
|
||||
'default' => env('REVERB_SERVER', 'reverb'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Browser (Echo) connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Public WebSocket host/key rendered into HTML so one image can serve
|
||||
| production and staging. Server-side publish still uses REVERB_HOST
|
||||
| (Docker DNS "reverb" behind the z00 overlay).
|
||||
|
|
||||
*/
|
||||
|
||||
'client' => [
|
||||
'key' => env('REVERB_APP_KEY'),
|
||||
'host' => env('REVERB_PUBLIC_HOST', env('REVERB_HOST')),
|
||||
'port' => env('REVERB_PUBLIC_PORT', env('REVERB_PORT', 443)),
|
||||
'scheme' => env('REVERB_PUBLIC_SCHEME', env('REVERB_SCHEME', 'https')),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Reverb Servers
|
||||
|
||||
+9
-5
@@ -30,6 +30,10 @@ x-app-env: &app-env
|
||||
REVERB_SCHEME: http
|
||||
REVERB_SERVER_HOST: 0.0.0.0
|
||||
REVERB_SERVER_PORT: 8080
|
||||
# Browser-facing Reverb (Blade meta). Overlay sets public host for z00.
|
||||
REVERB_PUBLIC_HOST: ${REVERB_PUBLIC_HOST:-localhost}
|
||||
REVERB_PUBLIC_PORT: ${REVERB_PUBLIC_PORT:-8081}
|
||||
REVERB_PUBLIC_SCHEME: ${REVERB_PUBLIC_SCHEME:-http}
|
||||
LOCAL_WHISPER_URL: http://whisper:8000/v1
|
||||
LOCAL_WHISPER_API_KEY: ${LOCAL_WHISPER_API_KEY:-not-needed}
|
||||
LOCAL_WHISPER_MODEL: ${LOCAL_WHISPER_MODEL:-Systran/faster-whisper-base}
|
||||
@@ -56,7 +60,7 @@ services:
|
||||
VITE_REVERB_HOST: ${VITE_REVERB_HOST:-localhost}
|
||||
VITE_REVERB_PORT: ${REVERB_HOST_PORT:-8081}
|
||||
VITE_REVERB_SCHEME: ${VITE_REVERB_SCHEME:-http}
|
||||
container_name: andytranscribe-app
|
||||
container_name: ${CONTAINER_PREFIX:-andytranscribe}-app
|
||||
ports:
|
||||
- "${APP_HOST_PORT:-8080}:80"
|
||||
environment:
|
||||
@@ -81,7 +85,7 @@ services:
|
||||
# `docker compose up --build` builds `app` first, then starts these with the tagged image.
|
||||
queue:
|
||||
<<: *app-image
|
||||
container_name: andytranscribe-queue
|
||||
container_name: ${CONTAINER_PREFIX:-andytranscribe}-queue
|
||||
command:
|
||||
- php
|
||||
- artisan
|
||||
@@ -108,7 +112,7 @@ services:
|
||||
|
||||
reverb:
|
||||
<<: *app-image
|
||||
container_name: andytranscribe-reverb
|
||||
container_name: ${CONTAINER_PREFIX:-andytranscribe}-reverb
|
||||
command:
|
||||
- php
|
||||
- artisan
|
||||
@@ -132,7 +136,7 @@ services:
|
||||
|
||||
whisper:
|
||||
image: fedirz/faster-whisper-server:latest-cpu
|
||||
container_name: andytranscribe-whisper
|
||||
container_name: ${CONTAINER_PREFIX:-andytranscribe}-whisper
|
||||
ports:
|
||||
# Host 8090 avoids clashing with the FrankenPHP app on 8080
|
||||
- "${WHISPER_HOST_PORT:-8090}:8000"
|
||||
@@ -153,7 +157,7 @@ services:
|
||||
whisper-gpu:
|
||||
profiles: ["gpu"]
|
||||
image: fedirz/faster-whisper-server:latest-cuda
|
||||
container_name: andytranscribe-whisper-gpu
|
||||
container_name: ${CONTAINER_PREFIX:-andytranscribe}-whisper-gpu
|
||||
ports:
|
||||
- "${WHISPER_HOST_PORT:-8090}:8000"
|
||||
volumes:
|
||||
|
||||
@@ -32,5 +32,9 @@
|
||||
<env name="PULSE_ENABLED" value="false"/>
|
||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||
<env name="NIGHTWATCH_ENABLED" value="false"/>
|
||||
<env name="REVERB_APP_KEY" value="testing-reverb-key" force="true"/>
|
||||
<env name="REVERB_PUBLIC_HOST" value="reverb.testing.example" force="true"/>
|
||||
<env name="REVERB_PUBLIC_PORT" value="443" force="true"/>
|
||||
<env name="REVERB_PUBLIC_SCHEME" value="https" force="true"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
||||
+28
-5
@@ -3,13 +3,36 @@ import Echo from 'laravel-echo';
|
||||
import Pusher from 'pusher-js';
|
||||
window.Pusher = Pusher;
|
||||
|
||||
function metaContent(name) {
|
||||
return document.querySelector(`meta[name="${name}"]`)?.getAttribute('content')?.trim() || '';
|
||||
}
|
||||
|
||||
const isLocalHost = ['localhost', '127.0.0.1'].includes(window.location.hostname);
|
||||
const internalHosts = new Set(['', 'reverb', 'localhost', '127.0.0.1']);
|
||||
|
||||
const key = metaContent('reverb-key') || import.meta.env.VITE_REVERB_APP_KEY;
|
||||
|
||||
let host = metaContent('reverb-host') || import.meta.env.VITE_REVERB_HOST || '';
|
||||
let port = metaContent('reverb-port') || import.meta.env.VITE_REVERB_PORT || '';
|
||||
let scheme = metaContent('reverb-scheme') || import.meta.env.VITE_REVERB_SCHEME || 'https';
|
||||
|
||||
if (!isLocalHost && internalHosts.has(host)) {
|
||||
host = `reverb.${window.location.hostname}`;
|
||||
port = '443';
|
||||
scheme = 'https';
|
||||
} else if (isLocalHost && host === 'reverb') {
|
||||
host = import.meta.env.VITE_REVERB_HOST || 'localhost';
|
||||
port = import.meta.env.VITE_REVERB_PORT || '8081';
|
||||
scheme = import.meta.env.VITE_REVERB_SCHEME || 'http';
|
||||
}
|
||||
|
||||
window.Echo = new Echo({
|
||||
broadcaster: 'reverb',
|
||||
key: import.meta.env.VITE_REVERB_APP_KEY,
|
||||
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
|
||||
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
|
||||
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
|
||||
key,
|
||||
wsHost: host,
|
||||
wsPort: port || 80,
|
||||
wssPort: port || 443,
|
||||
forceTLS: scheme === 'https',
|
||||
enabledTransports: ['ws', 'wss'],
|
||||
authEndpoint: '/broadcasting/auth',
|
||||
auth: {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="reverb-key" content="{{ config('reverb.client.key') }}">
|
||||
<meta name="reverb-host" content="{{ config('reverb.client.host') }}">
|
||||
<meta name="reverb-port" content="{{ config('reverb.client.port') }}">
|
||||
<meta name="reverb-scheme" content="{{ config('reverb.client.scheme') }}">
|
||||
|
||||
<title>
|
||||
{{ filled($title ?? null) ? $title.' — '.config('app.name', 'AndyTranscribe') : config('app.name', 'AndyTranscribe') }}
|
||||
|
||||
@@ -8,6 +8,7 @@ REPO_OWNER="${REPO_OWNER:-ben}"
|
||||
REPO_NAME="${REPO_NAME:-AndyTranscribe}"
|
||||
REGISTRY_HOST="${REGISTRY_HOST:-gitea.z00.nu}"
|
||||
DEPLOY_PATH="${DEPLOY_PATH:-${HOME}/andyTranscibe}"
|
||||
STAGE_DEPLOY_PATH="${STAGE_DEPLOY_PATH:-${HOME}/andyTranscibe-stage}"
|
||||
CREDENTIALS_FILE="${GITEA_DIR}/.credentials"
|
||||
|
||||
if [[ ! -f "${CREDENTIALS_FILE}" ]]; then
|
||||
@@ -57,6 +58,7 @@ set_secret() {
|
||||
|
||||
set_secret "REGISTRY_TOKEN" "${CI_TOKEN}"
|
||||
set_secret "DEPLOY_PATHS" "${DEPLOY_PATH}"
|
||||
set_secret "STAGE_DEPLOY_PATHS" "${STAGE_DEPLOY_PATH}"
|
||||
|
||||
printf '%s' "${PULL_TOKEN}" | docker login "${REGISTRY_HOST}" -u "${ADMIN_USERNAME}" --password-stdin
|
||||
|
||||
@@ -77,4 +79,6 @@ fi
|
||||
|
||||
echo "Gitea CI secrets configured for ${REGISTRY_HOST}/${REPO_OWNER}/${REPO_NAME}"
|
||||
echo "Deploy path: ${DEPLOY_PATH}"
|
||||
echo "Push to main to build, push the image, and deploy."
|
||||
echo "Stage deploy path: ${STAGE_DEPLOY_PATH}"
|
||||
echo "Push to main to deploy production; push to stage to deploy https://stage.transcribe.z00.nu"
|
||||
echo "First-time stage instance: ./scripts/setup-stage.sh"
|
||||
|
||||
Executable
+173
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-time bootstrap for the AndyTranscribe staging instance on z00.
|
||||
# Creates ~/andyTranscibe-stage, a unique .env, the stage Caddy Docker network,
|
||||
# and the Gitea STAGE_DEPLOY_PATHS secret.
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_DIR="${GITEA_DIR:-${HOME}/gitea}"
|
||||
REPO_OWNER="${REPO_OWNER:-ben}"
|
||||
REPO_NAME="${REPO_NAME:-AndyTranscribe}"
|
||||
REGISTRY_HOST="${REGISTRY_HOST:-gitea.z00.nu}"
|
||||
PROD_PATH="${PROD_PATH:-${HOME}/andyTranscibe}"
|
||||
STAGE_PATH="${STAGE_PATH:-${HOME}/andyTranscibe-stage}"
|
||||
STAGE_URL="${STAGE_URL:-https://stage.transcribe.z00.nu}"
|
||||
REVERB_PUBLIC_HOST="${REVERB_PUBLIC_HOST:-reverb.stage.transcribe.z00.nu}"
|
||||
CONTAINER_PREFIX="${CONTAINER_PREFIX:-andytranscribe-stage}"
|
||||
CADDY_NETWORK="${CADDY_NETWORK:-andytranscribe-stage-caddy}"
|
||||
PROD_CADDY_NETWORK="${PROD_CADDY_NETWORK:-caddy-proxy-manager-test_caddy-test-network}"
|
||||
CADDY_CONTAINER="${CADDY_CONTAINER:-}"
|
||||
CREDENTIALS_FILE="${GITEA_DIR}/.credentials"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
upsert_env() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
python3 - "$file" "$key" "$value" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
|
||||
path, key, value = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
text = open(path).read()
|
||||
pattern = re.compile(r"^" + re.escape(key) + r"=.*$", re.M)
|
||||
replacement = f"{key}={value}"
|
||||
if pattern.search(text):
|
||||
text = pattern.sub(replacement, text, count=1)
|
||||
else:
|
||||
text = text.rstrip("\n") + "\n" + replacement + "\n"
|
||||
open(path, "w").write(text)
|
||||
PY
|
||||
}
|
||||
|
||||
if [[ ! -f "${CREDENTIALS_FILE}" ]]; then
|
||||
echo "Missing ${CREDENTIALS_FILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "${CREDENTIALS_FILE}"
|
||||
|
||||
API="https://${REGISTRY_HOST}/api/v1"
|
||||
AUTH=(-u "${ADMIN_USERNAME}:${ADMIN_PASSWORD}")
|
||||
|
||||
if ! curl -fsS "${AUTH[@]}" "${API}/repos/${REPO_OWNER}/${REPO_NAME}" >/dev/null 2>&1; then
|
||||
echo "Repository ${REPO_OWNER}/${REPO_NAME} not found on ${REGISTRY_HOST}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${STAGE_PATH}/.git" ]]; then
|
||||
mkdir -p "$(dirname "${STAGE_PATH}")"
|
||||
if [[ -d "${PROD_PATH}/.git" ]]; then
|
||||
git clone "${PROD_PATH}" "${STAGE_PATH}"
|
||||
else
|
||||
git clone "https://${REGISTRY_HOST}/${REPO_OWNER}/${REPO_NAME}.git" "${STAGE_PATH}"
|
||||
fi
|
||||
fi
|
||||
|
||||
(
|
||||
cd "${STAGE_PATH}"
|
||||
git fetch origin || true
|
||||
if git rev-parse --verify origin/stage >/dev/null 2>&1; then
|
||||
git checkout stage
|
||||
git pull --ff-only origin stage || true
|
||||
else
|
||||
echo "Remote branch origin/stage not found yet; leaving $(git branch --show-current)."
|
||||
fi
|
||||
)
|
||||
|
||||
mkdir -p \
|
||||
"${STAGE_PATH}/database" \
|
||||
"${STAGE_PATH}/storage/app/private/recordings" \
|
||||
"${STAGE_PATH}/storage/app/public" \
|
||||
"${STAGE_PATH}/storage/framework/cache" \
|
||||
"${STAGE_PATH}/storage/framework/sessions" \
|
||||
"${STAGE_PATH}/storage/framework/views" \
|
||||
"${STAGE_PATH}/storage/logs" \
|
||||
"${STAGE_PATH}/bootstrap/cache"
|
||||
|
||||
if [[ ! -f "${STAGE_PATH}/database/database.sqlite" ]]; then
|
||||
touch "${STAGE_PATH}/database/database.sqlite"
|
||||
fi
|
||||
|
||||
ENV_SRC="${STAGE_PATH}/.env.example"
|
||||
if [[ ! -f "${ENV_SRC}" ]]; then
|
||||
ENV_SRC="${REPO_ROOT}/.env.example"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${STAGE_PATH}/.env" ]]; then
|
||||
cp "${ENV_SRC}" "${STAGE_PATH}/.env"
|
||||
upsert_env "${STAGE_PATH}/.env" "APP_ENV" "production"
|
||||
upsert_env "${STAGE_PATH}/.env" "APP_DEBUG" "false"
|
||||
upsert_env "${STAGE_PATH}/.env" "APP_KEY" "base64:$(openssl rand -base64 32)"
|
||||
upsert_env "${STAGE_PATH}/.env" "APP_URL" "${STAGE_URL}"
|
||||
upsert_env "${STAGE_PATH}/.env" "PUBLIC_APP_URL" "${STAGE_URL}"
|
||||
upsert_env "${STAGE_PATH}/.env" "APP_HOST_PORT" "18180"
|
||||
upsert_env "${STAGE_PATH}/.env" "REVERB_HOST_PORT" "18181"
|
||||
upsert_env "${STAGE_PATH}/.env" "WHISPER_HOST_PORT" "18190"
|
||||
upsert_env "${STAGE_PATH}/.env" "REVERB_APP_ID" "$(openssl rand -hex 8)"
|
||||
upsert_env "${STAGE_PATH}/.env" "REVERB_APP_KEY" "$(openssl rand -hex 16)"
|
||||
upsert_env "${STAGE_PATH}/.env" "REVERB_APP_SECRET" "$(openssl rand -hex 20)"
|
||||
upsert_env "${STAGE_PATH}/.env" "REVERB_PUBLIC_HOST" "${REVERB_PUBLIC_HOST}"
|
||||
upsert_env "${STAGE_PATH}/.env" "REVERB_PUBLIC_PORT" "443"
|
||||
upsert_env "${STAGE_PATH}/.env" "REVERB_PUBLIC_SCHEME" "https"
|
||||
upsert_env "${STAGE_PATH}/.env" "CONTAINER_PREFIX" "${CONTAINER_PREFIX}"
|
||||
upsert_env "${STAGE_PATH}/.env" "CADDY_NETWORK" "${CADDY_NETWORK}"
|
||||
echo "Wrote ${STAGE_PATH}/.env"
|
||||
else
|
||||
echo "Keeping existing ${STAGE_PATH}/.env"
|
||||
fi
|
||||
|
||||
if ! docker network inspect "${CADDY_NETWORK}" >/dev/null 2>&1; then
|
||||
docker network create "${CADDY_NETWORK}"
|
||||
echo "Created Docker network ${CADDY_NETWORK}"
|
||||
fi
|
||||
|
||||
if [[ -z "${CADDY_CONTAINER}" ]]; then
|
||||
CADDY_CONTAINER="$(
|
||||
docker network inspect "${PROD_CADDY_NETWORK}" \
|
||||
--format '{{range .Containers}}{{.Name}}{{"\n"}}{{end}}' \
|
||||
| grep -i caddy \
|
||||
| head -1 || true
|
||||
)"
|
||||
fi
|
||||
|
||||
if [[ -z "${CADDY_CONTAINER}" ]]; then
|
||||
echo "Could not find a Caddy container on ${PROD_CADDY_NETWORK}." >&2
|
||||
echo "Set CADDY_CONTAINER and re-run: docker network connect ${CADDY_NETWORK} <caddy-container>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if docker network inspect "${CADDY_NETWORK}" --format '{{range .Containers}}{{.Name}}{{"\n"}}{{end}}' | grep -qx "${CADDY_CONTAINER}"; then
|
||||
echo "Caddy container ${CADDY_CONTAINER} already on ${CADDY_NETWORK}"
|
||||
else
|
||||
docker network connect "${CADDY_NETWORK}" "${CADDY_CONTAINER}"
|
||||
echo "Attached ${CADDY_CONTAINER} to ${CADDY_NETWORK}"
|
||||
fi
|
||||
|
||||
set_secret() {
|
||||
local name="$1"
|
||||
local value="$2"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
python3 -c 'import json,sys; json.dump({"data": sys.argv[1]}, open(sys.argv[2], "w"))' "${value}" "${tmp}"
|
||||
curl -fsS "${AUTH[@]}" -X PUT \
|
||||
"${API}/repos/${REPO_OWNER}/${REPO_NAME}/actions/secrets/${name}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @"${tmp}" >/dev/null
|
||||
rm -f "${tmp}"
|
||||
}
|
||||
|
||||
set_secret "STAGE_DEPLOY_PATHS" "${STAGE_PATH}"
|
||||
|
||||
echo "Stage instance ready at ${STAGE_PATH}"
|
||||
echo "Public URL: ${STAGE_URL}"
|
||||
echo "Reverb host: ${REVERB_PUBLIC_HOST}"
|
||||
echo "Caddy network: ${CADDY_NETWORK} (container ${CADDY_CONTAINER})"
|
||||
echo "Gitea secret STAGE_DEPLOY_PATHS=${STAGE_PATH}"
|
||||
echo
|
||||
echo "Still required: DNS for stage.transcribe.z00.nu and ${REVERB_PUBLIC_HOST},"
|
||||
echo "plus Caddy Proxy Manager hosts pointing at ${CONTAINER_PREFIX}-app:80"
|
||||
echo "and ${CONTAINER_PREFIX}-reverb:8080."
|
||||
echo "Then: git checkout -b stage && git push -u origin stage"
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
class ReverbClientConfigTest extends TestCase
|
||||
{
|
||||
public function test_login_page_exposes_environment_reverb_client_settings(): void
|
||||
{
|
||||
$this->get(route('login'))
|
||||
->assertOk()
|
||||
->assertSee('<meta name="reverb-key" content="testing-reverb-key">', false)
|
||||
->assertSee('<meta name="reverb-host" content="reverb.testing.example">', false)
|
||||
->assertSee('<meta name="reverb-port" content="443">', false)
|
||||
->assertSee('<meta name="reverb-scheme" content="https">', false);
|
||||
}
|
||||
|
||||
public function test_login_page_reflects_runtime_reverb_client_config(): void
|
||||
{
|
||||
config([
|
||||
'reverb.client.key' => 'stage-reverb-key',
|
||||
'reverb.client.host' => 'reverb.stage.transcribe.z00.nu',
|
||||
'reverb.client.port' => '443',
|
||||
'reverb.client.scheme' => 'https',
|
||||
]);
|
||||
|
||||
$this->get(route('login'))
|
||||
->assertOk()
|
||||
->assertSee('<meta name="reverb-key" content="stage-reverb-key">', false)
|
||||
->assertSee('<meta name="reverb-host" content="reverb.stage.transcribe.z00.nu">', false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user