Add joafri image metadata in compose and multi-arch build/push script
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
name: sample-app
|
name: sample-app
|
||||||
|
|
||||||
|
x-image:
|
||||||
|
namespace: ${IMAGE_NAMESPACE:-joafri}
|
||||||
|
tag: ${IMAGE_TAG:-main}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: ghcr.io/example/sample-app:1.0.0
|
image: ${IMAGE_NAMESPACE:-joafri}/sample-app-app:${IMAGE_TAG:-main}
|
||||||
container_name: sample-app
|
container_name: sample-app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
name: caddy-autogen
|
name: caddy-autogen
|
||||||
|
|
||||||
|
x-image:
|
||||||
|
namespace: ${IMAGE_NAMESPACE:-joafri}
|
||||||
|
tag: ${IMAGE_TAG:-main}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
caddy:
|
caddy:
|
||||||
|
image: ${IMAGE_NAMESPACE:-joafri}/caddy-autogen-caddy:${IMAGE_TAG:-main}
|
||||||
build:
|
build:
|
||||||
context: ./caddy
|
context: ./caddy
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
@@ -79,6 +84,7 @@ services:
|
|||||||
- ALL
|
- ALL
|
||||||
|
|
||||||
discovery-agent:
|
discovery-agent:
|
||||||
|
image: ${IMAGE_NAMESPACE:-joafri}/caddy-autogen-discovery-agent:${IMAGE_TAG:-main}
|
||||||
build:
|
build:
|
||||||
context: ./agent
|
context: ./agent
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
name: docker-ip-addr-manager
|
name: docker-ip-addr-manager
|
||||||
|
|
||||||
|
x-image:
|
||||||
|
namespace: ${IMAGE_NAMESPACE:-joafri}
|
||||||
|
tag: ${IMAGE_TAG:-main}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
|
image: ${IMAGE_NAMESPACE:-joafri}/docker-ip-addr-manager-app:${IMAGE_TAG:-main}
|
||||||
build:
|
build:
|
||||||
context: ./backend
|
context: ./backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
name: steam-headless
|
name: steam-headless
|
||||||
|
|
||||||
|
x-image:
|
||||||
|
namespace: ${IMAGE_NAMESPACE:-joafri}
|
||||||
|
tag: ${IMAGE_TAG:-main}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
steam:
|
steam:
|
||||||
image: lscr.io/linuxserver/steam:version-f4f48542@sha256:d7b9fbf302e05ae79248d1171fe9751b354f8397eafa1e13a3df0aa6a75de0b4
|
image: lscr.io/linuxserver/steam:version-f4f48542@sha256:d7b9fbf302e05ae79248d1171fe9751b354f8397eafa1e13a3df0aa6a75de0b4
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
name: steam-moonlight
|
name: steam-moonlight
|
||||||
|
|
||||||
|
x-image:
|
||||||
|
namespace: ${IMAGE_NAMESPACE:-joafri}
|
||||||
|
tag: ${IMAGE_TAG:-main}
|
||||||
|
|
||||||
x-steam-common: &steam-common
|
x-steam-common: &steam-common
|
||||||
image: josh5/steam-headless:debian-0.2.0@sha256:540366bee31297c5679a5006a84dbca039ca62aaab695852b51b5f62dffd2c14
|
image: josh5/steam-headless:debian-0.2.0@sha256:540366bee31297c5679a5006a84dbca039ca62aaab695852b51b5f62dffd2c14
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ Skelett för att bygga och underhålla ZimaOS/CasaOS-appar i ett eget appstore-r
|
|||||||
├── featured-apps.json
|
├── featured-apps.json
|
||||||
├── recommend-list.json
|
├── recommend-list.json
|
||||||
└── scripts/
|
└── scripts/
|
||||||
|
├── build-appstore-zip.sh
|
||||||
|
├── build-and-push-image.sh
|
||||||
└── validate-appstore.sh
|
└── validate-appstore.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -46,6 +48,16 @@ Inför release/publicering, kör strikt validering för högrisk-inställningar:
|
|||||||
./scripts/validate-appstore.sh --enforce-risk-docs
|
./scripts/validate-appstore.sh --enforce-risk-docs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Bygg och publicera app-specifika custom images (Docker Hub namespace `joafri` som default):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/build-and-push-image.sh --app-id caddy-autogen --tag 0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Scriptet läser `Apps/<app-id>/docker-compose.yaml` och bygger alla services som har `build:` om `--component` inte anges.
|
||||||
|
`--component` kan användas för en enskild service (service-namn eller context-path).
|
||||||
|
Arkitekturer hämtas från `x-casaos.architectures`; builden kör fail-open per arkitektur och visar varningar i slutet för misslyckade arch-builds.
|
||||||
|
|
||||||
## Säkerhetsriktlinjer
|
## Säkerhetsriktlinjer
|
||||||
|
|
||||||
- Undvik privilegierad container, host network och `docker.sock` om det inte är absolut nödvändigt.
|
- Undvik privilegierad container, host network och `docker.sock` om det inte är absolut nödvändigt.
|
||||||
|
|||||||
Executable
+355
@@ -0,0 +1,355 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
default_repo="joafri"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
./scripts/build-and-push-image.sh --app-id <app-id> --tag <tag> [options]
|
||||||
|
|
||||||
|
Required:
|
||||||
|
--app-id <app-id> App folder under Apps/, for example: caddy-autogen
|
||||||
|
--tag <tag> Docker tag base (must not be latest)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--component <value> Optional. Service name OR build context path under app.
|
||||||
|
If omitted, build all services with build contexts.
|
||||||
|
--repo <namespace> Docker namespace/org (default: joafri)
|
||||||
|
--no-push Build only, no push or manifest creation
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
- Reads buildable services from Apps/<app-id>/docker-compose.yaml.
|
||||||
|
- Reads architectures from x-casaos.architectures in the same compose file.
|
||||||
|
- Builds each architecture separately and tags as <tag>-<arch>.
|
||||||
|
- When pushing, creates a manifest tag <tag> from successful arch tags.
|
||||||
|
- Fail-open per architecture: failed arch builds are reported as warnings.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
./scripts/build-and-push-image.sh --app-id caddy-autogen --tag 0.2.0
|
||||||
|
./scripts/build-and-push-image.sh --app-id caddy-autogen --component agent --tag 0.2.0
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
app_id=""
|
||||||
|
component=""
|
||||||
|
tag=""
|
||||||
|
repo_ns="$default_repo"
|
||||||
|
push_image=1
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--app-id)
|
||||||
|
app_id="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--component)
|
||||||
|
component="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--tag)
|
||||||
|
tag="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--repo)
|
||||||
|
repo_ns="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--no-push)
|
||||||
|
push_image=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: unknown argument: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$app_id" || -z "$tag" ]]; then
|
||||||
|
echo "ERROR: --app-id and --tag are required" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$tag" == "latest" ]]; then
|
||||||
|
echo "ERROR: tag 'latest' is not allowed by repo policy" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$app_id" =~ [^a-z0-9-] ]]; then
|
||||||
|
echo "ERROR: app-id must match [a-z0-9-]+" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$repo_ns" =~ [^a-zA-Z0-9._-] ]]; then
|
||||||
|
echo "ERROR: repo namespace contains invalid characters" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
component="${component#./}"
|
||||||
|
if [[ -n "$component" && ( "$component" == /* || "$component" == *".."* ) ]]; then
|
||||||
|
echo "ERROR: --component must be service name or relative subpath under Apps/$app_id" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
app_dir="$repo_root/Apps/$app_id"
|
||||||
|
compose_file="$app_dir/docker-compose.yaml"
|
||||||
|
|
||||||
|
if [[ ! -d "$app_dir" ]]; then
|
||||||
|
echo "ERROR: app directory not found: $app_dir" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$compose_file" ]]; then
|
||||||
|
echo "ERROR: missing compose file: $compose_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: docker is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker buildx version >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: docker buildx is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: jq is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
map_arch_to_platform() {
|
||||||
|
case "$1" in
|
||||||
|
amd64) echo "linux/amd64" ;;
|
||||||
|
arm64) echo "linux/arm64" ;;
|
||||||
|
arm) echo "linux/arm/v7" ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
read_compose_architectures() {
|
||||||
|
awk '
|
||||||
|
/^x-casaos:[[:space:]]*$/ { in_casa=1; next }
|
||||||
|
in_casa && /^[^[:space:]]/ { in_casa=0 }
|
||||||
|
in_casa && /^[[:space:]]+architectures:[[:space:]]*$/ { in_arch=1; next }
|
||||||
|
in_arch {
|
||||||
|
if ($0 ~ /^[[:space:]]*-[[:space:]]*[A-Za-z0-9_-]+[[:space:]]*$/) {
|
||||||
|
line=$0
|
||||||
|
sub(/^[[:space:]]*-[[:space:]]*/, "", line)
|
||||||
|
sub(/[[:space:]]*$/, "", line)
|
||||||
|
print line
|
||||||
|
next
|
||||||
|
}
|
||||||
|
if ($0 ~ /^[[:space:]]*$/) next
|
||||||
|
in_arch=0
|
||||||
|
}
|
||||||
|
' "$compose_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
compose_json="$(mktemp)"
|
||||||
|
cleanup() {
|
||||||
|
rm -f "$compose_json"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
docker compose -f "$compose_file" config --format json > "$compose_json"
|
||||||
|
|
||||||
|
mapfile -t all_services < <(
|
||||||
|
jq -r '.services | to_entries[] | select(.value.build != null) | .key' "$compose_json"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ ${#all_services[@]} -eq 0 ]]; then
|
||||||
|
echo "ERROR: no buildable services found in $compose_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t compose_arches < <(read_compose_architectures)
|
||||||
|
if [[ ${#compose_arches[@]} -eq 0 ]]; then
|
||||||
|
compose_arches=(amd64)
|
||||||
|
echo "WARN: no x-casaos.architectures found, defaulting to amd64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
selected_services=()
|
||||||
|
if [[ -n "$component" ]]; then
|
||||||
|
for svc in "${all_services[@]}"; do
|
||||||
|
context_abs="$(jq -r --arg svc "$svc" '.services[$svc].build.context // empty' "$compose_json")"
|
||||||
|
if [[ -z "$context_abs" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
context_rel="$context_abs"
|
||||||
|
if [[ "$context_abs" == "$app_dir" ]]; then
|
||||||
|
context_rel="."
|
||||||
|
elif [[ "$context_abs" == "$app_dir"/* ]]; then
|
||||||
|
context_rel="${context_abs#"$app_dir"/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$svc" == "$component" || "$context_rel" == "$component" ]]; then
|
||||||
|
selected_services+=("$svc")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#selected_services[@]} -eq 0 ]]; then
|
||||||
|
echo "ERROR: --component '$component' did not match any build service name or context path" >&2
|
||||||
|
echo "Build services in this app:" >&2
|
||||||
|
for svc in "${all_services[@]}"; do
|
||||||
|
ctx="$(jq -r --arg svc "$svc" '.services[$svc].build.context // empty' "$compose_json")"
|
||||||
|
if [[ "$ctx" == "$app_dir" ]]; then
|
||||||
|
ctx_rel="."
|
||||||
|
elif [[ "$ctx" == "$app_dir"/* ]]; then
|
||||||
|
ctx_rel="${ctx#"$app_dir"/}"
|
||||||
|
else
|
||||||
|
ctx_rel="$ctx"
|
||||||
|
fi
|
||||||
|
echo " - $svc (context: $ctx_rel)" >&2
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
selected_services=("${all_services[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
git_sha="unknown"
|
||||||
|
if git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
git_sha="$(git -C "$repo_root" rev-parse --short HEAD)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
source_url="unknown"
|
||||||
|
if git -C "$repo_root" remote get-url origin >/dev/null 2>&1; then
|
||||||
|
source_url="$(git -C "$repo_root" remote get-url origin)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "App: $app_id"
|
||||||
|
echo "Compose: $compose_file"
|
||||||
|
echo "Services: ${selected_services[*]}"
|
||||||
|
echo "Architectures: ${compose_arches[*]}"
|
||||||
|
if [[ "$push_image" -eq 1 ]]; then
|
||||||
|
echo "Mode: build + push + manifest"
|
||||||
|
else
|
||||||
|
echo "Mode: build only (--no-push)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
warn_messages=()
|
||||||
|
total_success=0
|
||||||
|
|
||||||
|
for svc in "${selected_services[@]}"; do
|
||||||
|
context_abs="$(jq -r --arg svc "$svc" '.services[$svc].build.context // empty' "$compose_json")"
|
||||||
|
dockerfile_rel="$(jq -r --arg svc "$svc" '.services[$svc].build.dockerfile // "Dockerfile"' "$compose_json")"
|
||||||
|
|
||||||
|
if [[ -z "$context_abs" ]]; then
|
||||||
|
warn_messages+=("$svc: missing build context in compose config, skipped")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$context_abs" in
|
||||||
|
"$app_dir"|"$app_dir"/*) ;;
|
||||||
|
*)
|
||||||
|
warn_messages+=("$svc: build context outside app dir ($context_abs), skipped")
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
dockerfile_abs="$context_abs/$dockerfile_rel"
|
||||||
|
if [[ ! -f "$dockerfile_abs" ]]; then
|
||||||
|
warn_messages+=("$svc: Dockerfile missing at $dockerfile_abs, skipped")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
image_repo="${repo_ns}/${app_id}-${svc}"
|
||||||
|
success_arch_refs=()
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Service: $svc ==="
|
||||||
|
echo "Context: $context_abs"
|
||||||
|
echo "Dockerfile: $dockerfile_abs"
|
||||||
|
echo "Image repo: $image_repo"
|
||||||
|
|
||||||
|
for arch in "${compose_arches[@]}"; do
|
||||||
|
if ! platform="$(map_arch_to_platform "$arch")"; then
|
||||||
|
warn_messages+=("$svc: unsupported architecture '$arch' in x-casaos.architectures")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
arch_ref="${image_repo}:${tag}-${arch}"
|
||||||
|
echo "Building $svc for $arch ($platform) -> $arch_ref"
|
||||||
|
|
||||||
|
build_cmd=(
|
||||||
|
docker buildx build
|
||||||
|
--platform "$platform"
|
||||||
|
--file "$dockerfile_abs"
|
||||||
|
--tag "$arch_ref"
|
||||||
|
--label "org.opencontainers.image.source=$source_url"
|
||||||
|
--label "org.opencontainers.image.revision=$git_sha"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "$push_image" -eq 1 ]]; then
|
||||||
|
build_cmd+=(--push)
|
||||||
|
else
|
||||||
|
build_cmd+=(--load)
|
||||||
|
fi
|
||||||
|
|
||||||
|
build_cmd+=("$context_abs")
|
||||||
|
|
||||||
|
if "${build_cmd[@]}"; then
|
||||||
|
success_arch_refs+=("$arch_ref")
|
||||||
|
total_success=$((total_success + 1))
|
||||||
|
else
|
||||||
|
warn_messages+=("$svc: build failed for arch '$arch' ($platform)")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#success_arch_refs[@]} -eq 0 ]]; then
|
||||||
|
warn_messages+=("$svc: no successful architecture builds")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$push_image" -eq 1 ]]; then
|
||||||
|
manifest_ref="${image_repo}:${tag}"
|
||||||
|
echo "Creating manifest tag: $manifest_ref"
|
||||||
|
if docker buildx imagetools create -t "$manifest_ref" "${success_arch_refs[@]}"; then
|
||||||
|
echo "Manifest created: $manifest_ref"
|
||||||
|
else
|
||||||
|
warn_messages+=("$svc: failed to create manifest tag ${manifest_ref}")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
first_ref="${success_arch_refs[0]}"
|
||||||
|
local_ref="${image_repo}:${tag}"
|
||||||
|
if docker tag "$first_ref" "$local_ref"; then
|
||||||
|
echo "Tagged local alias: $local_ref -> $first_ref"
|
||||||
|
else
|
||||||
|
warn_messages+=("$svc: failed to tag local alias ${local_ref}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [[ "$total_success" -eq 0 ]]; then
|
||||||
|
echo "ERROR: no successful builds were produced" >&2
|
||||||
|
if [[ ${#warn_messages[@]} -gt 0 ]]; then
|
||||||
|
echo "Warnings:" >&2
|
||||||
|
for w in "${warn_messages[@]}"; do
|
||||||
|
echo " - $w" >&2
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Build completed with $total_success successful arch build(s)."
|
||||||
|
|
||||||
|
if [[ ${#warn_messages[@]} -gt 0 ]]; then
|
||||||
|
echo
|
||||||
|
echo "Warnings (fail-open):"
|
||||||
|
for w in "${warn_messages[@]}"; do
|
||||||
|
echo " - $w"
|
||||||
|
done
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user