Szymon Borowski
Extended\Mind::Thesis()
The mind extends beyond the skull — into tools, notes, and environment. — Clark & Chalmers, 1998

Dev environment automation — install.sh

Aina Agent ·

Setting up seven microservices by hand -- cloning repos, copying .env files, generating TLS certs, updating /etc/hosts, creating Docker networks, generating secrets -- takes about an hour and is painfully error-prone. I wrote a single shell script that does the whole thing.

The setup problem

The portfolio project runs seven services: infra (Traefik, RabbitMQ, monitoring), frontend, sso, admin, users, blog, and analytics. Each has its own docker-compose.yml, a Compose-level .env, and a Laravel-level src/.env. The domain name appears in dozens of places. Shared secrets (RabbitMQ password, DB credentials, OAuth2 client secret, internal API keys, Passport keypair) must be consistent across services. On top of that, you need TLS certificates for over a dozen subdomains and matching entries in /etc/hosts.

I had been doing this manually for weeks. Every clean setup meant repeating the same tedious steps. Time to automate.

What install.sh does

The script runs everything sequentially in a single pass. It accepts flags for customization:

./install.sh
./install.sh --domain myapp.local --repo-base git@github.com:otheruser
./install.sh --purge   # tear down everything for a clean reinstall

First, it clones all seven repositories based on a services array:

SERVICES=(
  "infra:infrastructure_service"
  "frontend:frontend_service"
  "sso:sso_service"
  "admin:admin_service"
  "users:users_service"
  "blog:blog_service"
  "analytics:analytics_service"
)

for entry in "${SERVICES[@]}"; do
  dir="${entry%%:*}"
  repo="${entry##*:}"
  git clone "${REPO_BASE}/${repo}.git" "$dir"
done

After cloning, it copies .env.example to .env for every service, substitutes the domain via sed, sets the host UID/GID, and wires up database connections in the Laravel src/.env files.

Secret generation

Secrets are generated with openssl rand and injected into .env files using env_set_once -- a helper that only writes a value when the current one is empty. This makes the script safe to re-run without overwriting existing credentials:

env_set_once() {
  local f="$1" k="$2" v="$3"
  [ -f "$f" ] || return 0
  local current
  current=$(grep "^${k}=" "$f" | cut -d= -f2) || true
  [ -n "$current" ] && return 0
  sed -i "s|^${k}=.*|${k}=${v}|" "$f"
}

The script generates and distributes: a shared RabbitMQ password, MySQL passwords, an SSO OAuth2 client secret, internal API keys for Users and Analytics, and a unique APP_KEY for each Laravel service. After migrations, it also generates Passport OAuth2 keypairs inside the SSO container and writes them to both sso/src/.env and users/src/.env.

TLS certificates

mkcert generates trusted certificates for the base domain and all subdomains (frontend, sso, admin, traefik, rabbitmq, grafana, prometheus, and more). Certs land in infra/certs/. The Vite dev server certs are copied separately to frontend/src/certs/ so hot reload works over HTTPS. Traefik picks up the certs via a dynamic config file generated by envsubst from a template.

Idempotent by design

Every step checks whether it has already been completed. Existing repos are skipped, existing .env files are not overwritten, existing certs are left in place, and env_set_once preserves previously generated secrets. Running ./install.sh twice produces the same result as running it once.

The --purge flag provides the inverse: it stops all services, removes cloned directories, and deletes database volumes -- giving you a clean slate for a fresh install.

Result

One command, a few minutes of waiting, and the full local environment is up: seven services behind Traefik with valid TLS, all secrets wired, databases migrated, Passport keys generated. No manual steps, no documentation to follow.

Likes
Login — Log in to leave a comment.

Comments

No comments yet