Publish multi-platform Alpine image

This commit is contained in:
adminroot committed 2026-08-25 12:06:25 +00:00
1 parent b534c13b40
commit 046d5fe11b
5 files changed
+234 -2

No files matched your search

+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env bash
set -euo pipefail
: "${RELEASE_TAG:?RELEASE_TAG is required}"
: "${IMAGE_REPOSITORY:?IMAGE_REPOSITORY is required}"
CRANE_BIN="${CRANE_BIN:-crane}"
ALPINE_IMAGE="${ALPINE_IMAGE:-docker.io/library/alpine:latest}"
DIST_DIR="${DIST_DIR:-dist}"
SOURCE_URL="${SOURCE_URL:-}"
REVISION="${REVISION:-$(git rev-parse HEAD)}"
SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git show -s --format=%ct HEAD)}"
version="${RELEASE_TAG#v}"
if [ -z "$version" ]; then
echo "Release tag must contain a version" >&2
exit 1
fi
case "$SOURCE_DATE_EPOCH" in
*[!0-9]* | '')
echo "SOURCE_DATE_EPOCH must be an integer" >&2
exit 1
;;
esac
work_dir="$(mktemp -d /tmp/pixhelf-container.XXXXXX)"
cleanup() {
case "$work_dir" in
/tmp/pixhelf-container.*) rm -rf -- "$work_dir" ;;
*) echo "Refusing to remove unexpected path: $work_dir" >&2 ;;
esac
}
trap cleanup EXIT
crane_args=()
if [ "${CRANE_INSECURE:-0}" = 1 ]; then
crane_args+=(--insecure)
fi
crane() {
"$CRANE_BIN" "${crane_args[@]}" "$@"
}
created="$(date --utc --date="@$SOURCE_DATE_EPOCH" '+%Y-%m-%dT%H:%M:%SZ')"
platform_digest=''
build_platform() {
local arch="$1"
local binary="$2"
local root="$work_dir/root-$arch"
local layer="$work_dir/pixhelf-$arch.tar"
local platform_ref="$IMAGE_REPOSITORY:$version-$arch"
test -x "$binary"
install -d -m 0755 "$root/usr/local/bin" "$root/gallery"
install -d -m 0777 "$root/cache"
install -m 0755 "$binary" "$root/usr/local/bin/pixhelf"
tar \
--sort=name \
--mtime="@$SOURCE_DATE_EPOCH" \
--owner=0 \
--group=0 \
--numeric-owner \
-C "$root" \
-cf "$layer" \
cache gallery usr
crane append \
--platform "linux/$arch" \
--base "$ALPINE_IMAGE" \
--new_layer "$layer" \
--new_tag "$platform_ref" \
--set-base-image-annotations
crane mutate "$platform_ref" \
--platform "linux/$arch" \
--set-platform "linux/$arch" \
--entrypoint=/usr/local/bin/pixhelf \
--cmd=--gallery-dir \
--cmd=/gallery \
--cmd=--cache-dir \
--cmd=/cache \
--cmd=--listen \
--cmd=0.0.0.0:3002 \
--user=65532:65532 \
--workdir=/ \
--exposed-ports=3002/tcp \
--label=org.opencontainers.image.title=Pixhelf \
--label="org.opencontainers.image.description=Self-hosted image gallery" \
--label="org.opencontainers.image.version=$version" \
--label="org.opencontainers.image.revision=$REVISION" \
--label="org.opencontainers.image.created=$created" \
--label="org.opencontainers.image.source=$SOURCE_URL" \
--tag "$platform_ref"
platform_digest="$(crane digest "$platform_ref")"
echo "Published linux/$arch as $platform_ref@$platform_digest"
}
build_platform amd64 "$DIST_DIR/pixhelf-amd64-linux"
amd64_digest="$platform_digest"
build_platform arm64 "$DIST_DIR/pixhelf-arm64-linux"
arm64_digest="$platform_digest"
canonical_ref="$IMAGE_REPOSITORY:$version"
crane index append \
--manifest "$IMAGE_REPOSITORY@$amd64_digest" \
--manifest "$IMAGE_REPOSITORY@$arm64_digest" \
--tag "$canonical_ref"
if [ "$RELEASE_TAG" != "$version" ]; then
crane tag "$canonical_ref" "$RELEASE_TAG"
fi
case "$version" in
*-*) echo "Prerelease detected; latest tag was not updated" ;;
*) crane tag "$canonical_ref" latest ;;
esac
crane manifest "$canonical_ref" | node -e '
let input = "";
process.stdin.on("data", (chunk) => input += chunk);
process.stdin.on("end", () => {
const index = JSON.parse(input);
const platforms = new Set((index.manifests ?? []).map(
(manifest) => `${manifest.platform?.os}/${manifest.platform?.architecture}`,
));
for (const expected of ["linux/amd64", "linux/arm64"]) {
if (!platforms.has(expected)) {
throw new Error(`Published image is missing ${expected}`);
}
}
console.log(`Verified multi-platform image: ${[...platforms].sort().join(", ")}`);
});
'
echo "Published $canonical_ref"
+44 -1
View File
@@ -186,7 +186,7 @@ jobs:
delete_asset pixhelf-amd64
delete_asset pixhelf-arm64
for asset in dist/pixhelf-amd64-linux dist/pixhelf-arm64-linux; do
for asset in dist/pixhelf-amd64-linux dist/pixhelf-arm64-linux compose.yml; do
asset_name="$(basename "$asset")"
delete_asset "$asset_name"
curl --silent --show-error --fail \
@@ -197,3 +197,46 @@ jobs:
"${api}/repos/${repository}/releases/${release_id}/assets?name=${asset_name}"
printf '\n'
done
- name: Install container publishing tool
if: startsWith(forgejo.ref, 'refs/tags/v')
run: |
set -eu
crane_version=0.21.9
crane_archive="go-containerregistry_Linux_x86_64.tar.gz"
crane_sha256=5c16d8ddb971cb1d5e6ed8b1e743da8224414eeba2c2762d8f1a61b2f095699e
crane_url="https://github.com/google/go-containerregistry/releases/download/v${crane_version}/${crane_archive}"
curl --location --silent --show-error --fail \
--output "/tmp/${crane_archive}" \
"$crane_url"
printf '%s %s\n' "$crane_sha256" "/tmp/${crane_archive}" \
| sha256sum --check
tar -xzf "/tmp/${crane_archive}" -C /usr/local/bin crane
crane version
- name: Publish multi-platform Alpine container
if: startsWith(forgejo.ref, 'refs/tags/v')
run: |
set -eu
token="${FORGEJO_TOKEN:-${GITHUB_TOKEN:-}}"
actor="${FORGEJO_ACTOR:-${GITHUB_ACTOR}}"
server="${FORGEJO_SERVER_URL:-${GITHUB_SERVER_URL}}"
repository="${FORGEJO_REPOSITORY:-${GITHUB_REPOSITORY}}"
registry="${server#*://}"
registry="${registry%%/*}"
image_path="$(printf '%s' "$repository" | tr '[:upper:]' '[:lower:]')"
image_repository="${registry}/${image_path}"
test -n "$token"
export DOCKER_CONFIG=/tmp/pixhelf-container-auth
install -d -m 0700 "$DOCKER_CONFIG"
printf '%s' "$token" \
| crane auth login "$registry" --username "$actor" --password-stdin
export RELEASE_TAG
export IMAGE_REPOSITORY="$image_repository"
export SOURCE_URL="${server}/${repository}"
export REVISION
REVISION="$(git rev-parse HEAD)"
.forgejo/scripts/publish-container.sh
+1 -1
View File
@@ -1,7 +1,7 @@
/pic/
/.pixhelf-cache/
/target/
/dist/
/frontend/node_modules/
/frontend/dist/
*.log
+27
View File
@@ -37,6 +37,33 @@ cargo build --release
两个 Release 产物都是无扩展名的独立可执行文件,下载后需要执行
`chmod +x pixhelf-amd64-linux` 或 `chmod +x pixhelf-arm64-linux`。
## Docker Compose
发布工作流还会将同一批 AMD64、ARM64 二进制分别装入 `alpine:latest`,并发布为
`git.pixhelf.com/adminroot/pixhelf` 多架构镜像。最简安装方式:
```bash
curl -LO https://git.pixhelf.com/adminroot/pixhelf/raw/branch/master/compose.yml
mkdir -p pic
docker compose up -d
```
打开 `http://localhost:3002`。`compose.yml` 默认只读挂载当前目录的 `./pic`,并用
Docker volume 保存缩略图缓存。更新时执行:
```bash
docker compose pull
docker compose up -d
```
默认使用 `latest`,也可以固定版本:
```bash
PIXHELF_IMAGE=git.pixhelf.com/adminroot/pixhelf:0.1.7 docker compose up -d
```
容器以 UID/GID `65532:65532` 运行,因此图库目录和图片需要对该用户可读。
## 参数
```text
+25
View File
@@ -0,0 +1,25 @@
services:
pixhelf:
image: ${PIXHELF_IMAGE:-git.pixhelf.com/adminroot/pixhelf:latest}
pull_policy: always
restart: unless-stopped
init: true
ports:
- "${PIXHELF_PORT:-3002}:3002"
volumes:
- "${PIXHELF_GALLERY:-./pic}:/gallery:ro"
- pixhelf-cache:/cache
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://127.0.0.1:3002/api/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
volumes:
pixhelf-cache: