Compare commits
7 Commits
v1.2.2
..
5fc2292e8f
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fc2292e8f | |||
| d249656be8 | |||
| 3941429bb1 | |||
| 40c6af5326 | |||
| 499488e355 | |||
| 9f98c371b0 | |||
| 467eee25c2 |
@@ -3,130 +3,51 @@ name: Build and publish container
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
tags: ["v*"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
# Full history and tags are required to derive the next version
|
|
||||||
# from the conventional-commit messages since the last release.
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Determine registry host
|
- name: Determine registry host
|
||||||
run: echo "REGISTRY=${GITHUB_SERVER_URL#*://}" >> "$GITHUB_ENV"
|
run: echo "REGISTRY=${GITHUB_SERVER_URL#*://}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
# Derive the release version from conventional commits since the last
|
|
||||||
# v* tag: feat -> minor, fix/perf -> patch, ! or BREAKING CHANGE -> major.
|
|
||||||
# Anything else (chore, ci, docs, build) produces no release; those builds
|
|
||||||
# are published under a sha-<short> tag only.
|
|
||||||
- name: Compute version and image tags
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
image="${REGISTRY}/${GITHUB_REPOSITORY,,}"
|
|
||||||
|
|
||||||
last_tag="$(git tag --list 'v*' --sort=-v:refname | head -n1 || true)"
|
|
||||||
if [ -n "$last_tag" ]; then
|
|
||||||
range="${last_tag}..HEAD"
|
|
||||||
base="${last_tag#v}"
|
|
||||||
else
|
|
||||||
range=""
|
|
||||||
base="0.0.0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
subjects="$(git log ${range} --format='%s')"
|
|
||||||
bodies="$(git log ${range} --format='%B')"
|
|
||||||
|
|
||||||
bump="none"
|
|
||||||
if printf '%s\n' "$bodies" | grep -qiE 'BREAKING[ -]CHANGE' \
|
|
||||||
|| printf '%s\n' "$subjects" | grep -qE '^[a-z]+([(][^)]*[)])?!:'; then
|
|
||||||
bump="major"
|
|
||||||
elif printf '%s\n' "$subjects" | grep -qE '^feat([(][^)]*[)])?:'; then
|
|
||||||
bump="minor"
|
|
||||||
elif printf '%s\n' "$subjects" | grep -qE '^(fix|perf)([(][^)]*[)])?:'; then
|
|
||||||
bump="patch"
|
|
||||||
fi
|
|
||||||
|
|
||||||
major="${base%%.*}"
|
|
||||||
rest="${base#*.}"
|
|
||||||
minor="${rest%%.*}"
|
|
||||||
patch="${rest##*.}"
|
|
||||||
|
|
||||||
release="false"
|
|
||||||
if [ "${GITHUB_EVENT_NAME}" = "push" ] && [ "$bump" != "none" ]; then
|
|
||||||
release="true"
|
|
||||||
case "$bump" in
|
|
||||||
major) major=$((major + 1)); minor=0; patch=0 ;;
|
|
||||||
minor) minor=$((minor + 1)); patch=0 ;;
|
|
||||||
patch) patch=$((patch + 1)) ;;
|
|
||||||
esac
|
|
||||||
version="${major}.${minor}.${patch}"
|
|
||||||
{
|
|
||||||
echo "tags<<__EOT__"
|
|
||||||
echo "${image}:${version}"
|
|
||||||
echo "${image}:${major}.${minor}"
|
|
||||||
echo "${image}:${major}"
|
|
||||||
echo "${image}:latest"
|
|
||||||
echo "__EOT__"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
short="$(git rev-parse --short HEAD)"
|
|
||||||
{
|
|
||||||
echo "tags<<__EOT__"
|
|
||||||
echo "${image}:sha-${short}"
|
|
||||||
echo "__EOT__"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
echo "release=${release}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Computed bump=${bump}, release=${release}, base=${base}"
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
|
|
||||||
|
|
||||||
- name: Set up Buildx
|
- name: Set up Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to the Gitea container registry
|
- name: Log in to the Gitea container registry
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.PACKAGES_TOKEN }}
|
password: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract image metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ github.repository }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=sha
|
||||||
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.version.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: |
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
|
|
||||||
org.opencontainers.image.revision=${{ github.sha }}
|
|
||||||
|
|
||||||
# Record the release as an annotated git tag so the next run computes the
|
|
||||||
# following version from it. This push does not re-trigger the workflow,
|
|
||||||
# which only listens on the main branch and pull requests.
|
|
||||||
- name: Tag the release
|
|
||||||
if: steps.version.outputs.release == 'true'
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
v="v${{ steps.version.outputs.version }}"
|
|
||||||
git config user.name "${{ github.actor }}"
|
|
||||||
git config user.email "${{ github.actor }}@users.noreply.${REGISTRY}"
|
|
||||||
git tag -a "$v" -m "$v"
|
|
||||||
git push origin "$v"
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# Lightweight, non-root nginx serving the static site.
|
# Lightweight, non-root nginx serving the static site.
|
||||||
# Runs as user "nginx" and listens on 8080, ready to sit behind an
|
# Runs as user "nginx" and listens on 8080, ready to sit behind an
|
||||||
# external reverse proxy that terminates TLS and forwards requests.
|
# external reverse proxy that terminates TLS and forwards requests.
|
||||||
FROM nginxinc/nginx-unprivileged:1.31-alpine-slim@sha256:6616de6eaa82bc2ee3541fa287a8fca7dc7271e6374e9402014dbd13f4a980ae
|
FROM nginxinc/nginx-unprivileged:1.27-alpine-slim
|
||||||
|
|
||||||
COPY default.conf /etc/nginx/conf.d/default.conf
|
COPY default.conf /etc/nginx/conf.d/default.conf
|
||||||
COPY --chown=nginx:nginx index.html styles.css script.js messages.js /usr/share/nginx/html/
|
COPY --chown=nginx:nginx index.html styles.css script.js messages.js /usr/share/nginx/html/
|
||||||
|
|||||||
@@ -45,38 +45,13 @@ docker run --rm -p 8080:8080 dlr
|
|||||||
## CI
|
## CI
|
||||||
|
|
||||||
`.gitea/workflows/build-and-publish.yml` builds the container with Gitea Actions
|
`.gitea/workflows/build-and-publish.yml` builds the container with Gitea Actions
|
||||||
on every push to `main` and on pull requests. Pull requests build the image but
|
and publishes it to this Gitea instance's container registry on pushes to `main`
|
||||||
do not push. The registry host is derived from the Gitea server URL. Images are
|
and on `v*` tags. Pull requests build the image but do not push. Authentication
|
||||||
built for `linux/amd64` and `linux/arm64` (armv8) and published as a single
|
uses the automatically provided `GITEA_TOKEN`; the registry host is derived from
|
||||||
multi-arch manifest; the arm64 build runs under QEMU emulation.
|
the Gitea server URL.
|
||||||
|
|
||||||
Authentication requires a Personal Access Token with package read/write scope,
|
The published image is `<gitea-host>/<owner>/<repo>`, tagged by branch, semver
|
||||||
because the automatically provided `GITEA_TOKEN` does not carry container
|
(for `v*` tags), commit SHA, and `latest` on the default branch.
|
||||||
registry write permission on most Gitea instances. Create the token under an
|
|
||||||
account with write access to the target package namespace, then store it as a
|
|
||||||
repository Actions secret named `PACKAGES_TOKEN`.
|
|
||||||
|
|
||||||
### Automatic releases
|
|
||||||
|
|
||||||
The published image is `<gitea-host>/<owner>/<repo>`. Releases are derived from
|
|
||||||
[Conventional Commits](https://www.conventionalcommits.org/). On each push to
|
|
||||||
`main`, the workflow inspects the commits since the last `v*` tag and computes
|
|
||||||
the next version:
|
|
||||||
|
|
||||||
- `feat:` → minor bump,
|
|
||||||
- `fix:` / `perf:` → patch bump,
|
|
||||||
- `!` or `BREAKING CHANGE` → major bump,
|
|
||||||
- anything else (`chore`, `ci`, `docs`, `build`) → no release.
|
|
||||||
|
|
||||||
When a release is warranted, the image is published with `X.Y.Z`, `X.Y`, `X` and
|
|
||||||
`latest` tags, and the workflow records an annotated `vX.Y.Z` git tag so the next
|
|
||||||
release is computed from it. Pushes to `main` that warrant no release are
|
|
||||||
published under a `sha-<short>` tag only, so `latest` always points at the most
|
|
||||||
recent release rather than the newest commit.
|
|
||||||
|
|
||||||
Recording the release tag requires the workflow's `contents: write` permission;
|
|
||||||
if the instance forbids the automatic token from pushing, supply a PAT with
|
|
||||||
repository write scope and push the tag with it instead.
|
|
||||||
|
|
||||||
## Dependency updates
|
## Dependency updates
|
||||||
|
|
||||||
@@ -97,10 +72,6 @@ CDN, Renovate will track it if it is either annotated with a comment, e.g.
|
|||||||
or referenced through a versioned jsDelivr / unpkg npm URL, which is detected
|
or referenced through a versioned jsDelivr / unpkg npm URL, which is detected
|
||||||
automatically.
|
automatically.
|
||||||
|
|
||||||
Renovate is configured to commit updates as `fix(deps): …`. Each merged Renovate
|
|
||||||
PR therefore registers as a patch-level change, so the release workflow above
|
|
||||||
cuts a new patch release and tags the image automatically.
|
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|
|||||||
+4
-9
@@ -11,13 +11,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const MESSAGES = [
|
const MESSAGES = [
|
||||||
"Maggie came back, she was unimpressed",
|
"PLACEHOLDER: write your first reason here",
|
||||||
"They mixed up the B23s and the 2024 tube stock",
|
"PLACEHOLDER: write another reason here",
|
||||||
"The computer went on strike",
|
// Add as many entries as you like, one per line:
|
||||||
"Leaves on the track",
|
// "Your reason here",
|
||||||
"Escalators broke at Cutty Sark",
|
|
||||||
"EHRC decided it was woke",
|
|
||||||
"JK Rowling",
|
|
||||||
"Kaiju",
|
|
||||||
"28 Days Later happened",
|
|
||||||
];
|
];
|
||||||
|
|||||||
+3
-17
@@ -6,35 +6,21 @@
|
|||||||
":semanticCommits"
|
":semanticCommits"
|
||||||
],
|
],
|
||||||
"labels": ["renovate"],
|
"labels": ["renovate"],
|
||||||
"semanticCommits": "enabled",
|
|
||||||
"semanticCommitType": "fix",
|
|
||||||
"semanticCommitScope": "deps",
|
|
||||||
"github-actions": {
|
"github-actions": {
|
||||||
"managerFilePatterns": ["/^\\.gitea/workflows/[^/]+\\.ya?ml$/"]
|
"fileMatch": ["^\\.gitea/workflows/[^/]+\\.ya?ml$"]
|
||||||
},
|
},
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
"description": "Group nginx base image updates",
|
"description": "Group nginx base image updates",
|
||||||
"matchManagers": ["dockerfile"],
|
"matchManagers": ["dockerfile"],
|
||||||
"groupName": "docker base image"
|
"groupName": "docker base image"
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Commit every update as fix(deps) so each merged Renovate PR triggers a patch release. config:recommended pulls in :semanticPrefixFixDepsChoreOthers, which forces non-npm updates (Docker, Actions) to chore and would otherwise produce no release.",
|
|
||||||
"matchPackageNames": ["*"],
|
|
||||||
"semanticCommitType": "fix"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Auto-merge patch and minor updates once checks pass.",
|
|
||||||
"matchUpdateTypes": ["patch", "minor"],
|
|
||||||
"automerge": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"platformAutomerge": true,
|
|
||||||
"customManagers": [
|
"customManagers": [
|
||||||
{
|
{
|
||||||
"customType": "regex",
|
"customType": "regex",
|
||||||
"description": "Update HTML dependencies annotated with a renovate comment, e.g. <!-- renovate: datasource=npm depName=bootstrap --> before the versioned URL",
|
"description": "Update HTML dependencies annotated with a renovate comment, e.g. <!-- renovate: datasource=npm depName=bootstrap --> before the versioned URL",
|
||||||
"managerFilePatterns": ["/\\.html$/"],
|
"fileMatch": ["\\.html$"],
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)( versioning=(?<versioning>\\S+))?[\\s\\S]*?(?<currentValue>v?\\d+\\.\\d+\\.\\d+[\\w.-]*)"
|
"datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)( versioning=(?<versioning>\\S+))?[\\s\\S]*?(?<currentValue>v?\\d+\\.\\d+\\.\\d+[\\w.-]*)"
|
||||||
]
|
]
|
||||||
@@ -42,7 +28,7 @@
|
|||||||
{
|
{
|
||||||
"customType": "regex",
|
"customType": "regex",
|
||||||
"description": "Auto-detect versioned jsDelivr / unpkg npm assets in HTML",
|
"description": "Auto-detect versioned jsDelivr / unpkg npm assets in HTML",
|
||||||
"managerFilePatterns": ["/\\.html$/"],
|
"fileMatch": ["\\.html$"],
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"https://cdn\\.jsdelivr\\.net/npm/(?<depName>@?[^@/]+(?:/[^@/]+)?)@(?<currentValue>\\d[^/\"']+)",
|
"https://cdn\\.jsdelivr\\.net/npm/(?<depName>@?[^@/]+(?:/[^@/]+)?)@(?<currentValue>\\d[^/\"']+)",
|
||||||
"https://unpkg\\.com/(?<depName>@?[^@/]+(?:/[^@/]+)?)@(?<currentValue>\\d[^/\"']+)"
|
"https://unpkg\\.com/(?<depName>@?[^@/]+(?:/[^@/]+)?)@(?<currentValue>\\d[^/\"']+)"
|
||||||
|
|||||||
+14
-18
@@ -10,6 +10,7 @@
|
|||||||
/* Modern DLR — turquoise/teal */
|
/* Modern DLR — turquoise/teal */
|
||||||
[data-theme="modern"] {
|
[data-theme="modern"] {
|
||||||
--bg: #00afaa;
|
--bg: #00afaa;
|
||||||
|
--bg-accent: #007e7a;
|
||||||
--surface: #ffffff;
|
--surface: #ffffff;
|
||||||
--text: #ffffff;
|
--text: #ffffff;
|
||||||
--message: #ffffff;
|
--message: #ffffff;
|
||||||
@@ -19,13 +20,14 @@
|
|||||||
--button-active-text: #ffffff;
|
--button-active-text: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Original DLR — 1987 red, white and blue */
|
/* Original DLR — 1987 red and blue */
|
||||||
[data-theme="original"] {
|
[data-theme="original"] {
|
||||||
--bg: #c8102e;
|
--bg: #002b5c;
|
||||||
--surface: #002b5c;
|
--bg-accent: #c8102e;
|
||||||
|
--surface: #f5f0e1;
|
||||||
--text: #f5f0e1;
|
--text: #f5f0e1;
|
||||||
--message: #f5f0e1;
|
--message: #f5f0e1;
|
||||||
--button-bg: #002b5c;
|
--button-bg: #c8102e;
|
||||||
--button-text: #f5f0e1;
|
--button-text: #f5f0e1;
|
||||||
--button-active-bg: #f5f0e1;
|
--button-active-bg: #f5f0e1;
|
||||||
--button-active-text: #002b5c;
|
--button-active-text: #002b5c;
|
||||||
@@ -42,7 +44,7 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-family: var(--font-stack);
|
font-family: var(--font-stack);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background: var(--bg);
|
background: linear-gradient(135deg, var(--bg) 0%, var(--bg-accent) 100%);
|
||||||
transition: background 0.4s ease, color 0.4s ease;
|
transition: background 0.4s ease, color 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,13 +65,10 @@ body {
|
|||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Segmented control: both options joined in one rounded container,
|
|
||||||
with the active segment filled. */
|
|
||||||
.theme-toggle {
|
.theme-toggle {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
border: 2px solid var(--button-bg);
|
gap: 0.5rem;
|
||||||
border-radius: 999px;
|
border: none;
|
||||||
overflow: hidden;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@@ -78,22 +77,19 @@ body {
|
|||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 0.5rem 1.1rem;
|
padding: 0.5rem 1rem;
|
||||||
border: none;
|
border: 2px solid var(--button-bg);
|
||||||
|
border-radius: 999px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s ease, color 0.2s ease;
|
transition: background 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Divider between the two segments. */
|
|
||||||
.theme-button + .theme-button {
|
|
||||||
border-left: 2px solid var(--button-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-button[aria-pressed="true"] {
|
.theme-button[aria-pressed="true"] {
|
||||||
background: var(--button-active-bg);
|
background: var(--button-active-bg);
|
||||||
color: var(--button-active-text);
|
color: var(--button-active-text);
|
||||||
|
border-color: var(--button-active-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stage {
|
.stage {
|
||||||
|
|||||||
Reference in New Issue
Block a user