diff --git a/.github/workflows/build-and-test-job.yml b/.github/workflows/build-and-test-php.yml similarity index 100% rename from .github/workflows/build-and-test-job.yml rename to .github/workflows/build-and-test-php.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..f63afaf --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,64 @@ +on: + workflow_call: + inputs: + image: + required: true + type: string + target: + required: true + type: string + tests: + required: false + type: string + default: '' + +jobs: + build-and-test: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get version from branch name + run: | + export BRANCH=${GITHUB_REF##*/} + echo "VERSION=v${BRANCH/.x/}" >> $GITHUB_ENV + + - name: Build test image + uses: docker/build-push-action@v5 + with: + context: ./src + load: true + pull: true + tags: ghcr.io/sitepilot/${{ inputs.image }}:build + target: ${{ inputs.target }} + cache-from: type=gha,scope=${{ inputs.image }}-${{ env.VERSION }} + + - name: Run unit tests + run: ${{ inputs.tests }} + if: ${{ inputs.tests != '' }} + + - name: Build and publish image + uses: docker/build-push-action@v5 + with: + context: ./src + push: ${{ github.event_name != 'pull_request' }} + tags: ghcr.io/sitepilot/${{ inputs.image }}:${{ env.VERSION }} + platforms: linux/amd64, linux/arm64 + target: ${{ inputs.target }} + provenance: false + cache-to: type=gha,mode=max,scope=${{ inputs.image }}-${{ env.VERSION }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c8dc41..3587d71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,8 +12,20 @@ on: workflow_dispatch: jobs: + runtime: + uses: ./.github/workflows/build-and-test.yml + with: + image: runtime + target: runtime + tests: | + docker run ghcr.io/sitepilot/runtime:build id + docker run --user=root -e RUNTIME_PASSWORD=supersecret ghcr.io/sitepilot/runtime:build id app + docker run --user=root -e RUNTIME_UID=1001 -e RUNTIME_GID=1001 ghcr.io/sitepilot/runtime:build id app + docker run --user=root -e RUNTIME_USER=test-user -e RUNTIME_GROUP=test-group ghcr.io/sitepilot/runtime:build id test-user + secrets: inherit + php-cli: - uses: ./.github/workflows/build-and-test-job.yml + uses: ./.github/workflows/build-and-test-php.yml with: target: cli tests: | @@ -23,7 +35,7 @@ jobs: secrets: inherit php-fpm: - uses: ./.github/workflows/build-and-test-job.yml + uses: ./.github/workflows/build-and-test-php.yml with: target: fpm tests: | @@ -31,7 +43,7 @@ jobs: secrets: inherit php-nginx: - uses: ./.github/workflows/build-and-test-job.yml + uses: ./.github/workflows/build-and-test-php.yml with: target: nginx tests: | @@ -40,7 +52,7 @@ jobs: secrets: inherit php-ols: - uses: ./.github/workflows/build-and-test-job.yml + uses: ./.github/workflows/build-and-test-php.yml with: target: ols tests: | diff --git a/docker-compose.yml b/docker-compose.yml index 2ab949d..4baaec1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,12 @@ services: + runtime: + image: ghcr.io/sitepilot/runtime:v1 + profiles: + - donotstart + build: + context: ./src + target: runtime + php-cli: image: ghcr.io/sitepilot/php-cli:8.1 profiles: diff --git a/src/Dockerfile b/src/Dockerfile index 1935c18..1e66959 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -1,6 +1,49 @@ +# --------------- [RUNTIME] --------------- # + +FROM ubuntu:22.04 AS runtime + +ARG TARGETARCH +ARG DOCKERIZE_VERSION=v0.7.0 + +LABEL org.opencontainers.image.source = "https://github.com/sitepilot/docker-runtime" + +ENV DEBIAN_FRONTEND="noninteractive" \ + RUNTIME_UID=1000 \ + RUNTIME_GID=1000 \ + RUNTIME_USER=app \ + RUNTIME_GROUP=app \ + RUNTIME_PASSWORD="" \ + RUNTIME_LOG_LEVEL=1 \ + RUNTIME_DIR=/runtime \ + RUNTIME_WORKDIR=/app \ + RUNTIME_BIN_DIR=/runtime/bin \ + RUNTIME_INC_DIR=/runtime/inc \ + RUNTIME_RUN_DIR=/runtime/run \ + RUNTIME_LOGS_DIR=/runtime/logs \ + RUNTIME_TEMPLATES_DIR=/runtime/templates \ + RUNTIME_ENTRYPOINT_DIR=/runtime/entrypoint.d \ + RUNTIME_BOOTED_FILE=/runtime/booted + +ENV PATH="$PATH:$RUNTIME_BIN_DIR" + +ADD runtime/runtime/bin/rt-install $RUNTIME_BIN_DIR/rt-install + +RUN rt-install curl wget nano ca-certificates \ + && groupadd -r -g ${RUNTIME_GID} ${RUNTIME_GROUP} \ + && useradd --no-log-init -r -s /usr/bin/bash -m -d ${RUNTIME_WORKDIR} -u ${RUNTIME_UID} -g ${RUNTIME_GID} ${RUNTIME_USER} \ + && wget -O - https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-$TARGETARCH-$DOCKERIZE_VERSION.tar.gz | tar xzf - -C /usr/local/bin + +COPY --chown=$RUNTIME_UID:$RUNTIME_GID runtime / + +USER $RUNTIME_UID + +WORKDIR $RUNTIME_WORKDIR + +ENTRYPOINT ["runtime"] + # --------------- [PHP-CLI] --------------- # -FROM ghcr.io/sitepilot/runtime:v1 AS cli +FROM runtime AS cli USER root diff --git a/src/runtime/runtime/bin/rt-chown b/src/runtime/runtime/bin/rt-chown new file mode 100755 index 0000000..e69de29 diff --git a/src/runtime/runtime/bin/rt-install b/src/runtime/runtime/bin/rt-install new file mode 100755 index 0000000..6375759 --- /dev/null +++ b/src/runtime/runtime/bin/rt-install @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -e +set -u + +export DEBIAN_FRONTEND=noninteractive + +n=0 +max=2 + +until [ $n -gt $max ]; do + set +e + ( + apt-get update -qq && + apt-get install -y --no-install-recommends "$@" + ) + CODE=$? + set -e + if [ $CODE -eq 0 ]; then + break + fi + if [ $n -eq $max ]; then + exit $CODE + fi + echo "Apt install failed, retrying..." + n=$(($n + 1)) +done + +apt-get clean + +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* \ No newline at end of file diff --git a/src/runtime/runtime/bin/rt-rchown b/src/runtime/runtime/bin/rt-rchown new file mode 100755 index 0000000..9336df3 --- /dev/null +++ b/src/runtime/runtime/bin/rt-rchown @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e +set -u + +chown -R $RUNTIME_UID:$RUNTIME_GID "${@}" \ No newline at end of file diff --git a/src/runtime/runtime/bin/runtime b/src/runtime/runtime/bin/runtime new file mode 100755 index 0000000..44e3a35 --- /dev/null +++ b/src/runtime/runtime/bin/runtime @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -e +set -u + +source "$RUNTIME_INC_DIR/bash-functions" + +for file in "$RUNTIME_ENTRYPOINT_DIR"/*; do + if [ -f "$file" ]; then + source "$file" + fi +done + +touch $RUNTIME_BOOTED_FILE + +exec "$@" \ No newline at end of file diff --git a/src/runtime/runtime/entrypoint.d/10-logo b/src/runtime/runtime/entrypoint.d/10-logo new file mode 100755 index 0000000..5a1ebbc --- /dev/null +++ b/src/runtime/runtime/entrypoint.d/10-logo @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +if [ "$RUNTIME_LOG_LEVEL" -le 2 ]; then +cat << EOF + + _____ _ _ _ _ _ + / ____(_) | (_) | | | +| (___ _| |_ ___ _ __ _| | ___ | |_ + \___ \| | __/ _ \ '_ \| | |/ _ \| __| + ____) | | || __/ |_) | | | (_) | |_ +|_____/|_|\__\___| .__/|_|_|\___/ \__| + | | + |_| + +EOF +fi diff --git a/src/runtime/runtime/entrypoint.d/20-init b/src/runtime/runtime/entrypoint.d/20-init new file mode 100755 index 0000000..3b10dd0 --- /dev/null +++ b/src/runtime/runtime/entrypoint.d/20-init @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +if [ "$EUID" -eq 0 ]; then + warning "Container is running as root" + + if [[ ! -f $RUNTIME_BOOTED_FILE ]]; then + if [[ $RUNTIME_USER != "app" ]]; then + info "Updating user name to $RUNTIME_USER" + usermod -l $RUNTIME_USER app + fi + + if [[ $RUNTIME_GROUP != "app" ]]; then + info "Updating group name to $RUNTIME_GROUP" + groupmod -n $RUNTIME_GROUP app + fi + + if [[ $RUNTIME_UID != 1000 ]]; then + info "Updating UID to $RUNTIME_UID" + usermod -o -u "$RUNTIME_UID" "$RUNTIME_USER" + fi + + if [[ $RUNTIME_GID != 1000 ]]; then + info "Updating GID to $RUNTIME_UID" + groupmod -o -g "$RUNTIME_GID" "$RUNTIME_GROUP" + fi + + if [[ ! -z "$RUNTIME_PASSWORD" ]]; then + info "Updating user password" + echo "$RUNTIME_USER:$RUNTIME_PASSWORD" | chpasswd + fi + fi + + rt-rchown $RUNTIME_DIR + rt-chown $RUNTIME_WORKDIR +else + if [[ $RUNTIME_USER != "app" ]] \ + || [[ $RUNTIME_GROUP != "app" ]] \ + || [[ $RUNTIME_UID != 1000 ]] \ + || [[ $RUNTIME_GID != 1000 ]]; then + throw "Container is not running as root, user cannot be changed." + fi +fi \ No newline at end of file diff --git a/src/runtime/runtime/entrypoint.d/30-info b/src/runtime/runtime/entrypoint.d/30-info new file mode 100755 index 0000000..35214d8 --- /dev/null +++ b/src/runtime/runtime/entrypoint.d/30-info @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +debug "Container user: ${RUNTIME_USER} (uid: $(id -u $RUNTIME_USER))" +debug "Container group: ${RUNTIME_GROUP} (gid: $(id -g $RUNTIME_USER))" +debug "Container workdir: $PWD" \ No newline at end of file diff --git a/src/runtime/runtime/inc/bash-functions b/src/runtime/runtime/inc/bash-functions new file mode 100755 index 0000000..e6fab98 --- /dev/null +++ b/src/runtime/runtime/inc/bash-functions @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +function to_bool() +{ + if [[ $1 == 'true' || $1 == true || $1 == 1 || $1 == 'Y' || $1 == 'y' ]]; then + true + else + false + fi +} + +function debug() +{ + if [ "$RUNTIME_LOG_LEVEL" -eq 1 ]; then + echo "✳️ $1" >&2 + fi +} + +function info() +{ + if [ "$RUNTIME_LOG_LEVEL" -le 2 ]; then + echo "ℹ️ $1" >&2 + fi +} + +function warning() +{ + if [ "$RUNTIME_LOG_LEVEL" -le 3 ]; then + echo "⚠️ $1" >&2 + fi +} + +function error() +{ + if [ "$RUNTIME_LOG_LEVEL" -le 4 ]; then + echo "‼️ $1" 1>&2 + fi +} + +function throw() +{ + EXIT_CODE=${2:-1} + echo "‼️ $1" 1>&2 + exit $EXIT_CODE +} + +function template() +{ + info "Generating $1 to $2" + dockerize -template $RUNTIME_TEMPLATES_DIR/$1:$2 +} + +function generate_certs() +{ + if [ ! -f $1/ssl.key ]; then + info "Generating self-signed SSL certificate to $1" + openssl req -x509 -subj "/C=NL/ST=Gelderland/L=Doetinchem/O=Sitepilot/CN=app.test" -nodes -newkey rsa:2048 -keyout $1/ssl.key -out $1/ssl.crt -days 365 &> /dev/null + fi +} diff --git a/src/runtime/runtime/logs/.gitkeep b/src/runtime/runtime/logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/runtime/runtime/run/.gitkeep b/src/runtime/runtime/run/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/runtime/runtime/templates/.gitkeep b/src/runtime/runtime/templates/.gitkeep new file mode 100644 index 0000000..e69de29