#!/bin/bash # Post-start setup for the Explore devcontainer. # # This runs on EVERY container start (wired as `postStartCommand` in # devcontainer.json), unlike post-create.sh which runs only once when the # container is first created. Its job is the lightweight work that has to # happen on every boot: bring PostgreSQL back up. The heavy one-time work # (installing dependencies, creating + migrating the database, seeding) stays # in post-create.sh. # # Why this is needed: the container is started with an entrypoint that bypasses # the image's own startup script, so nothing restarts postgres for you. After # you stop the container or reboot your machine, postgres stays down until this # script runs — previously you had to start it by hand every session. # # Safe to run repeatedly: if postgres is already accepting connections, the # start step is skipped and this is effectively a no-op. set -euo pipefail REPO_NAME=$(node -e "try{process.stdout.write(require('/workspace/toolkit.json').repo)}catch{}" 2>/dev/null || true) # Dead-end the deployed hostnames this estate's sources still name, so booting an app with a # non-local environment setting can't send a login form (or anything else) to a live host. Has to # happen on every start, not in the image: Docker remounts /etc/hosts per container, so a # Dockerfile write to it never survives. BLOCKED_HOSTS=$(node -e "try{process.stdout.write((require('/workspace/toolkit.json').blockedHosts||[]).join(' '))}catch{}" 2>/dev/null || true) if [ -n "$BLOCKED_HOSTS" ] && ! grep -q "raccoon-blocked-hosts" /etc/hosts 2>/dev/null; then printf '# raccoon-blocked-hosts\n127.0.0.1 %s\n::1 %s\n' "$BLOCKED_HOSTS" "$BLOCKED_HOSTS" \ | sudo tee -a /etc/hosts >/dev/null 2>&1 \ || echo "warning: could not pin blocked hosts in /etc/hosts" >&2 fi # Corpus viewer: when this toolkit ships a corpus search index, serve the viewer on # container port 3002 (published as EXPLORE_CORPUS_PORT on the host). Only # corpus-shipping toolkits package the viewer at all; where present, the script # self-guards (no index / no python3 / already running → quiet no-op) and must never # block container startup. [ -f /workspace/corpus-viewer/view-corpus.sh ] && bash /workspace/corpus-viewer/view-corpus.sh start --quiet || true # True when postgres is up and answering queries. pg_ready() { sudo -u postgres psql -c "SELECT 1" >/dev/null 2>&1; } # Block until postgres is ready, but never hang the container start forever: # pg_isready alone races on cluster startup, so we poll an actual query with a # bounded number of attempts (60s) and move on with a warning if it never comes # up rather than wedging `devcontainer up`. wait_for_pg() { local n=0 until pg_ready; do sleep 0.5 n=$((n + 1)) if [ "$n" -ge 120 ]; then echo "warning: postgres did not become ready within 60s" >&2 return 0 fi done } # Polyglot toolkit: REPO_NAME is empty (no single repo). Bring up Postgres + Redis # (members need them; per-member DB setup is deferred to run-app/setup_repo), then done. # Trial parity: limit DNS to the model endpoint and the toolkit's telemetry, so a session # captured here cannot depend on network the trial agent will not have. Opt-in # (RACCOON_DNS_JAIL=1) and best-effort. postCreate patches .bashrc, but postStart gets no # login shell, so .env is read directly. Called on BOTH paths: the polyglot branch returns # before the end of this script. apply_dns_jail() { [ -f /workspace/.devcontainer/dns-jail.sh ] || return 0 # Read the one line rather than sourcing: this runs on every boot AND every run-app, and # with the jail off it must not execute the worker's .env as a side effect. if [ "${RACCOON_DNS_JAIL:-0}" != "1" ] && ! grep -qE '^[[:space:]]*(export[[:space:]]+)?RACCOON_DNS_JAIL[[:space:]]*=[[:space:]]*"?1"?[[:space:]]*(#.*)?$' \ /workspace/.env 2>/dev/null; then return 0 fi ( set -a # shellcheck disable=SC1091 . /workspace/.env 2>/dev/null || true set +a bash /workspace/.devcontainer/dns-jail.sh ) || true } IS_POLYGLOT=$(node -e "try{process.stdout.write(require('/workspace/toolkit.json').polyglot?'1':'')}catch{}" 2>/dev/null || true) if [ -n "$IS_POLYGLOT" ]; then if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi sudo service redis-server start >/dev/null 2>&1 || sudo redis-server --daemonize yes >/dev/null 2>&1 || true wait_for_pg sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'secret_password';" >/dev/null 2>&1 || true # Many members' committed .env / database.yml default the DB username to 'root' # (dotenv-rails applies it at boot, overriding DEV_DB_USERNAME=postgres). The image's # start-services.sh creates a root superuser, but devcontainers override the ENTRYPOINT so # it never runs — create root here too, mirroring the harbor task env. sudo -u postgres psql -c "CREATE ROLE root SUPERUSER LOGIN PASSWORD 'secret_password';" >/dev/null 2>&1 || true # MongoDB, for the polyglot images that bake it (potion's flagship member stores everything # in Mongo). `command -v mongod` is the switch, so the Mongo-less polyglot images skip this # untouched. It has to happen here for the same reason postgres does — the devcontainer # overrides the image ENTRYPOINT, so the baked start-services.sh never runs — and it matters # more than a stopped postgres would: mongoose BUFFERS operations while disconnected instead # of erroring, so a member whose Mongo is down doesn't fail loudly, it serves requests that # hang forever and pages that never finish rendering. Readiness is a dependency-free TCP # probe (no mongosh needed; mongoose connects lazily once the port is open). if command -v mongod >/dev/null 2>&1; then mongo_up() { (exec 3<>/dev/tcp/127.0.0.1/27017) 2>/dev/null && { exec 3>&- 3<&-; return 0; }; return 1; } if ! mongo_up; then sudo mkdir -p /data/db 2>/dev/null || mkdir -p /data/db 2>/dev/null || true sudo chown -R "$(id -u)":"$(id -g)" /data/db 2>/dev/null || true mongod --dbpath /data/db --bind_ip 127.0.0.1 --fork --logpath /tmp/mongod.log >/dev/null 2>&1 \ || (sudo -b mongod --dbpath /data/db --bind_ip 127.0.0.1 --logpath /var/log/mongod.log >/dev/null 2>&1) || true for _ in $(seq 1 60); do mongo_up && break; sleep 0.5; done mongo_up || echo "warning: mongod did not come up within 30s" >&2 fi fi # OpenSearch, for the polyglot images that bake it — same reason as mongod (the devcontainer # overrides the ENTRYPOINT, so the image's start-services.sh never runs). Presence of the # binary is the switch, so images without it are untouched. Flags mirror the trial image's # start-services block exactly; it runs as its own user because OpenSearch refuses to boot # as root. Non-fatal: a member that doesn't use it shouldn't be blocked by a slow JVM. if [ -x /opt/opensearch/bin/opensearch ]; then os_up() { curl -s --max-time 2 localhost:9200 >/dev/null 2>&1; } if ! os_up; then sudo -u opensearch env OPENSEARCH_JAVA_OPTS='-Xms512m -Xmx512m' \ /opt/opensearch/bin/opensearch -Ediscovery.type=single-node \ -Eplugins.security.disabled=true >/tmp/opensearch.log 2>&1 & for _ in $(seq 1 90); do os_up && break; sleep 2; done os_up || echo "warning: opensearch did not come up within 180s (see /tmp/opensearch.log)" >&2 fi fi apply_dns_jail return 0 2>/dev/null || exit 0 fi case "$REPO_NAME" in ZenBill-006) # Start is non-fatal: if it fails outright, wait_for_pg is the single # gate — it warns and continues rather than aborting `devcontainer up` # and leaving the worker with no shell. if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi wait_for_pg sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'secret_password';" >/dev/null 2>&1 || true ;; zeta-heimdall) # Bookworm base → `service postgresql start` (same as ZenBill). Non-fatal # start; wait_for_pg is the single gate so a hiccup warns rather than wedging # `devcontainer up` and leaving the worker with no shell. if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi wait_for_pg sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'secret_password';" >/dev/null 2>&1 || true ;; zeta-platform) # Postgres + Redis (sidekiq). Start both; wait_for_pg is the single gate. if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi sudo service redis-server start >/dev/null 2>&1 || sudo redis-server --daemonize yes >/dev/null 2>&1 || true wait_for_pg sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'secret_password';" >/dev/null 2>&1 || true ;; Palolo-031) if ! pg_ready; then PG_VERSION=$(pg_config --version | grep -oP '\d+' | head -1) sudo pg_ctlcluster "${PG_VERSION}" main start || echo "warning: 'pg_ctlcluster ${PG_VERSION} main start' failed" >&2 fi wait_for_pg sudo -u postgres psql -c "CREATE USER test WITH SUPERUSER PASSWORD 'test';" >/dev/null 2>&1 || true sudo -u postgres psql -c "CREATE DATABASE palolo OWNER test;" >/dev/null 2>&1 || true ;; human-essentials) # Bookworm base → `service postgresql start` (same as ZenBill). Non-fatal # start; wait_for_pg is the single gate. Trust auth (set in the image), so # no role password to seed — the app connects as postgres with no password. if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi wait_for_pg ;; stocks-in-the-future) # Postgres + Redis (background jobs). Start both; wait_for_pg is the gate. if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi sudo service redis-server start >/dev/null 2>&1 || sudo redis-server --daemonize yes >/dev/null 2>&1 || true wait_for_pg ;; casa) # Postgres only. Non-fatal start; wait_for_pg is the gate. if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi wait_for_pg ;; awbw) # MySQL 8 (Percona). Start it (init the data dir first if empty), then ensure root # is passwordless over TCP (mysql_native_password) for Trilogy. Self-contained — # the pg_ready/wait_for_pg helpers above are Postgres-specific. if ! mysqladmin ping >/dev/null 2>&1; then sudo mkdir -p /var/run/mysqld && sudo chown -R mysql:mysql /var/run/mysqld /var/lib/mysql 2>/dev/null || true [ -d /var/lib/mysql/mysql ] || sudo mysqld --initialize-insecure --user=mysql --datadir=/var/lib/mysql 2>/dev/null || true sudo service mysql start >/dev/null 2>&1 || (sudo mysqld_safe --user=mysql >/dev/null 2>&1 &) || echo "warning: mysql start failed" >&2 fi for i in $(seq 1 120); do mysqladmin ping >/dev/null 2>&1 && break; sleep 0.5; done mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''; CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED WITH mysql_native_password BY ''; GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION; GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;" >/dev/null 2>&1 || true # Load MySQL tz tables (Ahoy charts use Groupdate/CONVERT_TZ). One-time. [ "$(mysql -u root -N -e 'SELECT COUNT(*) FROM mysql.time_zone_name' 2>/dev/null || echo 0)" -gt 0 ] \ || mysql_tzinfo_to_sql /usr/share/zoneinfo 2>/dev/null | mysql -u root mysql 2>/dev/null || true ;; flaredown) # Three datastores: Postgres (relational slice) + Redis (Sidekiq) + MongoDB (Mongoid, # the primary store). Start all three; wait_for_pg gates the Postgres readiness, and # we poll mongod separately. All starts are non-fatal so a hiccup warns rather than # wedging `devcontainer up`. Trust auth on Postgres (set in the image). if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi sudo service redis-server start >/dev/null 2>&1 || sudo redis-server --daemonize yes >/dev/null 2>&1 || true # MongoDB (server tarball → bin/mongod on PATH; no service unit). Launch mongod against # a data dir if nothing is already listening on 27017. Readiness is a dependency-free # TCP probe (no mongosh needed — Mongoid connects lazily once the port is open). mongo_up() { (exec 3<>/dev/tcp/127.0.0.1/27017) 2>/dev/null && { exec 3>&- 3<&-; return 0; }; return 1; } if ! mongo_up; then sudo mkdir -p /data/db 2>/dev/null || mkdir -p /data/db 2>/dev/null || true sudo chown -R "$(id -u)":"$(id -g)" /data/db 2>/dev/null || true mongod --dbpath /data/db --bind_ip 127.0.0.1 --fork --logpath /tmp/mongod.log >/dev/null 2>&1 \ || (sudo -b mongod --dbpath /data/db --bind_ip 127.0.0.1 --logpath /var/log/mongod.log >/dev/null 2>&1) || true fi wait_for_pg for _ in $(seq 1 60); do mongo_up && break; sleep 0.5; done ;; breezy-complete) # Postgres + Redis (Sidekiq). Start both; wait_for_pg is the gate. Trust # auth (set in the image) — PGPASSWORD is baked but inert, no role seeding. if ! pg_ready; then sudo service postgresql start || echo "warning: 'service postgresql start' failed" >&2 fi sudo service redis-server start >/dev/null 2>&1 || sudo redis-server --daemonize yes >/dev/null 2>&1 || true wait_for_pg ;; esac apply_dns_jail