Rewrote license activation to use a lock to prevent race-conditions.

This commit is contained in:
2025-12-22 00:42:23 -08:00
parent 71b6b94a3a
commit e427bfd6b9
7 changed files with 98 additions and 20 deletions

View File

@@ -1,11 +1,19 @@
#Arg is replaced with the desired Unity container. #Arg is replaced with the desired Unity container.
ARG IMAGE=unityci/editor:2023.1.16f1-windows-mono-3 ARG IMAGE=ubuntu:latest
FROM ${IMAGE} FROM ${IMAGE}
ARG DOCKER_VERSION=20.10.23
RUN apt-get update RUN apt-get update
RUN apt-get install -y software-properties-common apt-transport-https wget chromium-browser RUN apt-get install -y software-properties-common apt-transport-https wget chromium-browser
RUN add-apt-repository -y ppa:savoury1/chromium RUN add-apt-repository -y ppa:savoury1/chromium
RUN apt-get update && apt-get install -y chromium-browser RUN apt-get update && \
apt-get install -y chromium-browser && \
apt-get install -y curl
# Install Docker CLI (static binary - much cleaner)
RUN apt-get update && apt-get install -y curl
RUN curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz | tar -xzf - --strip=1 -C /usr/local/bin docker/docker
#ADD https://minio.studiowhy.net/hackmd/UnityBuilder /usr/local/bin/ #ADD https://minio.studiowhy.net/hackmd/UnityBuilder /usr/local/bin/
COPY UnityBuilder /usr/local/bin/ COPY UnityBuilder /usr/local/bin/
@@ -15,10 +23,5 @@ COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh
COPY scripts/. /scripts COPY scripts/. /scripts
# Commented out until better image selection is enabled.
# RUN --mount=type=secret,id=SERIAL \
# --mount=type=secret,id=USERNAME \
# --mount=type=secret,id=PASSWORD \
# bash /scripts/activate_license.sh
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1,32 @@
#!/bin/bash
# Script to acquire a Docker-based lock for Unity image retagging
# Usage: acquire_lock.sh <lockName>
LOCK_NAME="$1"
TIMEOUT=10
WAIT_TIME=0.2
if [ -z "$LOCK_NAME" ]; then
echo "Error: LOCK_NAME parameter is required"
exit 1
fi
echo "Acquiring Docker lock '$LOCK_NAME' (timeout 10s)..."
# First, wait for any existing lock to be released
START_TS=$(date +%s)
while docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -qx "$LOCK_NAME"; do
NOW_TS=$(date +%s)
ELAPSED=$((NOW_TS - START_TS))
if [ "$ELAPSED" -ge $TIMEOUT ]; then
echo "Lock '$LOCK_NAME' still held after ${ELAPSED}s; proceeding anyway."
break
fi
echo "Lock '$LOCK_NAME' is held by another job. Waiting $WAIT_TIME seconds... (${ELAPSED}s elapsed)"
sleep $WAIT_TIME
done
# Now create our lock
docker pull hello-world
docker tag hello-world "$LOCK_NAME"
echo "Lock acquired: $LOCK_NAME"

View File

@@ -57,18 +57,19 @@ runs:
version: ${{ inputs.version }} version: ${{ inputs.version }}
platform: ${{ inputs.platform }} platform: ${{ inputs.platform }}
- name: "Pull Unity container." - name: "Pull Unity container."
id: lock
run: | run: |
LOCK_NAME="unity-lock:unity-lock"
echo "lockName=$LOCK_NAME" >> "$GITHUB_OUTPUT"
# Acquire Docker lock for exclusive access to retag the static image
bash ${{ github.action_path }}/acquire_lock.sh "$LOCK_NAME"
IMAGE_TAG="${{ inputs.imageTag }}"
CONTAINER="${{ steps.getContainer.outputs.container }}" CONTAINER="${{ steps.getContainer.outputs.container }}"
CACHED_CONTAINER="$CONTAINER-cached"
CACHED_CONTAINER="$CONTAINER-activated" docker build -t "$CACHED_CONTAINER" --build-arg IMAGE="$CONTAINER" ${{ github.action_path }}
# Activate the license on build. docker tag "$CACHED_CONTAINER" "$IMAGE_TAG"
# DOCKER_BUILDKIT=1 \
# SERIAL="${{ inputs.serial }}" USERNAME="${{ inputs.email }}" PASSWORD="${{ inputs.password }}" \
# docker build --secret id=SERIAL,env=SERIAL --secret id=USERNAME,env=USERNAME --secret id=PASSWORD,env=PASSWORD \
# -t $CACHED_CONTAINER --build-arg IMAGE=$CONTAINER ${{ github.action_path }}
docker build -t $CACHED_CONTAINER --build-arg IMAGE=$CONTAINER ${{ github.action_path }}
docker tag $CACHED_CONTAINER ${{ inputs.imageTag }}
shell: bash shell: bash
- name: "Get Unity Command." - name: "Get Unity Command."
id: command id: command
@@ -87,6 +88,7 @@ runs:
SSH_PUBLIC_KEY: ${{ inputs.sshPublicKey }} SSH_PUBLIC_KEY: ${{ inputs.sshPublicKey }}
SSH_PRIVATE_KEY: ${{ inputs.sshPrivateKey }} SSH_PRIVATE_KEY: ${{ inputs.sshPrivateKey }}
CATCH_ERRORS: ${{ inputs.catchErrors }} CATCH_ERRORS: ${{ inputs.catchErrors }}
LOCK_NAME: ${{ steps.lock.outputs.lockName }}
with: with:
serial: ${{ inputs.serial }} serial: ${{ inputs.serial }}
# serial: "activated" # serial: "activated"
@@ -94,4 +96,11 @@ runs:
password: ${{ inputs.password }} password: ${{ inputs.password }}
command: ${{ steps.command.outputs.command }} command: ${{ steps.command.outputs.command }}
unityBuilder: ${{ inputs.unityBuilder }} unityBuilder: ${{ inputs.unityBuilder }}
- name: "Release Unity Docker lock."
if: ${{ always() }}
run: |
LOCK_NAME="${{ steps.lock.outputs.lockName }}"
echo "Releasing Docker lock '$LOCK_NAME'..."
docker rmi "$LOCK_NAME" >/dev/null 2>&1 || true
shell: bash

View File

@@ -1,5 +1,17 @@
#!/bin/bash #!/bin/bash
SERIAL=$1; EMAIL=$2; PASSWORD=$3; COMMAND=$4; UNITY_BUILDER=$5 # Accept either positional arguments or environment variables
SERIAL=${1:-$SERIAL}
EMAIL=${2:-$EMAIL}
PASSWORD=${3:-$PASSWORD}
COMMAND=${4:-$COMMAND}
UNITY_BUILDER=${5:-$UNITY_BUILDER}
# Best-effort cleanup for any Docker lock created by the composite action.
if [[ -n "$LOCK_NAME" ]]; then
echo "Removing Docker lock '$LOCK_NAME'..."
docker rmi "$LOCK_NAME" >/dev/null 2>&1 || true
fi
DEFAULT_ARGS="-quit -logFile -" DEFAULT_ARGS="-quit -logFile -"
rm -rf $HOME/.config/unity3d rm -rf $HOME/.config/unity3d
@@ -29,7 +41,8 @@ RESULT=$?
echo "Unity command exited with code: $RESULT" echo "Unity command exited with code: $RESULT"
echo "::endgroup::" echo "::endgroup::"
echo "exitCode=$RESULT" >> "$GITHUB_OUTPUT" # Uncomment if movingback to a docker action.
#echo "exitCode=$RESULT" >> "$GITHUB_OUTPUT"
if [[ "$CATCH_ERRORS" != "true" ]]; then if [[ "$CATCH_ERRORS" != "true" ]]; then
exit $RESULT exit $RESULT

View File

@@ -18,6 +18,7 @@ check_path USERNAME
check_path PASSWORD check_path PASSWORD
if [[ -z "$SERIAL" || -z "$USERNAME" || -z "$PASSWORD" ]]; then if [[ -z "$SERIAL" || -z "$USERNAME" || -z "$PASSWORD" ]]; then
echo "Warning: License activation skipped - missing credentials"
exit 0 exit 0
fi fi
@@ -25,7 +26,16 @@ fi
if [[ "$SERIAL" == "personal" ]]; then if [[ "$SERIAL" == "personal" ]]; then
echo "Obtaining personal license for Unity..." echo "Obtaining personal license for Unity..."
UnityBuilder activate -i /usr/bin/unity-editor -u $USERNAME -p $PASSWORD UnityBuilder activate -i /usr/bin/unity-editor -u $USERNAME -p $PASSWORD
ACTIVATION_RESULT=$?
else else
echo "Activating Unity License with serial number." echo "Activating Unity License with serial number."
unity-editor $DEFAULT_ARGS -serial $SERIAL -username $USERNAME -password $PASSWORD unity-editor $DEFAULT_ARGS -serial $SERIAL -username $USERNAME -password $PASSWORD
ACTIVATION_RESULT=$?
fi fi
if [[ $ACTIVATION_RESULT -ne 0 ]]; then
echo "ERROR: Unity license activation failed with exit code $ACTIVATION_RESULT"
exit $ACTIVATION_RESULT
fi
echo "Unity license activated successfully"

View File

@@ -138,6 +138,16 @@ runs:
fi fi
shell: bash shell: bash
- name: "Upload Artifacts." - name: "Upload Artifacts."
id: upload_artifacts
uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4
with:
name: "${{ steps.get_product_name.outputs.nameShort }}_${{ inputs.artifactSuffix }}"
path: "${{ steps.find_output_dir.outputs.fullPath }}"
if-no-files-found: error
compression-level: ${{ inputs.compressionLevel }}
continue-on-error: true
- name: "Retry Upload Artifacts (if failed)."
if: ${{ steps.upload_artifacts.outcome == 'failure' }}
uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4 uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4
with: with:
name: "${{ steps.get_product_name.outputs.nameShort }}_${{ inputs.artifactSuffix }}" name: "${{ steps.get_product_name.outputs.nameShort }}_${{ inputs.artifactSuffix }}"

View File

@@ -68,6 +68,7 @@ outputs:
runs: runs:
using: "composite" using: "composite"
steps: steps:
# Note: I may want to add the platform input to the cache key in the future if I want to cache per-platform. Though this will use more space.
- name: "Get full cache key and build command." - name: "Get full cache key and build command."
id: command id: command
run: | run: |