Files
zeekay d696a015aa fix(brand): one name for the script language, and a guard that keeps it
The rename runs on the way in and nothing checked it afterwards, so paths and
content drifted apart in three places, and each one broke something quietly.

The script language had three names at once.
Files on disk carried `.iql`/`.iqle`, the source matchers said `.script`, and
the bytecode matchers said `.hoge`, so the test harness globbed zero files and
the STL compiler regenerated an empty table.
It is `script` now, in one place: `.script` source, `.scriptc` bytecode,
`bin/script` to run one and `bin/scriptc` to compile one.

190 TypeScript specifiers named files that had been renamed away, which is the
whole CDP consumer graph -- the event worker, not the web boot.
`--check` only ever read Python imports; it reads both module systems now, and
repoints both.

`-hog-` became `-mascot-` in paths while content said `script`, so the code
review product's skill directories stopped matching the prefix that discovers
them and it ran with no perspectives at all.

PATH_REGEX is CONTENT_REGEX -- the same list, not a copy -- so a rule can no
longer land in one and miss the other, and the four disagreeing `hog` path
rules collapse into the one word rule content already had.

`HOGLI_*` becomes `INSIGHTSCLI_*` at every reader and writer, with no alias: an
env var read under two names is two names.
The seven casings of `hogql` and five of `hogvm` collapse into one rule per
prefix casing, which leaves the suffix alone because `QL` is SQL's and `VM` is
a virtual machine's.

`--check` now refuses every retired spelling, in content and in paths, and
draws the line the rules draw: `insights_hogfunction` is a Postgres table so it
is left alone, and a migration filename is its identity in `django_migrations`
so it is frozen rather than failed.
Third-party names we cannot rename are reported as pending, never exempted.
2026-08-08 18:38:11 -07:00

308 lines
15 KiB
Bash
Executable File

#!/bin/bash
set -e
export REPOSITORY_ROOT=$(realpath "$(dirname "$0")/..")
export LOCK_FILE="$REPOSITORY_ROOT/bin/start.lock"
# All worktrees share one infra stack under the "insights" compose project. Pin it
# here so it never silently falls back to the worktree folder name when direnv
# hasn't exported it (.envrc) — that mismatch makes wait-for-docker query the wrong
# project and report db/redis/datastore/kafka as "missing" while docker ps shows
# them running. An explicit override still wins.
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-insights}"
# 1Password: auto-resolve op:// references in .env.local.
# Must run before lock acquisition: `exec op run ...` keeps the bash fds open
# in the op-run process, so if we held fd 9's flock here the re-exec'd child
# bin/start would fail to acquire it.
if [ -f "$REPOSITORY_ROOT/.env.local" ] && grep -q "op://" "$REPOSITORY_ROOT/.env.local" 2>/dev/null; then
if [ -z "$_INSIGHTS_OP_RESOLVED" ]; then
if command -v op &>/dev/null; then
echo "🔐 Resolving secrets from .env.local via 1Password"
export _INSIGHTS_OP_RESOLVED=1
exec op run --env-file="$REPOSITORY_ROOT/.env.local" -- "$0" "$@"
else
echo "⚠️ .env.local contains 1Password refs (op://) but 'op' CLI is not installed."
echo " These refs will be skipped — services that depend on them will fail with their own"
echo " 'missing key' errors. To resolve them automatically, install: brew install 1password-cli"
echo " Or replace op:// refs with literal values in .env.local."
fi
fi
fi
cleanup_lock() {
rm -f "$LOCK_FILE"
}
# Acquire exclusive lock using flock (atomic, no race condition)
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
echo "⚠️ Another instance of bin/start is already running (lock file: $LOCK_FILE)"
echo " To skip this check, delete the lock file: rm $LOCK_FILE"
exit 1
fi
trap cleanup_lock EXIT INT TERM
# Environment variables are defined in:
# .env.services - service connection defaults (shared with containers
# and hobby via docker-compose env_file)
# .env.development - dev-mode runtime knobs (DEBUG, OTEL, etc.)
# .env.local - your overrides + 1Password secrets (gitignored,
# copy .env.local.example to get started)
#
# Source env files respecting precedence:
# shell env > .env.local > .env.development > .env.services
# Only set vars that aren't already in the environment.
# op:// refs are always skipped here — they only get resolved by `op run` in
# the re-exec block above. Sourcing them as literals would set env vars to
# garbage strings that break downstream services with cryptic errors.
source_env_defaults() {
if [[ ! -f "$1" ]]; then
echo "⚠️ Expected env file missing: $1 — skipping. Did your checkout get truncated?" >&2
return 0
fi
# Use explicit if/fi (not `[[ ... ]] && cmd`) so the loop body always
# returns 0. With `&&`, the last iteration's `[[ -z … ]]` returning false
# (variable already set) makes the function return non-zero, and `set -e`
# silently kills the entire script — no error, no output, just exit. This
# was the root cause of "insightscli start" failing in Cursor and other terminals
# where some of the trailing env vars happened to already be in the shell.
while IFS='=' read -r name value; do
[[ -z "$name" || "$name" == \#* ]] && continue
# Substring match so quoted ("op://...") and space-padded values are
# also caught — mirrors what op run itself accepts.
[[ "$value" == *op://* ]] && continue
if [[ -z "${!name:-}" ]]; then
export "$name=$value"
fi
done < "$1"
return 0
}
# .env.local: already in env if processed by op run, otherwise source directly
if [[ -f "$REPOSITORY_ROOT/.env.local" ]] && [[ -z "$_INSIGHTS_OP_RESOLVED" ]]; then
source_env_defaults "$REPOSITORY_ROOT/.env.local"
elif [[ ! -f "$REPOSITORY_ROOT/.env.local" ]] && [[ -f "$REPOSITORY_ROOT/.env.local.example" ]]; then
echo "💡 No .env.local found — running with committed defaults only."
echo " For personal overrides or secrets: cp .env.local.example .env.local"
fi
source_env_defaults "$REPOSITORY_ROOT/.env.development"
source_env_defaults "$REPOSITORY_ROOT/.env.services"
# Limit Rust parallel compilation jobs to reduce CPU contention when multiple
# services start simultaneously via mprocs (they share a build lock anyway)
export CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-1}"
# Use sccache for Rust compilation caching (if available via flox or homebrew)
if [[ -z "${RUSTC_WRAPPER:-}" ]] && command -v sccache &>/dev/null; then
export RUSTC_WRAPPER="sccache"
fi
# Use lld linker on macOS for faster Rust linking (if available via flox or homebrew)
if [[ "$(uname -s)" == "Darwin" ]] && [[ -z "${CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS:-}" ]] && command -v lld &>/dev/null; then
export CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS="-C link-arg=-fuse-ld=lld"
fi
# Computed vars (need REPOSITORY_ROOT)
export DAGSTER_HOME=$REPOSITORY_ROOT/.dagster_home
# Tracing is disabled by default - requires 'tracing' intent for Jaeger/otel-collector
# Note: otel-collector uses significant CPU/memory resources
# Use --tracing flag to enable if you have the intent configured
if [[ "$*" == *"--tracing"* ]]; then
export OTEL_SDK_DISABLED="false"
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="1"
echo "👉 Tracing enabled, see http://localhost:16686 for Jaeger UI"
else
export OTEL_SDK_DISABLED="true"
fi
# Vars below need bash variable expansion in their defaults (${PGHOST:-db},
# $PERSONS_DATABASE_URL, etc.) — must stay in this script because
# .env.development is sourced via a loader that doesn't re-expand $-refs.
# Persons DB — Node.js and Rust use different env var names for the same connection
export PERSONS_DATABASE_URL=${PERSONS_DATABASE_URL:-postgres://insights:insights@${PGHOST:-db}:${PGPORT:-5432}/insights_persons}
export PERSONS_READONLY_DATABASE_URL=${PERSONS_READONLY_DATABASE_URL:-postgres://insights:insights@${PGHOST:-db}:${PGPORT:-5432}/insights_persons}
# nosemgrep: env-default-belongs-in-env-development -- bash variable ref in default
export PERSONS_WRITE_DATABASE_URL=${PERSONS_WRITE_DATABASE_URL:-$PERSONS_DATABASE_URL}
# nosemgrep: env-default-belongs-in-env-development -- bash variable ref in default
export PERSONS_READ_DATABASE_URL=${PERSONS_READ_DATABASE_URL:-$PERSONS_READONLY_DATABASE_URL}
# nosemgrep: env-default-belongs-in-env-development -- bash variable ref in default
export PERSONS_DB_WRITER_URL=${PERSONS_DB_WRITER_URL:-$PERSONS_DATABASE_URL}
# nosemgrep: env-default-belongs-in-env-development -- bash variable ref in default
export PERSONS_DB_READER_URL=${PERSONS_DB_READER_URL:-$PERSONS_READONLY_DATABASE_URL}
export BEHAVIORAL_COHORTS_DATABASE_URL=${BEHAVIORAL_COHORTS_DATABASE_URL:-postgres://insights:insights@${PGHOST:-db}:${PGPORT:-5432}/behavioral_cohorts}
export FLAGS_READ_STORE_DATABASE_URL=${FLAGS_READ_STORE_DATABASE_URL:-postgres://insights:insights@${PGHOST:-db}:${PGPORT:-5432}/flags_read_store}
export CYCLOTRON_DATABASE_URL=${CYCLOTRON_DATABASE_URL:-postgres://insights:insights@${PGHOST:-db}:${PGPORT:-5432}/cyclotron}
export CYCLOTRON_NODE_DATABASE_URL=${CYCLOTRON_NODE_DATABASE_URL:-postgres://insights:insights@${PGHOST:-db}:${PGPORT:-5432}/cyclotron_node}
# Clean up orphaned dev processes left behind by a previous unclean shutdown.
# These "zombies" hold ports and memory and cause most local dev issues. Only
# orphans are killed — processes under an active process manager are left alone.
# Set INSIGHTSCLI_SKIP_ZOMBIE_CHECK=1 to skip. Never let it fail the script (set -e).
if [[ -z "${INSIGHTSCLI_SKIP_ZOMBIE_CHECK:-}" ]] && command -v insightscli &>/dev/null; then
insightscli doctor:zombies --yes || true
fi
# Pre-flight: detect who already holds the host ports the dev stack needs. A stale
# stack under a *different* compose project binds the same ports, so one of our
# containers fails to bind — and that aborts the entire `docker compose up`,
# sometimes leaving a container detached from the compose network. In a TTY, offer
# to tear foreign stacks down; otherwise stay advisory. Delegated to insightscli (tested
# there, see doctor_ports in insightscli_commands/doctor.py).
#
# Also runs the one-time migration for the July 2026 named-volume switch: the named
# volume is absent until the first `up` after the switch, which would otherwise
# reset a datastore container's data that already exists under this project.
# Auto-salvages the old anonymous volumes when the mapping is unambiguous, otherwise
# prints the same manual-salvage warning as before. Must run before `docker compose
# up` below (see doctor_migrate_volumes in insightscli_commands/doctor.py).
#
# Both skipped if insightscli isn't on PATH, same as doctor:zombies above. Without insightscli
# there's no auto-migration either, so warn plainly instead of silently losing data.
if command -v insightscli &>/dev/null; then
insightscli doctor:ports || true
insightscli doctor:migrate-volumes || true
else
named_volume_reset_notice() {
command -v docker &>/dev/null || return 0
docker volume inspect "${COMPOSE_PROJECT_NAME}_datastore-data" &>/dev/null && return 0
if ! docker ps -a --filter 'label=com.docker.compose.service=datastore' \
--format '{{.Label "com.docker.compose.project"}}' 2>/dev/null \
| grep -qx "$COMPOSE_PROJECT_NAME"; then
# No datastore container left — either a fresh clone (nothing to lose) or
# `docker compose down` (removes containers but keeps volumes). Postgres has
# no profile gate, so its volume surviving is a reliable "not a fresh clone"
# signal, same as _has_prior_install in insightscli_commands/doctor.py.
docker volume inspect "${COMPOSE_PROJECT_NAME}_postgres-15-data" &>/dev/null || return 0
fi
echo "⚠️ Switching Datastore/ZooKeeper to named docker volumes. This first start"
echo " recreates them empty, so local Datastore data resets once (happens once"
echo " per machine). Install insightscli to auto-migrate your existing data instead. See"
echo " 'Local Datastore suddenly empty' in"
echo " docs/published/handbook/engineering/developing-locally.md to salvage old data."
}
named_volume_reset_notice || true
fi
# Geo files
./bin/download-mmdb
PROCESS_MANAGER="phrocs"
# PROCESS_MANAGER is the bare name (kept for telemetry); PROCESS_MANAGER_BIN is
# the executable to run, resolved per-manager below. For phrocs that's the
# in-repo source build when present, so it wins over anything on PATH.
# Detect detached mode early (`-d` is the shorthand, `--detach` the long
# form). Incompatible with --mprocs since mprocs has no detached mode; only
# phrocs supports it.
DETACHED=0
if [[ " $* " == *" --detach "* ]] || [[ " $* " == *" -d "* ]]; then
DETACHED=1
if [[ " $* " == *" --mprocs "* ]]; then
echo "Error: -d / --detach requires phrocs (not compatible with --mprocs)"
exit 1
fi
fi
# Only use mprocs when --mprocs is explicitly passed
if [[ " $* " == *" --mprocs "* ]]; then
# Ensure mprocs is available
if ! command -v mprocs &>/dev/null; then
if command -v brew &>/dev/null; then
echo "🔁 Installing mprocs via Homebrew..."
brew install mprocs
else
echo "👉 To use --mprocs, install mprocs: https://github.com/pvolok/mprocs#installation"
exit 1
fi
fi
PROCESS_MANAGER="mprocs"
PROCESS_MANAGER_BIN="mprocs"
fi
if [[ "$PROCESS_MANAGER" == "phrocs" ]]; then
# Resolve phrocs deterministically. The in-repo source build (produced by
# flox activation via `make -C tools/phrocs build`) is canonical: it always
# matches this checkout, so prefer it over anything on PATH. This stops a
# stray brew/curl install from silently shadowing the version your branch
# built. Non-flox setups have no dist binary and fall back to PATH.
DIST_PHROCS="$REPOSITORY_ROOT/tools/phrocs/dist/phrocs"
if [[ -x "$DIST_PHROCS" ]]; then
PROCESS_MANAGER_BIN="$DIST_PHROCS"
elif [[ -n "${FLOX_ENV_PROJECT:-}" ]]; then
# Inside flox the source build is the only accepted binary — do NOT fall
# back to PATH, or a stray brew/curl phrocs would shadow the checkout
# build in exactly the failure mode this resolution is meant to prevent.
echo "👉 phrocs isn't built yet. Re-activate your flox environment to build it"
echo " (leave and re-enter the repo directory, or run: flox activate),"
echo " or build it directly: insightscli phrocs:build"
exit 1
elif PATH_PHROCS="$(command -v phrocs)"; then
PROCESS_MANAGER_BIN="$PATH_PHROCS"
else
echo "👉 To run bin/start, install phrocs:"
echo " brew tap insights/tap && brew install phrocs"
echo " or, without Homebrew:"
echo " curl -fsSL https://raw.githubusercontent.com/Insights/insights/master/tools/phrocs/install.sh | bash"
exit 1
fi
fi
# Use custom config, if provided (e.g. bin/start --custom bin/mprocs-custom.yaml)
# Ensure to provide config path after --custom flag
if [[ "$*" == *"--custom"* ]]; then
# Extract the path after --custom
config_path=""
found_custom=false
for i in "$@"; do
if [[ $found_custom == true ]]; then
config_path="$i"
break
fi
if [[ "$i" == "--custom" ]]; then
found_custom=true
fi
done
if [[ -z "$config_path" ]]; then
echo "Error: --custom requires a config path"
exit 1
fi
if [[ "$DETACHED" == "1" ]]; then
exec 9<&-
"$PROCESS_MANAGER_BIN" --detach --config "$config_path"
else
"$PROCESS_MANAGER_BIN" --config "$config_path"
fi
else
GENERATED_CONFIG="$REPOSITORY_ROOT/.insights/.generated/mprocs.yaml"
# Generate/regenerate config using insightscli (defaults to product_analytics if no config exists)
# INSIGHTSCLI_PROCESS_MANAGER is picked up by dev:generate for telemetry
if command -v insightscli &>/dev/null; then
INSIGHTS_TELEMETRY_OPT_OUT=1 INSIGHTSCLI_PROCESS_MANAGER="$PROCESS_MANAGER" insightscli dev:generate 2>/dev/null || true
fi
if [[ -f "$GENERATED_CONFIG" ]]; then
CONFIG_TO_USE="$GENERATED_CONFIG"
else
# Fallback if insightscli not available
CONFIG_TO_USE="$REPOSITORY_ROOT/bin/mprocs.yaml"
fi
if [[ "$DETACHED" == "1" ]]; then
# Close fd 9 before spawning the detached child. Go's exec.Command
# inherits every open fd by default, and fd 9 holds this script's
# flock on $LOCK_FILE. If the child inherits it, the lock stays held
# for the child's whole lifetime even after bin/start exits —
# blocking the next bin/start invocation.
exec 9<&-
"$PROCESS_MANAGER_BIN" --detach --config "$CONFIG_TO_USE"
else
"$PROCESS_MANAGER_BIN" --config "$CONFIG_TO_USE"
fi
fi