mirror of
https://github.com/m1k1o/neko.git
synced 2025-04-28 18:06:20 +02:00
322 lines
9.9 KiB
Bash
Executable file
322 lines
9.9 KiB
Bash
Executable file
#!/bin/bash
|
|
set -e
|
|
cd "$(dirname "$0")"
|
|
|
|
#
|
|
# This script builds the neko base image and all the applications
|
|
#
|
|
|
|
# disable buildx because of https://github.com/docker/buildx/issues/847
|
|
# if you want to use buildx, set USE_BUILDX=1
|
|
if [ -z "$USE_BUILDX" ]; then
|
|
USE_BUILDX=0
|
|
fi
|
|
|
|
# check if docker buildx is available, its not docker-buildx command but rather subcommand of docker
|
|
#if [ -z "$USE_BUILDX" ] && [ -x "$(command -v docker)" ]; then
|
|
# if docker buildx version >/dev/null 2>&1; then
|
|
# USE_BUILDX=1
|
|
# fi
|
|
#fi
|
|
|
|
function log() {
|
|
echo "$(date +'%Y-%m-%d %H:%M:%S') - [NEKO] - $1" > /dev/stderr
|
|
}
|
|
|
|
function help() {
|
|
echo "Usage: $0 [options] [image]"
|
|
echo
|
|
echo "Options:"
|
|
echo " -p, --platform : The platform (default: system architecture)"
|
|
echo " -r, --repository : The repository prefix (default: ghcr.io/m1k1o/neko)"
|
|
echo " -t, --tag : The image tag, can be specified multiple times, if not specified"
|
|
echo " uses 'latest' and if available, current git semver tag (v*.*.*)"
|
|
echo " -f, --flavor : The flavor, if not specified, builds without flavor"
|
|
echo " -b, --base_image : The base image name (default: <repository>[<flavor>-]base:<tag>)"
|
|
echo " -a, --application : The app to build, if not specified, builds the base image"
|
|
echo " -y, --yes : Skip confirmation prompts"
|
|
echo " --no-cache : Build without docker cache"
|
|
echo " --push : Push the image to the registry after building"
|
|
echo " -h, --help : Show this help message"
|
|
echo
|
|
echo "Positional arguments:"
|
|
echo " <image> : The image name, if not specified, uses the full image name"
|
|
echo " in the format <repository>/<flavor>-<application>:<tag>"
|
|
echo " Example: ghcr.io/m1k1o/neko/nvidia-firefox:latest"
|
|
echo " You can override any of the above options by specifying them"
|
|
echo " after the image name."
|
|
echo
|
|
echo "Environment variables:"
|
|
echo " USE_BUILDX : Set to 1 to use docker buildx instead of docker build"
|
|
echo " (default: 0)"
|
|
echo " CLIENT_DIST : The client dist file to use, if not specified, builds them"
|
|
echo " from the source code."
|
|
echo " (options) : Options can be specified as environment variables, for example:"
|
|
echo " PLATFORM=linux/arm64 $0 --repository ghcr.io/m1k1o/neko"
|
|
}
|
|
|
|
FULL_IMAGE=""
|
|
while [[ "$#" -gt 0 ]]; do
|
|
case $1 in
|
|
--platform|-p) PLATFORM="$2"; shift ;;
|
|
--repository|-r) REPOSITORY="$2"; shift ;;
|
|
--tag|-t) TAGS+=("$2"); TAG="$2"; shift ;;
|
|
--flavor|-f) FLAVOR="$2"; shift ;;
|
|
--base_image|-b) BASE_IMAGE="$2"; shift ;;
|
|
--application|-a) APPLICATION="$2"; shift ;;
|
|
--yes|-y) YES=1 ;;
|
|
--no-cache) NO_CACHE="--no-cache" log "Building without cache" ;;
|
|
--push) PUSH=1 ;;
|
|
--help|-h) help; exit 0 ;;
|
|
-*) log "Unknown parameter passed: $1"; help; exit 1 ;;
|
|
*)
|
|
if [ -z "$FULL_IMAGE" ]; then
|
|
FULL_IMAGE="$1"
|
|
|
|
# extracts image, flavor, app and tag from the full image name
|
|
# example:
|
|
# ghcr.io/m1k1o/neko/nvidia-firefox:latest
|
|
# will be split into:
|
|
# REPOSITORY=ghcr.io/m1k1o/neko
|
|
# FLAVOR=nvidia
|
|
# APPLICATION=firefox
|
|
# TAG=latest
|
|
|
|
# remove the tag from the image name
|
|
if [[ "$FULL_IMAGE" == *":"* ]]; then
|
|
# removes everything before the last :
|
|
TAG="${FULL_IMAGE##*:}" # will be latest
|
|
TAGS+=("$TAG")
|
|
# removes everything after the last :
|
|
FULL_IMAGE="${FULL_IMAGE%:*}" # will be ghcr.io/m1k1o/neko/nvidia-firefox
|
|
fi
|
|
# extract the image name and save the rest to REPOSITORY
|
|
if [[ "$FULL_IMAGE" == *"/"* ]]; then
|
|
# removes everything after the last /
|
|
REPOSITORY="${FULL_IMAGE%/*}" # will be ghcr.io/m1k1o/neko
|
|
# removes everything before the last /
|
|
FULL_IMAGE="${FULL_IMAGE##*/}" # will be nvidia-firefox
|
|
fi
|
|
# extract the flavor and application name
|
|
if [[ "$FULL_IMAGE" == *"-"* ]]; then
|
|
# removes everything after the last -
|
|
FLAVOR="${FULL_IMAGE%-*}" # will be nvidia
|
|
# removes everything before the last -
|
|
APPLICATION="${FULL_IMAGE#*-}" # will be firefox
|
|
else
|
|
# no flavor specified so use the full image name as application name
|
|
APPLICATION="$FULL_IMAGE" # will be firefox
|
|
fi
|
|
# if application name is base, set it to empty
|
|
if [ "$APPLICATION" == "base" ]; then
|
|
APPLICATION=""
|
|
fi
|
|
else
|
|
log "Unknown positional argument: $1"
|
|
help
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
function prompt() {
|
|
if [ ! -z "$YES" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local OK=""
|
|
while [ -z "$OK" ]; do
|
|
read -p "$1 (yes/no) " REPLY
|
|
case "$REPLY" in
|
|
yes|YES|y|Y)
|
|
OK=1
|
|
;;
|
|
no|NO|n|N)
|
|
log "Aborting build."
|
|
exit 1
|
|
;;
|
|
*)
|
|
log "Please answer 'yes' or 'no'."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
function build_image() {
|
|
# first argument is the image name, rest are the build args
|
|
local APPLICATION_IMAGE="$1"
|
|
shift
|
|
|
|
# get list of tags in full format: <image>:<tag>
|
|
local IMAGE_NO_TAG="${APPLICATION_IMAGE%:*}"
|
|
local FULL_TAGS=()
|
|
for T in "${TAGS[@]}"; do
|
|
FULL_TAGS+=("$IMAGE_NO_TAG:$T")
|
|
done
|
|
|
|
if [ -z "$USE_BUILDX" ] || [ "$USE_BUILDX" != "1" ]; then
|
|
# if buildx is not available, use docker build
|
|
docker build \
|
|
--platform $PLATFORM \
|
|
$NO_CACHE \
|
|
-t $APPLICATION_IMAGE \
|
|
$@
|
|
|
|
# tag and push the image to the registry
|
|
for T in $FULL_TAGS; do
|
|
# do not tag if the tag is the same as the image tag
|
|
if [ "$T" != "$APPLICATION_IMAGE" ]; then
|
|
log "Tagging $APPLICATION_IMAGE as $T"
|
|
docker tag "$APPLICATION_IMAGE" "$T"
|
|
fi
|
|
|
|
# if push is enabled, push the image to the registry
|
|
if [ ! -z "$PUSH" ]; then
|
|
log "Pushing $T to registry"
|
|
docker push "$T"
|
|
fi
|
|
done
|
|
else
|
|
# create cmd for buildx, repeat --tag for each tag
|
|
local CMD=""
|
|
for T in $FULL_TAGS; do
|
|
CMD+="--tag $T "
|
|
done
|
|
# if push is enabled, add --push
|
|
if [ ! -z "$PUSH" ]; then
|
|
CMD+="--push "
|
|
fi
|
|
# if no cache is enabled, add --no-cache
|
|
if [ ! -z "$NO_CACHE" ]; then
|
|
CMD+="--no-cache "
|
|
fi
|
|
|
|
# buildx build command
|
|
docker buildx build \
|
|
--platform $PLATFORM \
|
|
--load --pull $CMD \
|
|
$@
|
|
fi
|
|
}
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
if [ -z "$USE_BUILDX" ] || [ "$USE_BUILDX" != "1" ]; then
|
|
log "Using docker build"
|
|
else
|
|
log "Using docker buildx"
|
|
fi
|
|
|
|
if [ -z "$PLATFORM" ]; then
|
|
# use system architecture if not specified
|
|
if [ "$(uname -m)" == "x86_64" ]; then
|
|
PLATFORM="linux/amd64"
|
|
elif [ "$(uname -m)" == "aarch64" ]; then
|
|
PLATFORM="linux/arm64"
|
|
elif [ "$(uname -m)" == "armv7l" ]; then
|
|
PLATFORM="linux/arm/v7"
|
|
else
|
|
log "Unknown architecture: $(uname -m)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
log "Using platform: $PLATFORM"
|
|
|
|
if [ -z "$REPOSITORY" ]; then
|
|
REPOSITORY="ghcr.io/m1k1o/neko"
|
|
fi
|
|
log "Using repository: $REPOSITORY"
|
|
|
|
if [ -z "$TAG" ]; then
|
|
# if git is available, use the latest tag from git
|
|
if [ -d ".git" ] && [ -x "$(command -v git)" ]; then
|
|
# get the latest version tag from git (v*.*.*)
|
|
GIT_VER=$(git describe --tags --exact-match --match "v*.*.*" 2>/dev/null || true)
|
|
if [ ! -z "$GIT_VER" ]; then
|
|
# split to major, minor and patch
|
|
IFS='.' read -r MAJOR MINOR PATCH <<< "${GIT_VER#v}"
|
|
# append MAJOR.MINOR.PATCH, MAJOR.MINOR and MAJOR to the tags
|
|
TAGS=("$MAJOR.$MINOR.$PATCH" "$MAJOR.$MINOR" "$MAJOR")
|
|
#else
|
|
# # if no version tag is found, use the latest commit hash
|
|
# GIT_VER=$(git rev-parse HEAD
|
|
# TAGS=("sha-$GIT_VER")
|
|
fi
|
|
fi
|
|
TAG="latest"
|
|
TAGS=("$TAG" "${TAGS[@]}")
|
|
fi
|
|
|
|
for T in "${TAGS[@]}"; do
|
|
log "Using tag: $T"
|
|
done
|
|
|
|
if [ -z "$FLAVOR" ]; then
|
|
log "No flavor specified, building without flavor"
|
|
else
|
|
log "Using flavor: $FLAVOR"
|
|
fi
|
|
|
|
if [ -z "$BASE_IMAGE" ]; then
|
|
if [ -z "$FLAVOR" ]; then
|
|
BASE_IMAGE="$REPOSITORY/base:$TAG"
|
|
else
|
|
BASE_IMAGE="$REPOSITORY/$FLAVOR-base:$TAG"
|
|
fi
|
|
fi
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
if [ ! -z "$APPLICATION" ]; then
|
|
log "Building application: $APPLICATION"
|
|
log "Using base image: $BASE_IMAGE"
|
|
|
|
# check if application directory exists
|
|
APPLICATION_DIR="apps/$APPLICATION"
|
|
if [ ! -d "$APPLICATION_DIR" ]; then
|
|
log "Application directory $APPLICATION_DIR does not exist."
|
|
exit 1
|
|
fi
|
|
|
|
# flavor is specified, append it to the image name and Dockerfile
|
|
APPLICATION_IMAGE="$REPOSITORY/$APPLICATION:$TAG"
|
|
APPLICATION_DOCKERFILE="apps/$APPLICATION/Dockerfile"
|
|
if [ ! -z "$FLAVOR" ]; then
|
|
APPLICATION_IMAGE="$REPOSITORY/$FLAVOR-$APPLICATION"
|
|
# if application flavor is specified and Dockerfile exists, use it
|
|
if [ -f "$APPLICATION_DIR/Dockerfile.$FLAVOR" ]; then
|
|
APPLICATION_DOCKERFILE="$APPLICATION_DIR/Dockerfile.$FLAVOR"
|
|
fi
|
|
fi
|
|
|
|
prompt "Are you sure you want to build $APPLICATION_IMAGE from $APPLICATION_DOCKERFILE?"
|
|
|
|
log "Building $APPLICATION_IMAGE image from $APPLICATION_DOCKERFILE"
|
|
build_image $APPLICATION_IMAGE \
|
|
--build-arg="BASE_IMAGE=$BASE_IMAGE" \
|
|
-f $APPLICATION_DOCKERFILE \
|
|
$APPLICATION_DIR
|
|
|
|
exit 0
|
|
fi
|
|
|
|
# --------------------------------------------------------------------
|
|
|
|
prompt "Are you sure you want to build $BASE_IMAGE?"
|
|
|
|
RUNTIME_DOCKERFILE="Dockerfile"
|
|
if [ ! -z "$FLAVOR" ] && [ -f "runtime/Dockerfile.$FLAVOR" ]; then
|
|
RUNTIME_DOCKERFILE="Dockerfile.$FLAVOR"
|
|
fi
|
|
|
|
log "Building base image: $BASE_IMAGE"
|
|
docker run --rm -i \
|
|
-v ./:/src \
|
|
-e "RUNTIME_DOCKERFILE=$RUNTIME_DOCKERFILE" \
|
|
--workdir /src \
|
|
--entrypoint go \
|
|
golang:1.24-bullseye \
|
|
run utils/docker/main.go \
|
|
-i Dockerfile.tmpl -client "$CLIENT_DIST" | build_image $BASE_IMAGE -f - .
|