Setup CI Host
Prepares a new physical host to run miles GitHub Actions CI runners by making
/data/miles_ci canonical on the host.
The miles CI workflow (.github/workflows/_run-ci.yml) bind-mounts
/data/miles_ci and its subdirectories (models, datasets, hf_cache)
from the host into every CI job container, using literal paths everywhere.
For that to work, every CI host must provide /data/miles_ci either as a
real directory on its biggest disk OR as a symlink to wherever the real big
disk is. This script automates that.
Run this once on each new CI host, before the runner-setup steps in
tests/ci/README.md or the externally-managed
flow in manage-gh-runners.
Usage
cd /root/miles/.claude/skills/setup-ci-host
./setup-host.sh
Re-runs are safe: the script is idempotent and a no-op on hosts that already
have /data/miles_ci correctly set up.
What it does
- Probes mounted filesystems with
dfand picks the mount point with the most total bytes, excludingtmpfs/devtmpfs/overlay/squashfsand system mounts (/,/boot,/dev,/proc,/run,/sys). - If the biggest mount is
/data(the canonical case, e.g. scitix-72): ensures/data/miles_ciexists as a real directory. Done. - If the biggest mount is something else (e.g.
/mnt/nvme0n1on novita): ensures<big_disk>/miles_ciexists as a real directory, then atomically replaces/data/miles_ciwith a symlink pointing to<big_disk>/miles_ci(creates/datafirst if missing). - Creates
/data/miles_ci/models,/data/miles_ci/datasets,/data/miles_ci/hf_cacheif they do not exist. - Prints a summary: chosen mount, whether
/data/miles_ciis a real dir or a symlink, free space on the backing disk.
Verification
After running, the operator can confirm the result with:
ls -la /data/miles_ci
df -h /data/miles_ci
readlink /data/miles_ci || echo "(real directory, not a symlink)"
The first command should list the four subdirs (models, datasets,
hf_cache, plus any existing runner_<hostname> work dirs). df -h should
report on the host's big disk regardless of whether /data/miles_ci is real
or a symlink. readlink succeeds only when /data/miles_ci is a symlink.
When to re-run
- After adding a larger disk to the host (the script will re-detect; if
/data/miles_cialready exists pointing at a smaller disk, the script prompts before moving anything — see Safety). - After noticing CI mount errors that suggest
/data/miles_ciis wrong.
Safety
The script never silently overwrites a /data/miles_ci that already exists
and points somewhere unexpected. When run from a terminal, it prompts the
operator interactively; in non-interactive contexts (cron, CI, scripts) it
refuses and exits non-zero so a human always makes the destructive call.
Two prompts can appear:
- Existing real directory on the wrong disk —
/data/miles_cihas data but the biggest disk is elsewhere. Options:[m] migrate:rsyncthe data to the big disk, thenrm -rfthe original and symlink. Preserves data.[w] wipe:rm -rfthe original (data lost), then symlink. Requires typingyesto confirm.[a] abort(default): leave everything as-is, exit non-zero.
- Existing symlink pointing at the wrong target —
/data/miles_cialready symlinks somewhere, but not to the current biggest disk. The prompt asks[y/N]to re-point. Re-pointing does NOT migrate data from the old target; only the symlink changes.
The script requires root (or write access to /data). It will detect a
non-root invocation and suggest sudo ./setup-host.sh.
Inside the container
Once the host side is prepared, the runner container's compose mount
/data/miles_ci:/data/miles_ci works as an identity bind (per the docker-out-
of-docker requirement explained in docker-compose.yml). The inner job
container sees /data/miles_ci as a real bind from the host's canonical
path, whether or not the host side is itself a symlink — Docker resolves the
host symlink at bind time, so the container always sees the underlying
directory. This is the desired behavior.