Skip to content

Instantly share code, notes, and snippets.

@AniTexs
Last active January 9, 2026 20:45
Show Gist options
  • Select an option

  • Save AniTexs/27c02e850c35df96364c03a8c0000550 to your computer and use it in GitHub Desktop.

Select an option

Save AniTexs/27c02e850c35df96364c03a8c0000550 to your computer and use it in GitHub Desktop.
full laravel enviroment setup for codex
#!/usr/bin/env bash
set -euo pipefail
printf "#################################"
printf "Codex Setup for Laravel"
printf ""
printf "Version 0.1.1"
printf "Author: Nicolai Jacobsen"
printf "#################################"
###############################################################################
# One-file setup orchestrator for Codex-style environments
# Steps: php, node, db(mysql|pgsql), meili, redis, minio
#
# Examples:
# ./setup.sh --all # defaults to mysql
# ./setup.sh --all --db=pgsql # full stack but pgsql instead
# ./setup.sh --php --node --db=pgsql
# ./setup.sh --redis --minio
###############################################################################
# -----------------------------
# Defaults (override via env)
# -----------------------------
ENV_FILE_PATH="${ENV_FILE_PATH:-.env}"
WRITE_ENV_FILE="${WRITE_ENV_FILE:-1}"
# DB selection default: mysql (unless --db=pgsql)
DB_ENGINE="${DB_ENGINE:-mysql}" # mysql|pgsql
# MySQL (Laravel)
MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}"
MYSQL_PORT="${MYSQL_PORT:-3306}"
MYSQL_DATABASE="${MYSQL_DATABASE:-mydb}"
MYSQL_USERNAME="${MYSQL_USERNAME:-myuser}"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-}" # prefer secret; generated if empty
MYSQL_SOCKET="${MYSQL_SOCKET:-/tmp/mysql.sock}"
MYSQL_DATADIR="${MYSQL_DATADIR:-/tmp/mysql-data}"
# PostgreSQL (Laravel)
PG_HOST="${PG_HOST:-127.0.0.1}"
PG_PORT="${PG_PORT:-5432}"
PG_DATABASE="${PG_DATABASE:-laravel}"
PG_USERNAME="${PG_USERNAME:-laravel}"
PG_PASSWORD="${PG_PASSWORD:-}" # prefer secret; generated if empty
# Node/Yarn
NODE_VERSION="${NODE_VERSION:-20}"
YARN_VERSION="${YARN_VERSION:-1.22.22}"
# Meilisearch
MEILI_VERSION="${MEILI_VERSION:-latest}"
MEILI_MASTER_KEY="${MEILI_MASTER_KEY:-masterKey}"
MEILISEARCH_HOST_DEFAULT="${MEILISEARCH_HOST_DEFAULT:-http://127.0.0.1:7700}"
# Redis
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
REDIS_PORT="${REDIS_PORT:-6379}"
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
# MinIO
MINIO_HOST="${MINIO_HOST:-127.0.0.1}"
MINIO_PORT="${MINIO_PORT:-9000}"
MINIO_CONSOLE_PORT="${MINIO_CONSOLE_PORT:-9001}"
MINIO_DATA_DIR="${MINIO_DATA_DIR:-/tmp/minio-data}"
MINIO_ROOT_USER="${MINIO_ROOT_USER:-}" # secret recommended
MINIO_ROOT_PASSWORD="${MINIO_ROOT_PASSWORD:-}" # secret recommended
MINIO_BUCKET="${MINIO_BUCKET:-laravel-local}"
AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-eu-west-1}"
AWS_USE_PATH_STYLE_ENDPOINT="${AWS_USE_PATH_STYLE_ENDPOINT:-true}"
# -----------------------------
# Helpers
# -----------------------------
log() { printf "\n\033[1m%s\033[0m\n" "$*"; }
need_cmd() { command -v "$1" >/dev/null 2>&1; }
as_root() {
if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then
"$@"
else
sudo "$@"
fi
}
gen_password() {
if need_cmd openssl; then
openssl rand -base64 32 | tr -d '\n' | tr '/+' 'Aa'
else
date +%s%N | sha256sum | awk '{print $1}'
fi
}
write_env_kv() {
local file="$1" key="$2" value="$3"
[[ "$WRITE_ENV_FILE" == "1" ]] || return 0
[[ -f "$file" ]] || touch "$file"
if grep -qE "^${key}=" "$file"; then
local tmp; tmp="$(mktemp)"
awk -v k="$key" -v v="$value" 'BEGIN{FS=OFS="="} $1==k{$0=k"="v} {print}' "$file" > "$tmp"
mv "$tmp" "$file"
else
echo "${key}=${value}" >> "$file"
fi
}
wait_for_http() {
local url="$1"
local name="${2:-service}"
printf "Waiting for %s" "$name"
for _ in {30..0}; do
if curl -sf "$url" >/dev/null 2>&1; then
echo -e "\r✅ $name ready "
return 0
fi
printf "."
sleep 1
done
echo
return 1
}
has_step() {
local want="$1"
[[ ",$STEPS," == *",$want,"* ]]
}
# -----------------------------
# 1) PHP & Composer
# -----------------------------
step_php() {
log "1) PHP & Composer"
as_root apt-get update -y
as_root apt-get install -y software-properties-common
as_root add-apt-repository -y ppa:ondrej/php
as_root apt-get update -y
as_root apt-get install -y \
php8.4 php8.4-cli \
php8.4-mbstring php8.4-xml php8.4-intl \
php8.4-gd php8.4-zip php8.4-curl php8.4-mysql php8.4-pgsql \
curl gnupg lsb-release ca-certificates
as_root update-alternatives --install /usr/bin/php php /usr/bin/php8.4 84
as_root update-alternatives --set php /usr/bin/php8.4
local EXPECTED_CHECKSUM ACTUAL_CHECKSUM
EXPECTED_CHECKSUM="$(curl -fsSL https://composer.github.io/installer.sig)"
curl -fsSL https://getcomposer.org/installer -o composer-setup.php
ACTUAL_CHECKSUM="$(sha384sum composer-setup.php | cut -d ' ' -f 1)"
if [[ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]]; then
echo "Composer installer corrupt" >&2
rm -f composer-setup.php
exit 1
fi
php composer-setup.php --install-dir=/usr/local/bin --filename=composer --quiet
rm -f composer-setup.php
if [[ -f composer.json ]]; then
composer install --ignore-platform-req=ext-bcmath
fi
php -v
composer -V
}
# -----------------------------
# 2) Node.js & Yarn
# -----------------------------
step_node() {
log "2) Node.js & Yarn"
curl -fsSL "https://deb.nodesource.com/setup_${NODE_VERSION}.x" | bash -
as_root apt-get install -y nodejs
if ! need_cmd corepack; then
as_root npm install -g corepack
fi
corepack enable
corepack prepare "yarn@${YARN_VERSION}" --activate
node -v
yarn -v
}
# -----------------------------
# 3a) MySQL (ephemeral /tmp) + Laravel env
# -----------------------------
step_mysql() {
log "3) MySQL (local /tmp datadir) + Laravel env"
if [[ -z "$MYSQL_PASSWORD" ]]; then
MYSQL_PASSWORD="$(gen_password)"
fi
as_root apt-get update -y
as_root apt-get install -y mysql-client mysql-common mysql-server-core-8.0
mkdir -p "$MYSQL_DATADIR"
mkdir -p /var/lib/mysql-files || true
if [[ ! -d "$MYSQL_DATADIR/mysql" ]]; then
mysqld --initialize-insecure --datadir="$MYSQL_DATADIR"
fi
# Start mysqld in background
nohup mysqld --user=root --datadir="$MYSQL_DATADIR" --socket="$MYSQL_SOCKET" \
> /tmp/mysql.log 2>&1 &
printf "Waiting for MySQL"
for _ in {30..0}; do
if mysqladmin --socket="$MYSQL_SOCKET" ping --silent 2>/dev/null; then
echo -e "\r✅ MySQL ready "
break
fi
printf "."
sleep 1
done
mysqladmin --socket="$MYSQL_SOCKET" ping --silent >/dev/null || { tail -n 120 /tmp/mysql.log; exit 1; }
mysql -u root --socket="$MYSQL_SOCKET" <<SQL
CREATE DATABASE IF NOT EXISTS \`${MYSQL_DATABASE}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${MYSQL_USERNAME}'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}';
GRANT ALL PRIVILEGES ON \`${MYSQL_DATABASE}\`.* TO '${MYSQL_USERNAME}'@'%';
FLUSH PRIVILEGES;
SQL
# Write Laravel DB env (ONLY for chosen DB)
write_env_kv "$ENV_FILE_PATH" "DB_CONNECTION" "mysql"
write_env_kv "$ENV_FILE_PATH" "DB_HOST" "$MYSQL_HOST"
write_env_kv "$ENV_FILE_PATH" "DB_PORT" "$MYSQL_PORT"
write_env_kv "$ENV_FILE_PATH" "DB_DATABASE" "$MYSQL_DATABASE"
write_env_kv "$ENV_FILE_PATH" "DB_USERNAME" "$MYSQL_USERNAME"
write_env_kv "$ENV_FILE_PATH" "DB_PASSWORD" "\"$MYSQL_PASSWORD\""
echo "MySQL configured: ${MYSQL_USERNAME}/${MYSQL_DATABASE} (password hidden)"
}
# -----------------------------
# 3b) PostgreSQL + Laravel env
# -----------------------------
step_pgsql() {
log "3) PostgreSQL (system service) + Laravel env"
if [[ -z "$PG_PASSWORD" ]]; then
PG_PASSWORD="$(gen_password)"
fi
if ! need_cmd apt-get; then
echo "Postgres installer in this script assumes Debian/Ubuntu (apt-get)." >&2
exit 1
fi
as_root apt-get update -y
as_root apt-get install -y postgresql postgresql-contrib
if need_cmd systemctl; then
as_root systemctl enable --now postgresql >/dev/null 2>&1 || true
as_root systemctl start postgresql >/dev/null 2>&1 || true
else
as_root service postgresql start >/dev/null 2>&1 || true
fi
# Create role/db idempotently
as_root -u postgres psql -v ON_ERROR_STOP=1 -qAtc \
"DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='${PG_USERNAME}') THEN
CREATE ROLE \"${PG_USERNAME}\" WITH LOGIN PASSWORD '${PG_PASSWORD}' CREATEDB;
ELSE
ALTER ROLE \"${PG_USERNAME}\" WITH PASSWORD '${PG_PASSWORD}';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname='${PG_DATABASE}') THEN
CREATE DATABASE \"${PG_DATABASE}\" OWNER \"${PG_USERNAME}\";
ELSE
ALTER DATABASE \"${PG_DATABASE}\" OWNER TO \"${PG_USERNAME}\";
END IF;
END
\$\$;"
write_env_kv "$ENV_FILE_PATH" "DB_CONNECTION" "pgsql"
write_env_kv "$ENV_FILE_PATH" "DB_HOST" "$PG_HOST"
write_env_kv "$ENV_FILE_PATH" "DB_PORT" "$PG_PORT"
write_env_kv "$ENV_FILE_PATH" "DB_DATABASE" "$PG_DATABASE"
write_env_kv "$ENV_FILE_PATH" "DB_USERNAME" "$PG_USERNAME"
write_env_kv "$ENV_FILE_PATH" "DB_PASSWORD" "\"$PG_PASSWORD\""
echo "PostgreSQL configured: ${PG_USERNAME}/${PG_DATABASE} (password hidden)"
}
# -----------------------------
# 3) DB router (chosen by --db=)
# -----------------------------
step_db() {
case "$DB_ENGINE" in
mysql) step_mysql ;;
pgsql|postgres|postgresql) DB_ENGINE="pgsql"; step_pgsql ;;
*)
echo "Invalid --db value: '$DB_ENGINE' (use mysql or pgsql)" >&2
exit 1
;;
esac
}
# -----------------------------
# 4) Meilisearch
# -----------------------------
step_meili() {
log "4) Meilisearch"
local MEILISEARCH_BIN="/usr/local/bin/meilisearch"
local MEILISEARCH_HOST="${MEILISEARCH_HOST_DEFAULT}"
if [[ ! -x "$MEILISEARCH_BIN" ]]; then
echo "Installing Meilisearch (${MEILI_VERSION}) ..."
if [[ "$MEILI_VERSION" == "latest" ]]; then
curl -L https://install.meilisearch.com | sh
else
local ASSET_URL="https://github.com/meilisearch/meilisearch/releases/download/${MEILI_VERSION}/meilisearch-linux-amd64"
if ! curl -fSL "$ASSET_URL" -o meilisearch; then
echo "⚠️ Version $MEILI_VERSION not found – falling back to latest" >&2
curl -L https://install.meilisearch.com | sh
fi
fi
chmod +x ./meilisearch 2>/dev/null || true
[[ -f ./meilisearch ]] && as_root mv ./meilisearch "$MEILISEARCH_BIN"
fi
nohup "$MEILISEARCH_BIN" --master-key "$MEILI_MASTER_KEY" --http-addr "127.0.0.1:7700" \
> /tmp/meilisearch.log 2>&1 &
wait_for_http "$MEILISEARCH_HOST/health" "Meilisearch" || { tail -n 120 /tmp/meilisearch.log; exit 1; }
write_env_kv "$ENV_FILE_PATH" "MEILISEARCH_HOST" "$MEILISEARCH_HOST"
write_env_kv "$ENV_FILE_PATH" "MEILISEARCH_KEY" "\"$MEILI_MASTER_KEY\""
}
# -----------------------------
# 5) Redis
# -----------------------------
step_redis() {
log "5) Redis"
as_root apt-get update -y
as_root apt-get install -y redis-server redis-tools
# Try normal service start paths first (won't work in many containers)
if need_cmd systemctl; then
as_root systemctl enable --now redis-server >/dev/null 2>&1 || true
as_root systemctl start redis-server >/dev/null 2>&1 || true
else
as_root service redis-server start >/dev/null 2>&1 || true
fi
# If still not responding, start a local instance explicitly (Codex/CI-safe)
if ! redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ping >/dev/null 2>&1; then
echo "Redis not responding; starting redis-server manually..."
# Avoid clashes if package install left a dead pidfile
rm -f /var/run/redis/redis-server.pid 2>/dev/null || true
# Run Redis in the background, bind to host/port, store logs
nohup redis-server \
--bind "$REDIS_HOST" \
--port "$REDIS_PORT" \
--protected-mode yes \
--daemonize no \
> /tmp/redis.log 2>&1 &
# Wait for readiness
printf "Waiting for Redis"
for _ in {30..0}; do
if redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ping >/dev/null 2>&1; then
echo -e "\r✅ Redis ready "
break
fi
printf "."
sleep 1
done
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ping >/dev/null 2>&1 || {
echo
echo "Redis still not responding. Last logs:"
tail -n 120 /tmp/redis.log || true
exit 1
}
fi
# Write Laravel env
write_env_kv "$ENV_FILE_PATH" "REDIS_HOST" "$REDIS_HOST"
write_env_kv "$ENV_FILE_PATH" "REDIS_PORT" "$REDIS_PORT"
if [[ -n "$REDIS_PASSWORD" ]]; then
write_env_kv "$ENV_FILE_PATH" "REDIS_PASSWORD" "\"$REDIS_PASSWORD\""
fi
}
# -----------------------------
# 6) MinIO
# -----------------------------
step_minio() {
log "6) MinIO (S3-compatible) + Laravel env"
if [[ -z "$MINIO_ROOT_USER" ]]; then
MINIO_ROOT_USER="minioadmin"
fi
if [[ -z "$MINIO_ROOT_PASSWORD" ]]; then
MINIO_ROOT_PASSWORD="$(gen_password)"
fi
local MINIO_BIN="/usr/local/bin/minio"
local MC_BIN="/usr/local/bin/mc"
local ENDPOINT="http://${MINIO_HOST}:${MINIO_PORT}"
if [[ ! -x "$MINIO_BIN" ]]; then
curl -fSL "https://dl.min.io/server/minio/release/linux-amd64/minio" -o /tmp/minio
chmod +x /tmp/minio
as_root mv /tmp/minio "$MINIO_BIN"
fi
if [[ ! -x "$MC_BIN" ]]; then
curl -fSL "https://dl.min.io/client/mc/release/linux-amd64/mc" -o /tmp/mc
chmod +x /tmp/mc
as_root mv /tmp/mc "$MC_BIN"
fi
mkdir -p "$MINIO_DATA_DIR"
MINIO_ROOT_USER="$MINIO_ROOT_USER" MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" \
nohup "$MINIO_BIN" server "$MINIO_DATA_DIR" \
--address "${MINIO_HOST}:${MINIO_PORT}" \
--console-address "${MINIO_HOST}:${MINIO_CONSOLE_PORT}" \
> /tmp/minio.log 2>&1 &
wait_for_http "${ENDPOINT}/minio/health/live" "MinIO" || { tail -n 120 /tmp/minio.log; exit 1; }
"$MC_BIN" alias set localminio "$ENDPOINT" "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null 2>&1 || true
"$MC_BIN" mb --ignore-existing "localminio/${MINIO_BUCKET}" >/dev/null 2>&1 || true
write_env_kv "$ENV_FILE_PATH" "FILESYSTEM_DISK" "s3"
write_env_kv "$ENV_FILE_PATH" "AWS_ACCESS_KEY_ID" "\"$MINIO_ROOT_USER\""
write_env_kv "$ENV_FILE_PATH" "AWS_SECRET_ACCESS_KEY" "\"$MINIO_ROOT_PASSWORD\""
write_env_kv "$ENV_FILE_PATH" "AWS_DEFAULT_REGION" "$AWS_DEFAULT_REGION"
write_env_kv "$ENV_FILE_PATH" "AWS_BUCKET" "$MINIO_BUCKET"
write_env_kv "$ENV_FILE_PATH" "AWS_ENDPOINT" "$ENDPOINT"
write_env_kv "$ENV_FILE_PATH" "AWS_USE_PATH_STYLE_ENDPOINT" "$AWS_USE_PATH_STYLE_ENDPOINT"
echo "MinIO endpoint: $ENDPOINT (console: http://${MINIO_HOST}:${MINIO_CONSOLE_PORT})"
echo "MinIO bucket: ${MINIO_BUCKET} (credentials hidden)"
}
# -----------------------------
# CLI parsing
# -----------------------------
usage() {
cat <<EOF
Usage:
./setup.sh --all [--db=mysql|pgsql]
./setup.sh --php --node --db=mysql|pgsql [--meili] [--redis] [--minio]
SETUP_STEPS=php,node,db,redis,minio DB_ENGINE=pgsql ./setup.sh
Flags:
--all
--php --node --db=mysql|pgsql --meili --redis --minio
--env-file=PATH
--no-write-env
EOF
}
STEPS="${SETUP_STEPS:-}"
if [[ $# -eq 0 && -z "$STEPS" ]]; then
usage
exit 1
fi
while [[ $# -gt 0 ]]; do
case "$1" in
--all)
# default db engine is mysql unless overridden by --db=...
STEPS="php,node,db,meili,redis,minio"
shift
;;
--php) STEPS="${STEPS:+$STEPS,}php"; shift ;;
--node) STEPS="${STEPS:+$STEPS,}node"; shift ;;
--meili) STEPS="${STEPS:+$STEPS,}meili"; shift ;;
--redis) STEPS="${STEPS:+$STEPS,}redis"; shift ;;
--minio) STEPS="${STEPS:+$STEPS,}minio"; shift ;;
--db=*)
DB_ENGINE="${1#--db=}"
# ensure db step is included if user specified --db=
STEPS="${STEPS:+$STEPS,}db"
shift
;;
--env-file=*)
ENV_FILE_PATH="${1#--env-file=}"
shift
;;
--no-write-env)
WRITE_ENV_FILE="0"
shift
;;
-h|--help) usage; exit 0 ;;
*)
echo "Unknown flag: $1" >&2
usage
exit 1
;;
esac
done
# Deduplicate steps (preserve order)
dedupe_steps() {
local in="$1"
local out=""
IFS=',' read -ra parts <<< "$in"
for s in "${parts[@]}"; do
[[ -z "$s" ]] && continue
if [[ ",$out," != *",$s,"* ]]; then
out="${out:+$out,}$s"
fi
done
echo "$out"
}
STEPS="$(dedupe_steps "$STEPS")"
# -----------------------------
# Run selected steps
# -----------------------------
has_step "php" && step_php
has_step "node" && step_node
has_step "db" && step_db
has_step "meili" && step_meili
has_step "redis" && step_redis
has_step "minio" && step_minio
log "Setup complete. Ran steps: $STEPS (db=${DB_ENGINE})"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment