Setup Script

ngss-dev-setup.sh sets up a Mac for VA Mobile / NGSS development in one guided run, and checks it again whenever you like. It automates the Development Environment Setup, Token and Environment Configuration, Maven Configuration and Skaffold and Kustomize Setup pages, the workstation part of the New Hire Checklist, and most of the Token Verification Checklist. It can also repair what it finds wrong, clone your team’s repositories and show where each one stands, set up IntelliJ IDEA, keep your tools up to date, free up disk space, and remove your saved credentials when you hand back a laptop.

Before You Start

  • A Mac with an administrator account. Homebrew asks for your password once.

  • The onboarding credentials: Bitbucket, VA Nexus and Sandbox DTR usernames and tokens, plus Jira and Jenkins tokens if you have them. The onboarding team may give you these as a credentials file.

  • 30 to 60 minutes on a new Mac. Most of that is downloading, and the questions all come at the start. An Intel Mac can take much longer, because Homebrew no longer has prebuilt packages for Intel Macs and builds the command-line tools from source. The script warns you and asks before it does.

Run It

curl -fsSL https://onboarding.apothesource.io/apothesource-onboarding/_attachments/dev-setup/ngss-dev-setup.sh -o ngss-dev-setup.sh
bash ngss-dev-setup.sh

Before any questions, it checks the Mac:

  • Rosetta. If Terminal is set to Open using Rosetta, Homebrew won’t install Apple Silicon tools and SDKMAN would pick Intel JDKs, so the script starts itself again as Apple Silicon code (arch -arm64), and tells you how to turn Rosetta off for Terminal. If it can’t, for example when you piped it into bash, it stops instead.

  • An Intel Mac. Homebrew stopped making prebuilt packages for Intel Macs in September 2026, so some tools may build from source, which can take hours; the script asks before it does that. Homebrew stops supporting Intel Macs in September 2027; see Intel Macs for what that means and how to install the tools yourself.

  • An Intel Homebrew. Migration Assistant can copy an old Intel Mac’s Homebrew into /usr/local. On Apple Silicon the script uses, and if needed installs, Homebrew in /opt/homebrew instead, and sets it up last in your login profile so new terminals find it first. If a line that runs later, say in ~/.zshrc, puts /usr/local/bin back in front, doctor points it out.

  • Disk space. The setup needs about 30 GB. With less, it warns and asks; under 10 GB the default answer is to stop.

  • The VA network. If coderepo.mobilehealth.va.gov isn’t reachable, it tells you to connect to the VPN. Without it, cloning, the token checks and the build fail, so run the script again once you’re connected.

  • A setup started by hand. If you already did part of the setup by following the docs, the script lists what it found and stops before changing anything, because working around a half-finished setup makes its output hard to follow. Signs of this include credential or Skaffold settings in your shell profile outside the script’s block, a ~/.m2/settings.xml for VA Nexus, the Jira or Jenkins CLI, or the git hooks. Finish the setup by hand with Development Environment Setup and the pages it links to, then run doctor to check it, and doctor --fix to repair what it finds (see Check Your Setup). Repositories you cloned yourself don’t count, and neither does anything from an earlier run of the script. To run the guided setup anyway, add --force.

Then it asks a few questions (container runtime, optional extras, your git name and email, where to clone repositories, and your credentials), shows its plan, and works through it. It tries each credential against its server as you enter it. When you run it again, it asks for the credentials you still don’t have, and for the others only if you want to re-enter them. If both OrbStack and Docker Desktop are installed, it keeps the one docker uses now, and points docker at the runtime it sets up, so images you build land in the Kubernetes cluster that runs them.

It remembers the folder you choose for repositories (in ~/.config/ngss/settings), so doctor, clone, repos, tidy and upgrade look there too. Pass --workspace to use another folder for one run. At the end it runs doctor, lists what’s left to do by hand, such as licenses and IntelliJ settings, and, if the setup took more than a few minutes, posts a macOS notification.

You can run it again at any time. Finished steps are skipped, so after a failure, fix the cause and re-run it to continue. Logs for the longer steps are in ~/.config/ngss/logs.

Add --dry-run to see exactly what it would change without changing anything, and --help for all of the options.

What It Sets Up

Area What it does

Tools

Xcode Command Line Tools, Homebrew, Skaffold, Kustomize, jq, wget and TruffleHog (for the secret scans in the git hooks). OrbStack (or Docker Desktop) with Kubernetes turned on. Optionally IntelliJ IDEA, Postman, Headlamp, PlantUML, the Jira CLI, iTerm2, Sublime Text, Windows App and Citrix Workspace.

Java

SDKMAN with Java 21.0.10 (the default) and 17.0.13, Maven 3.9.15 and Gradle. On Apple Silicon, if SDKMAN came over from an Intel Mac (it still fetches Intel JDKs), it switches SDKMAN to Apple Silicon builds and reinstalls its JDKs. It turns on sdkman_auto_env, so the JDK follows each repo’s .sdkmanrc. If an interrupted install left an incomplete ~/.sdkman, it moves that aside as ~/.sdkman.ngss-backup-<time> and installs SDKMAN fresh.

Shell

A marked block in ~/.zshrc (and in ~/.bash_profile if bash is your shell) with SKAFFOLD_PUSH, SKAFFOLD_RPC_PORT, DTR_URL and the jcli helper. On Apple Silicon Macs, Homebrew’s setup line goes at the end of ~/.zprofile (or ~/.bash_profile). If your ~/.zshenv sets ZDOTDIR, the zsh files are the ones in that folder. If bash is your shell and the setup creates ~/.bash_profile, it makes it load your ~/.profile (or ~/.bash_login) first, since bash would otherwise stop reading that file.

Credentials

~/.config/ngss/credentials.env (private, chmod 600), git’s saved Coderepo login in your keychain, and docker login for dtr.mapsandbox.net. If old credential lines are in your shell profile, it offers to disable them.

Maven

~/.m2/settings.xml from the Maven Configuration template.

Git

Your name and email, core.trustctime=false, and optionally the global git hooks, linked for every common hook event so each repository’s own hooks keep running. A hook of yours that was in the way moves into the matching .d folder, where the dispatcher runs it.

CLIs

jira init for project CKM and the Greenfield board, and the Jenkins CLI JAR.

IntelliJ IDEA

The PlantUML Integration, Google Cloud Code, Bitbucket Integration Pro and GitHub Copilot plugins, the VA code style and inspections, and the Copilot sign-in host. See IntelliJ IDEA.

macOS

Optionally turns off smart quotes and smart dashes, which break code pasted into a terminal, and shows hidden files in Finder.

Repositories

Clones location-service, starter-project, jwtgen, mobile-framework and ngss-maven-tiles, installs the JDKs they pin, and builds location-service with mvn clean verify -Pwith-skaffold. With --system, it also clones your team’s repositories (see Clone Your Team’s Repositories).

Files it rewrites are backed up first as <file>.ngss-backup-<time>. Tokens never appear on a command line or in the script’s output.

Check Your Setup

bash ngss-dev-setup.sh doctor
bash ngss-dev-setup.sh doctor --fix    # then offer to repair what it found

doctor changes nothing. It checks your tools, free disk space, the VA network, what a new terminal sees, your Maven settings, and OrbStack and Kubernetes, and it tries each credential against its server. It also flags Rosetta, an Intel Homebrew or an Intel SDKMAN on Apple Silicon, leftover copies of the onboarding credentials, a damaged ngss-dev-setup block in your shell profile, older copies of the git hooks that could let secrets or conflict markers through, and hook events that aren’t linked to the dispatcher, so Git skips each repository’s own hooks for them, such as Git LFS’s post-checkout. Its output never includes secrets, so you can paste it into Slack when you ask for help.

With --fix, it then offers a fix for each problem it can repair, asking before each one, and checks again at the end. It runs only those fixes, not the whole setup. For example, it can install a missing tool or JDK, restore the shell settings, ~/.m2/settings.xml or docker login, move credential lines from your shell profile into credentials.env, start OrbStack (or install it if you have no container runtime), or ask you for credentials that are missing or were rejected. Problems it can’t fix say what to do. If the fixes uncover more, such as git settings it couldn’t check before the Command Line Tools were in, it tells you to run it again.

Fixes that would change how you work, such as switching your kubectl context or replacing an old git hooks dispatcher (which then blocks pushes to main), default to no, and --yes skips them, along with the ones that need you to type credentials. Add --dry-run to see the fixes without making them.

Clone Your Team’s Repositories

bash ngss-dev-setup.sh clone                 # list the Backstage systems
bash ngss-dev-setup.sh clone --system ckm    # clone every repository in CKM

It looks up every component in the system in Backstage, clones the repositories you don’t have yet into your workspace (~/Projects, or the folder you chose at install; --workspace changes it for one run), and installs the JDK and Maven versions their .sdkmanrc files pin. Repositories you already have are left alone. Add --dry-run to see the list first, or pass --system to install to do this as part of the setup.

See Where Your Repositories Stand

bash ngss-dev-setup.sh repos           # one line per repository
bash ngss-dev-setup.sh repos --pull    # and fast-forward the ones you haven't changed

repos fetches every git repository in your workspace (~/Projects, the folder you chose at install, or --workspace), up to two folders down, and shows each one’s branch, uncommitted changes, and how far it is behind or ahead of its upstream branch. With --pull, it fast-forwards each repository that is only behind and has no changes (untracked files don’t count). It leaves the rest alone: repositories with changes or unpushed commits, a detached HEAD, or no upstream branch. Fetching never stops to ask for a password. If the first fetch takes too long, as it does off the VPN, the rest show where they stood at their last fetch. With --no-fetch (or --dry-run), it fetches nothing and answers at once from what each repository knew at its last fetch.

bash ngss-dev-setup.sh repos --clean-branches --dry-run    # see which branches would go
bash ngss-dev-setup.sh repos --clean-branches

Bitbucket can delete a branch when its pull request merges, but your local copy stays. repos counts those branches, and --clean-branches lists them and asks once before deleting them. It deletes a branch only when its remote branch is gone and all its commits are in the repository’s main branch. It keeps a branch that has commits of its own, which is what a squash merge leaves behind (it prints the git branch -D command for that), and the branch you have checked out.

IntelliJ IDEA

bash ngss-dev-setup.sh intellij

Quit IntelliJ first, because it overwrites its settings when it quits. The command:

  • Installs the PlantUML Integration, Google Cloud Code, Bitbucket Integration Pro and GitHub Copilot plugins.

  • Imports the VA_Mobile_CodeStyle code style and the VA_Mobile_Inspections inspections from mobile-framework (see IntelliJ Checkstyle & Inspection Profiles), makes the code style the default, and makes the inspections the default for projects you open from now on.

  • Sets GitHub Copilot to sign in through va.ghe.com (see GitHub Copilot Access and IDE Configuration (VA GHEC-US)).

If you upgraded IntelliJ and haven’t opened the new version yet, the command waits: writing its settings first would stop IntelliJ from offering to import your old ones. Open it once, let it import them, quit it, and run the command again.

install does the same when you choose IntelliJ. You still need a license, your token in the Bitbucket plugin, and the Copilot sign-in. When mobile-framework updates its IDE files, run the command again to pick up the new versions.

Switching to Your Own Tokens

Once you have your PIV and your own accounts, create your tokens and run:

bash ngss-dev-setup.sh credentials

It replaces the onboarding values in credentials.env, git’s saved login, docker login and the Jira CLI, and then checks them. Update the token saved in IntelliJ’s git settings and in the Bitbucket plugin yourself.

Keep the Script Current

bash ngss-dev-setup.sh update

doctor tells you when a newer version is published. update downloads it, checks that it’s a complete copy of the script, and replaces yours, keeping the old one as ngss-dev-setup.sh.previous. It won’t overwrite a copy that a git repository tracks, such as this site’s; use git there. If your copy is a symlink, it updates the file the symlink points to.

Upgrade Your Tools

bash ngss-dev-setup.sh upgrade --dry-run    # see what's out of date
bash ngss-dev-setup.sh upgrade

update replaces the script; upgrade upgrades the tools it installed. It runs brew update, lists the Homebrew tools from the setup that have newer versions (Skaffold, Kustomize, jq, wget, PlantUML and Graphviz, the Jira CLI, TruffleHog, kubectl, Headlamp and Windows App), and asks once before upgrading them. If the copy of a tool on your PATH isn’t Homebrew’s, such as a Kustomize you installed by hand from Skaffold and Kustomize Setup, it tells you, because upgrading Homebrew’s copy won’t change the one you run. It also installs any JDK or Maven version a repository has newly pinned, updates SDKMAN itself, and refreshes the Jenkins CLI JAR from the Sandbox Jenkins. Your other outdated Homebrew packages are listed, and it asks before upgrading those too; with --yes, it leaves them alone. doctor mentions it when the setup’s tools have upgrades.

To keep your builds working, it leaves some things alone:

  • Java and Maven. Each repository’s .sdkmanrc pins them, so upgrade never moves them to a newer version, and it doesn’t upgrade Homebrew’s JDK or Maven packages either.

  • New major versions of Skaffold, Kustomize and TruffleHog. Builds, deployments and the git hooks use them, so upgrade tells you when one is out. Check with the team, then upgrade it with the brew upgrade command it prints.

  • Apps that are running. Homebrew closes an app to upgrade it, which would end a Windows App session, for example. Quit the app and run upgrade again.

  • Apps that update themselves: OrbStack, Docker Desktop, IntelliJ IDEA (which also updates its plugins), Postman, iTerm2, Sublime Text and Citrix Workspace. Use their own update prompts. Upgrading OrbStack or Docker Desktop through Homebrew would stop your containers.

Homebrew no longer has prebuilt packages for Intel Macs, so on an Intel Mac, upgrading a command-line tool means building it from source. That can take hours, because it may build LLVM first, and it can fail. upgrade warns you and asks first (with --yes, it skips them). Apps still upgrade normally.

Free Up Disk Space

bash ngss-dev-setup.sh tidy --dry-run    # how much could be freed
bash ngss-dev-setup.sh tidy              # choose what to remove

tidy adds up what it could remove and asks about each kind separately. The answer defaults to yes only for what comes back by itself (marked yes below); the rest defaults to no, and tidy --yes leaves it alone.

  • Caches and logs left behind by IntelliJ IDEA versions that aren’t installed and haven’t run for 30 days (yes; IntelliJ rebuilds them).

  • Settings of those IntelliJ versions, moved to the Trash rather than deleted. It keeps the settings that a newly upgraded IntelliJ will import the first time it starts, including Community Edition settings that the unified IntelliJ IDEA can import. An IntelliJ installed somewhere tidy doesn’t look, such as a custom Toolbox folder, still counts as in use while it keeps writing logs.

  • Docker images and build cache that nothing has used for 30 days, and unused anonymous volumes (yes; images are pulled or rebuilt when next needed). The size it shows is an upper bound, and leaves the volumes out, because Docker counts named volumes there too, which it doesn’t remove. With a Docker CLI older than 23, it leaves volumes alone, because its volume prune removes named volumes too.

  • JDK, Maven and Gradle versions that no repository’s .sdkmanrc pins, no IntelliJ project uses as its SDK, and JAVA_HOME or your shell profile doesn’t name. It reads the repositories in your workspace, following symlinks; pass --workspace if some are somewhere else. It skips this when it finds no repositories, rather than guess. SDKMAN may no longer offer an old version once it’s gone.

  • Maven SNAPSHOT folders in ~/.m2/repository that no build has used for 30 days (nothing in them has changed), except ones built only on this Mac (mvn install without a deploy), which Maven couldn’t download again. Others download again on the VPN, unless Nexus has removed them.

  • Homebrew’s downloads and old versions (brew cleanup -s, yes).

Remove Your Credentials

Before you hand back a laptop, or when tokens may have leaked, run:

bash ngss-dev-setup.sh forget --dry-run    # see what's saved
bash ngss-dev-setup.sh forget

forget lists what it found and asks once before removing it (the answer defaults to no; --yes removes it all without asking): credentials.env, git’s saved Coderepo login, the docker login for dtr.mapsandbox.net, the Jira CLI settings, credential lines in your shell profile, and the script’s backups of your profile that still hold credentials.

It doesn’t change:

  • Your shell history. It warns you if the history sets token variables, so you can delete those lines yourself.

  • ~/.m2/settings.xml, and the backups of it the script makes when it replaces that file. It lists the ones with passwords written into them, so you can replace each password with an ${env.VAR} reference (see Maven Configuration) or delete the backup yourself.

  • IntelliJ. Remove the token in IntelliJ’s git settings and in the Bitbucket plugin.

  • The tokens themselves. Removing local copies doesn’t stop a token from working, so forget ends by saying where to revoke each one: Bitbucket, VA Nexus, Sandbox DTR, Jira and Jenkins each have a page for it (see Creating Token Values). For the onboarding team’s tokens, it tells you to let them know instead.

Credentials File (for the Onboarding Team)

Instead of typing credentials, a new hire can pass a file with --credentials-file. Put one KEY=VALUE on each line; any subset works, and # comments are fine:

VA_BITBT_USER=<Bitbucket username>
VA_BITBT_PWD=<Bitbucket HTTP access token>
VA_NEXUS_USER=<VA Nexus user token name>
VA_NEXUS_PWD=<VA Nexus user token passcode>
DTR_USER=<Sandbox DTR user token name>
DTR_PWD=<Sandbox DTR user token passcode>
JIRA_LOGIN=<Jira username>
JIRA_API_TOKEN=<Jira personal access token>
JENKINS_API_USER=<Sandbox Jenkins username>
JENKINS_API_TOKEN=<Jenkins API token>
bash ngss-dev-setup.sh --credentials-file ~/Downloads/onboarding.env

The script reads the file as data and never runs it. Once the values are saved, it offers to delete the file. Share the file through the new hire’s personal Google Drive folder rather than Slack or email.

Maintaining the Script

The script is in this site’s repository at antora/apothesource-onboarding/modules/ROOT/attachments/dev-setup/ngss-dev-setup.sh.

  • The versions and endpoints are at the top of the script. When the team moves to a new JDK or Maven version, change them there and on Development Environment Setup, and everyone re-runs the script to catch up.

  • It contains copies of the settings.xml from Maven Configuration and the hook scripts from Modular Git Hooks (Pick-and-Choose). After you change either page or the script, run scripts/check-dev-setup-sync.sh. It prints a diff and fails if they no longer match.

  • The IntelliJ plugin IDs are in IDEA_PLUGINS at the top of the script. The script imports the code style and inspections from mobile-framework, and falls back to this site’s VA_Mobile_CodeStyle.xml and VA_Mobile_Inspections.xml attachments when it can’t reach mobile-framework, so copy those over when mobile-framework’s docs/appendices/ide files change.

  • upgrade leaves the apps in SELF_UPDATING_CASKS to their own updaters, and holds back new major versions of the tools in HOLD_MAJOR. Both lists are at the top of the script.

  • Bump VERSION in the script whenever you change it. doctor tells people when a newer version is published, and update installs it.

  • Before you open a pull request that changes the script, run scripts/test-dev-setup/run.sh on a Mac. It runs the script against a throwaway home folder with stand-in commands, so nothing on your Mac changes and nothing goes over the network. Apps and Homebrew tools come only from that sandbox too, so the results don’t depend on what your Mac has installed, and the prompts are answered on a pseudo-terminal, the way a person types them. It runs several scenarios at once and takes a few minutes; scripts/test-dev-setup/run.sh h runs a single scenario (the scenarios are listed in the script), and JOBS=1 runs them one at a time. Ctrl-C stops them all, and a scenario still running after ten minutes is stopped and counted as failed (SCENARIO_LIMIT changes that). The top of run.sh also lists settings for trying other setups, such as bash as the login shell, GNU tools first on PATH, or another locale.