How would you use an init script in CI to inject a repository mirror and credentials across all builds without committing secrets to any repo?
answer
- drop into GRADLE_USER_HOME/init.d on the agent
- or --init-script per CI invocation
- allprojects { repositories { maven {} } }
- credentials from System.getenv, never literals
- keeps repo secret-free + reproducible
basics
~20 sGenerate or ship an init script on the CI agent (e.g. in ~/.gradle/init.d/ or via --init-script) that adds the mirror under allprojects { repositories {} } and reads credentials from environment variables, never literals. The repo stays secret-free.
solid answer
~50 sPut the cross-cutting config in an init script delivered to the CI agent — typically dropped into `GRADLE_USER_HOME/init.d/` or passed with `--init-script` — so it applies to every build on that runner without any change to the project. Inside it, use `allprojects { repositories { maven { url = uri(mirror) } } }` to route resolution through the mirror, and read secrets from the environment (`System.getenv("NEXUS_USER")`) rather than hardcoding them, so nothing sensitive lands in a script that might be cached or logged. For publishing, the same script can register credentials on publish repositories. This keeps the project repo free of environment- and secret-specific config: a clean checkout builds the same way locally, while CI layers the mirror/credentials on top. Combine with a custom Gradle distribution's `init.d/` when you want the policy baked into the toolchain itself.
code
bash · 4 lines# CI step: provision the agent then build through the mirror
mkdir -p "$GRADLE_USER_HOME/init.d"
cp ci/10-ci-repos.init.gradle.kts "$GRADLE_USER_HOME/init.d/"
NEXUS_USER=$NEXUS_USER NEXUS_PASS=$NEXUS_PASS gradle buildgo deeper
Know that an init script can add a repository for all projects and that it lives outside the repo.
Show the allprojects repositories block and reading credentials from environment variables.
Design the delivery mechanism (init.d vs --init-script vs distribution), secret handling, and mirror-only enforcement.
Govern this org-wide via managed distributions/agent images, balancing enforceability, auditability, and developer reproducibility.
## The problem CI agents usually must resolve dependencies through an internal mirror (for speed, reliability, and supply-chain control) and sometimes need credentials to do so. You do **not** want that mirror URL or any secret committed to every project's `build.gradle`. An init script is the canonical answer: it applies machine-wide and lives outside the repo. ## Delivering the init script to the agent Three common mechanisms: 1. **`GRADLE_USER_HOME/init.d/`** — write a `*.init.gradle.kts` into the agent's Gradle user home during provisioning. Every build picks it up automatically. 2. **`--init-script <path>`** — pass it explicitly in the CI build command for a single invocation; good when you want it only on CI, not interactive shells. 3. **A custom Gradle distribution** — bundle the script in the distribution's `init.d/`, so the toolchain itself carries the policy. ## Injecting the mirror and reading secrets safely ```kotlin // init.d/10-ci-repos.init.gradle.kts val mirror = System.getenv("GRADLE_MIRROR_URL") ?: "https://nexus.corp.example/repository/maven-public" val nexusUser = System.getenv("NEXUS_USER") val nexusPass = System.getenv("NEXUS_PASS") allprojects { repositories { maven { url = uri(mirror) if (nexusUser != null && nexusPass != null) { credentials { username = nexusUser password = nexusPass } } } } } ``` Key rules: - **Never hardcode secrets** in the script. Read them from environment variables (or a secrets file the CI system mounts). Scripts can be cached, scanned, or printed in `--info`/`--debug` logs. - Prefer the **resolved** values to come from the CI secret store, exposed as env vars only for the build's lifetime. - If you must *also* block public repos, you can clear and replace `repositories` so builds can't silently fall back to Maven Central. ## Why not put this in the build script? Because then every repo carries CI-specific URLs and secret-handling, and a developer checking out the project gets CI-only config (or worse, leaked secrets). The init script keeps the project reproducible from a clean checkout while CI layers environment concerns on top. ## Governance angle At org scale, bake the init script into a managed Gradle distribution or provision it via the agent image, so policy (mirror-only resolution, mandatory build scans) is enforced uniformly and can't be bypassed per project.
- Why read credentials with System.getenv rather than embedding them in the init script?Scripts can be cached, version-controlled by accident, or echoed in --info/--debug logs. Reading from env keeps secrets out of the script body and scoped to the build's lifetime.
- How can you force builds to resolve ONLY through the mirror, not Maven Central?In the allprojects repositories block, clear the existing repositories and add only the mirror — or use settingsEvaluated to enforce dependencyResolutionManagement with a single repository — so there's no public fallback.
- What's the org-scale alternative to copying the script onto each agent?Bake the init script into a custom Gradle distribution's init.d/, so the toolchain carries the policy and it can't be bypassed per project.
saying these in an interview costs you the question
- Hardcoding usernames/passwords or mirror tokens directly in the init script.
- Putting CI mirror config in the project's build.gradle, leaking environment concerns into the repo.
- Assuming an init script is encrypted or hidden — it is plain text and can be logged.