Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions configure-azure-sccache/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Configure Azure sccache
description: |
Select sccache Azure Blob credentials for this run. Non-secret settings
(SCCACHE_AZURE_RW_MODE, _BLOB_CONTAINER, _KEY_PREFIX) are exported for
subsequent steps via GITHUB_ENV; the connection string itself is only
emitted as a step output, NOT via GITHUB_ENV - environment exported
there lands in the process env of every later step, including
third-party actions, while an output reaches only the steps that
explicitly bind it. Bind it at the point of use:

- name: build
env:
SCCACHE_AZURE_CONNECTION_STRING: ${{ steps.<id>.outputs.connection-string }}

and consume it however the build needs: pass the variable name through
`docker run -e NAME` (values never land in generated scripts), or feed
docker/build-push-action via
`secret-envs: sccache_conn=SCCACHE_AZURE_CONNECTION_STRING` into a
`RUN --mount=type=secret` in the Dockerfile. The value stays masked in
logs regardless of transport (masking is value-based).

Only triggers named in trusted-events (maintainer-gated contexts) get
the read-write connection string; everything else - pull requests
today, any trigger added later (e.g. merge_group) - fails closed to the
read-only token, so code that has not been reviewed and merged can
never poison cache objects later consumed by release builds. When the
selected connection string is empty (e.g. fork PRs, which receive no
secrets), `configured` is "false" and sccache falls back to a local
disk cache.

inputs:
connection-string-rw:
description: 'Read-write Azure connection string (org secret)'
required: true
connection-string-ro:
description: 'Read-only Azure connection string (org secret)'
required: true
key-prefix:
description: >-
Cache namespace within the container, one per project (e.g.
"kernel", "xen"). Used for per-project lifecycle rules and metrics.
required: true
container:
description: 'Azure blob container name'
default: 'sccache'
trusted-events:
description: >-
Space-separated event names allowed to write to the shared cache.
Every event not listed gets read-only access.
default: 'push schedule workflow_dispatch'

outputs:
configured:
description: 'Whether Azure cache credentials were selected ("true"/"false")'
value: ${{ steps.select.outputs.configured }}
rw-mode:
description: 'Selected access mode (READ_WRITE/READ_ONLY)'
value: ${{ steps.select.outputs.rw-mode }}
connection-string:
description: >-
The selected connection string (empty when no credentials are
available). Step outputs within a job are not subject to the
secret-dropping applied to job outputs; bind this as step-level env
at the point of use only.
value: ${{ steps.select.outputs.connection-string }}

runs:
using: composite
steps:
- name: select sccache credentials
id: select
shell: bash
env:
SCCACHE_CONN_RW: ${{ inputs.connection-string-rw }}
SCCACHE_CONN_RO: ${{ inputs.connection-string-ro }}
EVENT_NAME: ${{ github.event_name }}
TRUSTED_EVENTS: ${{ inputs.trusted-events }}
run: |
case " ${TRUSTED_EVENTS} " in
*" ${EVENT_NAME} "*)
SCCACHE_CONN="${SCCACHE_CONN_RW}"
SCCACHE_MODE="READ_WRITE"
;;
*)
SCCACHE_CONN="${SCCACHE_CONN_RO}"
SCCACHE_MODE="READ_ONLY"
;;
esac
echo "rw-mode=${SCCACHE_MODE}" >> "${GITHUB_OUTPUT}"
echo "connection-string=${SCCACHE_CONN}" >> "${GITHUB_OUTPUT}"
if [ -n "${SCCACHE_CONN}" ]; then
{
echo "SCCACHE_AZURE_RW_MODE=${SCCACHE_MODE}"
echo "SCCACHE_AZURE_BLOB_CONTAINER=${{ inputs.container }}"
echo "SCCACHE_AZURE_KEY_PREFIX=${{ inputs.key-prefix }}"
} >> "${GITHUB_ENV}"
echo "configured=true" >> "${GITHUB_OUTPUT}"
echo "sccache: ${SCCACHE_MODE} for event ${EVENT_NAME}"
else
echo "configured=false" >> "${GITHUB_OUTPUT}"
echo "sccache: no credentials for event ${EVENT_NAME}; local cache only"
fi
93 changes: 93 additions & 0 deletions reclaim-disk-space/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Reclaim Disk Space
description: |
Delete preinstalled runner-image tooling the calling workflow does not
need, freeing tens of GB on the root disk.

Ordering matters when remove-toolcache is enabled: this must run BEFORE
any tool-installing action (cosign-installer, setup-python,
setup-cargo-make, ...), because those actions place binaries under
/opt/hostedtoolcache and re-create what they need only if they run
afterwards.

All rm paths are no-ops where a runner image does not ship them, so the
action is safe across runner classes and image generations.

inputs:
remove-android:
description: 'Remove the Android SDK (/usr/local/lib/android, ~6GB)'
default: 'true'
remove-language-toolchains:
description: >-
Remove Java, .NET, Swift, Haskell, and Julia toolchains (~9GB
combined). Rust is separate; see remove-rust.
default: 'true'
remove-browsers:
description: >-
Remove Chromium and the Edge/Chrome installs under /opt/microsoft and
/opt/google (~2GB)
default: 'true'
remove-cloud-clis:
description: 'Remove the Azure CLI, Google Cloud SDK, and PowerShell (~1GB)'
default: 'true'
remove-toolcache:
description: >-
Remove /opt/hostedtoolcache wholesale (~10GB: CodeQL plus every cached
Python/Node/Go version). Breaks setup-* actions and tool installers
that already ran; see the ordering note in the action description.
default: 'false'
remove-rust:
description: >-
Remove the preinstalled Rust toolchain (~/.rustup and ~/.cargo,
~2GB). Leave disabled for jobs that build Rust on the runner or use
setup-cargo-make (which caches into ~/.cargo/bin).
default: 'false'
prune-docker:
description: 'Prune preseeded docker images and build cache'
default: 'true'

outputs:
freed-kb:
description: 'Kilobytes freed on the root filesystem'
value: ${{ steps.reclaim.outputs.freed-kb }}

runs:
using: composite
steps:
- name: reclaim disk space
id: reclaim
shell: bash
run: |
avail_before="$(df --output=avail -k / | tail -1 | tr -d ' ')"

remove() {
sudo rm -rf "$@"
}
if [ "${{ inputs.remove-android }}" = "true" ]; then
remove /usr/local/lib/android
fi
if [ "${{ inputs.remove-language-toolchains }}" = "true" ]; then
remove /usr/lib/jvm /usr/share/dotnet /usr/share/swift \
/usr/local/.ghcup /usr/local/julia*
fi
if [ "${{ inputs.remove-browsers }}" = "true" ]; then
remove /usr/local/share/chromium /opt/microsoft /opt/google
fi
if [ "${{ inputs.remove-cloud-clis }}" = "true" ]; then
remove /opt/az /usr/lib/google-cloud-sdk /usr/local/share/powershell
fi
if [ "${{ inputs.remove-toolcache }}" = "true" ]; then
remove /opt/hostedtoolcache
fi
if [ "${{ inputs.remove-rust }}" = "true" ]; then
remove "${HOME}/.rustup" "${HOME}/.cargo"
fi
if [ "${{ inputs.prune-docker }}" = "true" ]; then
docker system prune -af >/dev/null 2>&1 || true
docker builder prune -af >/dev/null 2>&1 || true
fi

avail_after="$(df --output=avail -k / | tail -1 | tr -d ' ')"
freed=$((avail_after - avail_before))
echo "freed-kb=${freed}" >> "${GITHUB_OUTPUT}"
echo "freed ${freed}KB on /"
df -hT -x tmpfs -x devtmpfs -x squashfs -x efivarfs -x overlay
14 changes: 14 additions & 0 deletions report-disk-space/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Report Disk Space
description: |
Print all real filesystems (type, size, usage, mount point), filtering
out tmpfs/snap/overlay pseudo-mounts. Use at job start and after the
main build (with `if: always()`) so disk-exhaustion failures come with a
post-mortem and runner disk topology is visible in every log - runner
classes vary and their specs are not reliably documented.

runs:
using: composite
steps:
- name: report disk space
shell: bash
run: df -hT -x tmpfs -x devtmpfs -x squashfs -x efivarfs -x overlay