Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7955bed4a9 | ||
|
|
66f3cc1380 | ||
|
|
19de9fcf75 |
Vendored
+148
@@ -0,0 +1,148 @@
|
|||||||
|
// Jenkinsfile — build + publish both images to the thinkserver:5000 registry.
|
||||||
|
//
|
||||||
|
// ttrpg-tracker-firebase : root Dockerfile, nginx serving the static SPA,
|
||||||
|
// Firebase storage (config baked in at build time).
|
||||||
|
// ttrpg-tracker-sqlite : docker/Dockerfile, Caddy + Node, server/SQLite
|
||||||
|
// storage (REACT_APP_STORAGE=server).
|
||||||
|
//
|
||||||
|
// Requirements on the Jenkins agent:
|
||||||
|
// * Docker CLI available, agent user in the docker group (or DooD socket).
|
||||||
|
// * thinkserver:5000 reachable and trusted by the Docker daemon. If it is a
|
||||||
|
// plain-HTTP registry, add it to /etc/docker/daemon.json on the AGENT host:
|
||||||
|
// { "insecure-registries": ["thinkserver:5000"] } (then restart docker)
|
||||||
|
//
|
||||||
|
// Jenkins credentials this pipeline expects:
|
||||||
|
// * ttrpg-firebase-env-local (Secret file) — the .env.local contents with
|
||||||
|
// REACT_APP_FIREBASE_* values. Baked into the firebase image at build time.
|
||||||
|
// * thinkserver-registry (Username/pw) — OPTIONAL. Only needed if the
|
||||||
|
// registry requires auth; the login stage is guarded below.
|
||||||
|
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
disableConcurrentBuilds()
|
||||||
|
timeout(time: 30, unit: 'MINUTES')
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters {
|
||||||
|
string(name: 'TRACKER_APP_ID',
|
||||||
|
defaultValue: 'ttrpg-initiative-tracker-default',
|
||||||
|
description: 'Firestore/app namespace baked into the SQLite image (REACT_APP_TRACKER_APP_ID).')
|
||||||
|
booleanParam(name: 'PUSH_LATEST', defaultValue: true,
|
||||||
|
description: 'Also tag and push :latest in addition to the commit-SHA tag.')
|
||||||
|
booleanParam(name: 'REGISTRY_AUTH', defaultValue: false,
|
||||||
|
description: 'Enable docker login using the thinkserver-registry credential.')
|
||||||
|
}
|
||||||
|
|
||||||
|
environment {
|
||||||
|
REGISTRY = 'thinkserver:5000'
|
||||||
|
FIREBASE_IMAGE = "${REGISTRY}/ttrpg-tracker-firebase"
|
||||||
|
SQLITE_IMAGE = "${REGISTRY}/ttrpg-tracker-sqlite"
|
||||||
|
DOCKER_BUILDKIT = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout scm
|
||||||
|
script {
|
||||||
|
env.SHORT_SHA = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim()
|
||||||
|
// Only build/push on main. BRANCH_NAME is set by Multibranch jobs; it
|
||||||
|
// is unset in a plain "Pipeline from SCM" job, which already only ever
|
||||||
|
// checks out main, so treat unset as main too.
|
||||||
|
env.IS_MAIN = (env.BRANCH_NAME == null || env.BRANCH_NAME == 'main').toString()
|
||||||
|
if (env.IS_MAIN != 'true') {
|
||||||
|
echo "Branch '${env.BRANCH_NAME}' is not main — skipping image build/push."
|
||||||
|
} else {
|
||||||
|
echo "Building tag ${env.SHORT_SHA} (build #${env.BUILD_NUMBER})"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Registry login') {
|
||||||
|
when { allOf { expression { env.IS_MAIN == 'true' }; expression { return params.REGISTRY_AUTH } } }
|
||||||
|
steps {
|
||||||
|
withCredentials([usernamePassword(credentialsId: 'thinkserver-registry',
|
||||||
|
usernameVariable: 'REG_USER',
|
||||||
|
passwordVariable: 'REG_PASS')]) {
|
||||||
|
sh 'echo "$REG_PASS" | docker login "$REGISTRY" -u "$REG_USER" --password-stdin'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build firebase image') {
|
||||||
|
when { expression { env.IS_MAIN == 'true' } }
|
||||||
|
steps {
|
||||||
|
// Firebase config is compiled into the static bundle, so it must exist
|
||||||
|
// at build time. Inject .env.local from the Jenkins secret file, then
|
||||||
|
// the root Dockerfile's `COPY .env.local .env` picks it up.
|
||||||
|
withCredentials([file(credentialsId: 'ttrpg-firebase-env-local', variable: 'FIREBASE_ENV')]) {
|
||||||
|
sh '''
|
||||||
|
cp "$FIREBASE_ENV" .env.local
|
||||||
|
docker build \
|
||||||
|
-f Dockerfile \
|
||||||
|
-t "$FIREBASE_IMAGE:$SHORT_SHA" \
|
||||||
|
.
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Build sqlite image') {
|
||||||
|
when { expression { env.IS_MAIN == 'true' } }
|
||||||
|
steps {
|
||||||
|
// Server/SQLite build. BuildKit is required (syntax directive + cache
|
||||||
|
// mount in docker/Dockerfile). Context is the repo root.
|
||||||
|
sh '''
|
||||||
|
docker build \
|
||||||
|
-f docker/Dockerfile \
|
||||||
|
--build-arg REACT_APP_TRACKER_APP_ID="$TRACKER_APP_ID" \
|
||||||
|
-t "$SQLITE_IMAGE:$SHORT_SHA" \
|
||||||
|
.
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Tag latest') {
|
||||||
|
when { allOf { expression { env.IS_MAIN == 'true' }; expression { return params.PUSH_LATEST } } }
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
docker tag "$FIREBASE_IMAGE:$SHORT_SHA" "$FIREBASE_IMAGE:latest"
|
||||||
|
docker tag "$SQLITE_IMAGE:$SHORT_SHA" "$SQLITE_IMAGE:latest"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Push') {
|
||||||
|
when { expression { env.IS_MAIN == 'true' } }
|
||||||
|
steps {
|
||||||
|
sh '''
|
||||||
|
docker push "$FIREBASE_IMAGE:$SHORT_SHA"
|
||||||
|
docker push "$SQLITE_IMAGE:$SHORT_SHA"
|
||||||
|
'''
|
||||||
|
script {
|
||||||
|
if (params.PUSH_LATEST) {
|
||||||
|
sh '''
|
||||||
|
docker push "$FIREBASE_IMAGE:latest"
|
||||||
|
docker push "$SQLITE_IMAGE:latest"
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
// Never leave Firebase secrets in the workspace or log out cleanly.
|
||||||
|
sh 'rm -f .env.local || true'
|
||||||
|
sh 'docker logout "$REGISTRY" || true'
|
||||||
|
}
|
||||||
|
success {
|
||||||
|
echo "Pushed ${FIREBASE_IMAGE}:${SHORT_SHA} and ${SQLITE_IMAGE}:${SHORT_SHA}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# docker/portainer-stack.yml — Portainer stack for the SQLite/server build.
|
||||||
|
# Deploys the prebuilt image from the local registry (built + pushed by Jenkins).
|
||||||
|
# Paste into Portainer's web editor (Stacks -> Add stack -> Web editor).
|
||||||
|
#
|
||||||
|
# TLS is terminated by the external nginx, which should reverse_proxy to
|
||||||
|
# http://<docker-host>:8080 (and pass the WebSocket upgrade headers for /ws).
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: thinkserver:5000/ttrpg-tracker-sqlite:latest
|
||||||
|
container_name: ttrpg-tracker
|
||||||
|
ports:
|
||||||
|
- "8080:80" # host:container — nginx upstream is :8080
|
||||||
|
volumes:
|
||||||
|
- app-data:/data # SQLite DB lives here; back this up
|
||||||
|
environment:
|
||||||
|
- DB_PATH=/data/tracker.sqlite
|
||||||
|
- PORT=4001 # node backend port (internal; caddy proxies to it)
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-qO-", "http://localhost:80/"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
app-data:
|
||||||
Reference in New Issue
Block a user