neko/build
2025-03-28 21:32:02 +01:00

318 lines
9.5 KiB
Bash
Executable file

#!/bin/bash
set -e
cd "$(dirname "$0")"
# check if docker buildx is available, its not docker-buildx command but rather subcommand of docker
if [ -x "$(command -v docker)" ]; then
if docker buildx version >/dev/null 2>&1; then
if [ -z "$USE_BUILDX" ]; then
USE_BUILDX=1
fi
fi
fi
#
# This script builds the neko base image and all the applications
#
function log() {
echo "$(date +'%Y-%m-%d %H:%M:%S') - [NEKO] - $1" > /dev/stderr
}
function help() {
echo "Usage: $0"
echo " -p, --platform : The platform (default: linux/amd64)"
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 : The base image name (default: <repository>[<flavor>-]base:<tag>)"
echo " -a, --app : 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"
}
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|-b) BASE_IMAGE="$2"; shift ;;
--app|-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
local FULL_TAGS=()
# if the image name starts with local/, just use the tag as is
if [[ "$APPLICATION_IMAGE" == *"local/"* ]]; then
FULL_TAGS=("$APPLICATION_IMAGE")
else
# get list of tags in full format: <image>:<tag>
local IMAGE_NO_TAG="${APPLICATION_IMAGE%:*}"
for T in "${TAGS[@]}"; do
FULL_TAGS+=("$IMAGE_NO_TAG:$T")
done
fi
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
PLATFORM="linux/amd64"
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?"
log "Building base image: $BASE_IMAGE"
log "[STAGE 1]: Building local/neko-xorg-deps image"
build_image local/neko-xorg-deps runtime/xorg-deps/
log "[STAGE 2]: Building local/neko-server image"
build_image local/neko-server server/
log "[STAGE 3]: Building local/neko-client image"
build_image local/neko-client client/
if [ -z "$FLAVOR" ]; then
RUNTIME_IMAGE="local/neko-runtime"
log "[STAGE 4]: Building $RUNTIME_IMAGE image"
build_image $RUNTIME_IMAGE runtime/
else
RUNTIME_IMAGE="local/neko-$FLAVOR-runtime"
log "[STAGE 4]: Building $RUNTIME_IMAGE image"
build_image $RUNTIME_IMAGE -f runtime/Dockerfile.$FLAVOR runtime/
fi
log "[STAGE 5]: Building $BASE_IMAGE image"
build_image $BASE_IMAGE -f - . <<EOF
FROM local/neko-xorg-deps AS xorg-deps
FROM local/neko-server AS server
FROM local/neko-client AS client
FROM $RUNTIME_IMAGE AS runtime
COPY --from=xorg-deps /usr/local/lib/xorg/modules/drivers/dummy_drv.so /usr/lib/xorg/modules/drivers/dummy_drv.so
COPY --from=xorg-deps /usr/local/lib/xorg/modules/input/neko_drv.so /usr/lib/xorg/modules/input/neko_drv.so
COPY --from=server /src/bin/plugins/ /etc/neko/plugins/
COPY --from=server /src/bin/neko /usr/bin/neko
COPY --from=client /src/dist/ /var/www
COPY config.yml /etc/neko/neko.yaml
EOF