mirror of
https://github.com/runyanjake/jakesphotos.git
synced 2026-06-25 15:24:51 -07:00
Compare commits
No commits in common. "851b67a0f53ec1cf5977d6705477026d8bb1b529" and "6fd67e6057f97d8bb70dc61d8fbf6d08fc4ad206" have entirely different histories.
851b67a0f5
...
6fd67e6057
@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
build
|
|
||||||
src/generated
|
|
||||||
.git
|
|
||||||
42
Dockerfile
42
Dockerfile
@ -1,28 +1,32 @@
|
|||||||
# Shared dependency layer, reused by the ci and build targets.
|
# Use the official Node.js image as a build stage
|
||||||
FROM node:20-alpine AS deps
|
FROM node:20-alpine AS build
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Lint + content validation target, used by CI (docker build --target ci).
|
# Copy package.json and package-lock.json
|
||||||
FROM deps AS ci
|
COPY package*.json ./
|
||||||
COPY . .
|
|
||||||
RUN node scripts/build-content.js && npx eslint src --ignore-pattern 'src/generated/**' --max-warnings=0
|
|
||||||
|
|
||||||
# Production build target: generates content then bundles the static site.
|
# Install dependencies
|
||||||
FROM deps AS build
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the application code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Build the React application
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Serve the static bundle with nginx.
|
# Use a lightweight web server to serve the build files
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
COPY --from=build /app/build /usr/share/nginx/html
|
|
||||||
# Custom config so client-side routes fall back to index.html.
|
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
EXPOSE 80
|
|
||||||
# Liveness probe. Use 127.0.0.1 (not localhost): nginx listens IPv4-only and
|
|
||||||
# busybox wget would resolve localhost to ::1 and get connection refused.
|
|
||||||
HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 \
|
|
||||||
CMD wget -q -O /dev/null http://127.0.0.1:80/ || exit 1
|
|
||||||
|
|
||||||
|
# Copy the build files from the previous stage
|
||||||
|
COPY --from=build /app/build /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Use custom nginx config so client-side routes (e.g. /contact, /about) fall back to index.html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Expose port 80
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Start Nginx
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
148
Jenkinsfile
vendored
148
Jenkinsfile
vendored
@ -1,148 +0,0 @@
|
|||||||
pipeline {
|
|
||||||
agent any
|
|
||||||
|
|
||||||
environment {
|
|
||||||
DISCORD_WEBHOOK = credentials('discord-pws-builds-channel-webhook')
|
|
||||||
|
|
||||||
COMPOSE_SERVICE = 'portfolio'
|
|
||||||
CONTAINER_NAME = 'jakesphotos'
|
|
||||||
}
|
|
||||||
|
|
||||||
options {
|
|
||||||
timestamps()
|
|
||||||
disableConcurrentBuilds()
|
|
||||||
timeout(time: 30, unit: 'MINUTES')
|
|
||||||
}
|
|
||||||
|
|
||||||
stages {
|
|
||||||
stage('Checkout') {
|
|
||||||
steps {
|
|
||||||
checkout scm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Preflight') {
|
|
||||||
steps {
|
|
||||||
sh '''
|
|
||||||
set -eu
|
|
||||||
: "${DISCORD_WEBHOOK:?required credential discord-pws-builds-channel-webhook is missing}"
|
|
||||||
for f in Dockerfile package.json docker-compose.yml content/_config.json; do
|
|
||||||
[ -f "$f" ] || { echo "ERROR: required file '$f' not found at repo root" >&2; exit 1; }
|
|
||||||
done
|
|
||||||
docker compose config -q
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Lint & Type-check') {
|
|
||||||
steps {
|
|
||||||
// Static checks run inside the image's ci target; nothing touches the agent.
|
|
||||||
sh 'docker build --target ci -t ${CONTAINER_NAME}-ci:${BUILD_NUMBER} .'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Teardown') {
|
|
||||||
steps {
|
|
||||||
sh '''
|
|
||||||
set -eu
|
|
||||||
docker compose down --remove-orphans || true
|
|
||||||
# container_name is fixed, so a stale container can survive "down"
|
|
||||||
# and then block "up" with a name conflict; reap it explicitly.
|
|
||||||
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Build & Deploy') {
|
|
||||||
steps {
|
|
||||||
// No build-time secrets: the React build consumes only static content/.
|
|
||||||
sh 'docker compose up -d --build'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Health Check') {
|
|
||||||
steps {
|
|
||||||
sh '''
|
|
||||||
set -eu
|
|
||||||
cid="$(docker compose ps -q "$COMPOSE_SERVICE")"
|
|
||||||
[ -n "$cid" ] || { echo "ERROR: $COMPOSE_SERVICE container not found" >&2; exit 1; }
|
|
||||||
|
|
||||||
# Wait for the image's HEALTHCHECK to report healthy, failing fast on terminal states.
|
|
||||||
deadline=$(( $(date +%s) + 90 ))
|
|
||||||
while :; do
|
|
||||||
status="$(docker inspect -f '{{.State.Status}}' "$cid")"
|
|
||||||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$cid")"
|
|
||||||
[ "$status" = "running" ] && [ "$health" = "healthy" ] && break
|
|
||||||
[ "$health" = "unhealthy" ] && { echo "ERROR: $COMPOSE_SERVICE reported unhealthy" >&2; exit 1; }
|
|
||||||
case "$status" in
|
|
||||||
exited|dead) echo "ERROR: $COMPOSE_SERVICE container $status before becoming healthy" >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
[ "$(date +%s)" -ge "$deadline" ] && { echo "ERROR: timed out waiting for healthy (status=$status, health=$health)" >&2; exit 1; }
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
echo "$COMPOSE_SERVICE healthy"
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Smoke Test') {
|
|
||||||
steps {
|
|
||||||
sh '''
|
|
||||||
set -eu
|
|
||||||
cid="$(docker compose ps -q "$COMPOSE_SERVICE")"
|
|
||||||
[ -n "$cid" ] || { echo "ERROR: $COMPOSE_SERVICE container not found" >&2; exit 1; }
|
|
||||||
|
|
||||||
# Real request from inside the container: GET / must serve the SPA shell.
|
|
||||||
body="$(docker exec "$cid" wget -q -O - http://127.0.0.1:80/)" || { echo "ERROR: GET / did not return a successful response" >&2; exit 1; }
|
|
||||||
echo "$body" | grep -q 'id="root"' || { echo "ERROR: GET / response missing expected app markup" >&2; exit 1; }
|
|
||||||
echo "smoke test passed"
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
script {
|
|
||||||
def result = currentBuild.currentResult
|
|
||||||
def emoji = result == 'SUCCESS' ? ':green_circle:' :
|
|
||||||
result == 'FAILURE' ? ':red_circle:' : ':yellow_circle:'
|
|
||||||
|
|
||||||
def branch = env.BRANCH_NAME ?: env.GIT_BRANCH ?: 'Main/Manual'
|
|
||||||
|
|
||||||
def duration = currentBuild.durationString
|
|
||||||
.replace(' and no weeks', '')
|
|
||||||
.replace(' and counting', '')
|
|
||||||
|
|
||||||
def commits = currentBuild.changeSets.collectMany { set ->
|
|
||||||
set.items.collect { "> ${it.msg} (by *${it.author.fullName}*)" }
|
|
||||||
}
|
|
||||||
def commitText = commits ? commits.join('\n') : 'No recent changes detected.'
|
|
||||||
|
|
||||||
def discordDescription = """**Status:** ${emoji} ${result}
|
|
||||||
**Branch:** `${branch}`
|
|
||||||
**Duration:** :stopwatch: ${duration}
|
|
||||||
|
|
||||||
**Commits:**
|
|
||||||
${commitText}"""
|
|
||||||
|
|
||||||
discordSend(
|
|
||||||
webhookURL: env.DISCORD_WEBHOOK,
|
|
||||||
title: "📦 Build Alert: ${env.JOB_NAME} [Build #${env.BUILD_NUMBER}]",
|
|
||||||
link: "${env.BUILD_URL}",
|
|
||||||
result: "${currentBuild.currentResult}",
|
|
||||||
description: discordDescription
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
failure {
|
|
||||||
sh '''
|
|
||||||
echo "=== docker compose ps ==="
|
|
||||||
docker compose ps || true
|
|
||||||
echo "=== recent logs ==="
|
|
||||||
docker compose logs --tail=200 || true
|
|
||||||
'''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user