#!/bin/bash set -e cd "$(dirname "$0")" # disable buildx because of https://github.com/docker/buildx/issues/847 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 # # 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: [-]base:)" 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: : 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?" 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 ./docker/main.go \ -i Dockerfile.tmpl -client $CLIENT_DIST | build_image $BASE_IMAGE -f - .