scripts/dev-seed.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
#
# Seed the dev stack's database the way the installers do (install/lib/db.sh), then set the admin password through
# the real first-login flow (the console's Set Password call). Re-runnable: on a seeded database it only re-applies
# the idempotent post-seed repairs.
#
# Usage: scripts/dev-seed.sh (after: docker compose --env-file docker/dev.env up -d --build)
# DEV_ADMIN_PASSWORD admin password to set on a fresh database (default: admin, scripts/agent-v1-e2e.sh's default)
set -euo pipefail
cd "$(CDPATH='' cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=install/lib/db.sh
. install/lib/db.sh
DC=(docker compose --env-file docker/dev.env)
# shellcheck disable=SC2034 # PSQL is used by install/lib/db.sh
PSQL=("${DC[@]}" exec -T postgres psql -U mdmesh -d mdmesh)
PW="${DEV_ADMIN_PASSWORD:-admin}"
md5u() { printf '%s' "$1" | md5sum | awk '{print toupper($1)}'; }
# POST a JSON body to $API$1 and print the response's "data" as JSON. The body goes on stdin, so password hashes and
# tokens never appear in the process list. Any failure is one line on stderr naming the call.
api_post() {
local resp
resp=$(printf '%s' "$2" | curl -sS -m 30 -H 'Content-Type: application/json' --data-binary @- "$API$1" 2>&1) \
|| { echo "POST $1 failed: $(printf '%s' "$resp" | head -n 1)" >&2; return 1; }
printf '%s' "$resp" | python3 -c '
import sys, json
raw = sys.stdin.read()
try:
r = json.loads(raw)
except ValueError:
sys.exit("POST %s failed: the response is not JSON: %.100r" % (sys.argv[1], raw))
if not isinstance(r, dict) or r.get("status") != "OK":
sys.exit("POST %s failed: %.200s" % (sys.argv[1], json.dumps(r)))
json.dump(r.get("data"), sys.stdout)' "$1"
}
# Set the admin password with a reset token: the call the console's Set Password page makes.
reset_pw() {
api_post /rest/public/passwordReset/reset \
"{\"passwordResetToken\":\"$1\",\"newPassword\":\"$(md5u "$PW")\"}" >/dev/null
}
# Act only on a dev stack. docker/dev.env names the project mdmesh-dev, but an exported COMPOSE_PROJECT_NAME (how a
# second, isolated dev stack runs) or an edited dev.env can point these commands at another project, such as a
# production install's (setup.sh names it mdmesh). Compose labels each container with the files it was created from,
# so require the dev overlay on every container this script uses; a production stack's never carry it.
[ -f docker/dev.env ] || { echo "docker/dev.env is missing; this script only seeds the dev stack" >&2; exit 1; }
cfg=$("${DC[@]}" config --format json) || exit 1
project=$(printf '%s' "$cfg" | python3 -c 'import sys,json; print(json.load(sys.stdin)["name"])')
L=com.docker.compose.project
for svc in postgres server; do
ids=$("${DC[@]}" ps -q --status running "$svc")
[ -n "$ids" ] || { echo "the dev stack's $svc is not running; start it:" \
"docker compose --env-file docker/dev.env up -d --build" >&2; exit 1; }
for id in $ids; do
meta=$(docker inspect --format "{{index .Config.Labels \"$L\"}}|{{index .Config.Labels \"$L.config_files\"}}" "$id")
case ",${meta#*|}," in
*[/,]docker-compose.dev.yml,*) ;;
*) echo "refusing: project '${meta%%|*}' was not started with docker-compose.dev.yml, so it is not a dev stack" \
"(its $svc was created from: ${meta#*|})" >&2; exit 1 ;;
esac
done
done
API="http://$("${DC[@]}" port server 8080)" # the published host port, wherever DEV_API_PORT put it
# docker/entrypoint.sh removes initialized.txt at every start, and the server writes it when its initialization is over
# (with "OK" or the error), so also wait for the API to answer, which it does only after a boot that worked. Both
# probes are silent: while Tomcat boots, refused or reset connections are expected, not errors worth printing.
ready() { "${DC[@]}" exec -T server test -f /opt/mdmesh/initialized.txt >/dev/null 2>&1 \
&& curl -fs -o /dev/null -m 5 "$API/rest/public/auth/options" 2>/dev/null; }
printf 'dev stack %s: waiting for the server (the first boot runs Liquibase)' "$project"
up=
for _ in $(seq 1 60); do ready && { up=1; break; }; printf '.'; sleep 5; done
if [ -n "$up" ]; then echo " ready"; else
echo " timed out"
echo "the server did not answer $API/rest/public/auth/options within 5 minutes;" \
"see its log: docker compose --env-file docker/dev.env logs server" >&2
exit 1
fi
state=$(mdm_db_state)
case "$state" in
fresh)
tmp=$(mdm_rand)
mdm_seed admin@localhost install/sql/hmdm_init.en.sql "$tmp" "$(openssl rand -hex 16)"
mdm_post_seed install/sql/post_seed.sql
# First login with the temporary password returns a reset token; setting the password with it is what the
# console's Set Password page does. If a step fails from here on, the database is seeded but admin still has the
# random password; a re-run finishes the reset (seeded branch).
resume() { echo "the database is seeded; re-run scripts/dev-seed.sh to finish setting the admin password" >&2
exit 1; }
user=$(api_post /rest/public/auth/login "{\"login\":\"admin\",\"password\":\"$(md5u "$tmp")\"}") || resume
token=$(printf '%s' "$user" | python3 -c 'import sys,json; d=json.load(sys.stdin) or {}
print(d.get("passwordResetToken") or "" if d.get("passwordReset") else "")')
[ -n "$token" ] || { echo "the first admin login did not ask for a password change" >&2; resume; }
reset_pw "$token" || resume
echo "seeded; console login: admin / $PW" ;;
seeded)
mdm_post_seed install/sql/post_seed.sql
# A run that seeded but failed before the password reset left admin behind a random password it never printed.
# mdm_seed stored the reset token; finish the reset with it. The server takes a reset token for 24 hours from
# its issue time, which the seed does not set, so it is stamped now.
token=$(mdm_psql -c "UPDATE users SET passwordresettokentime = (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT
WHERE login='admin' AND passwordreset AND passwordresettoken IS NOT NULL
RETURNING passwordresettoken")
if [ -n "$token" ]; then
reset_pw "$token" || exit 1
echo "seeded (admin password reset resumed): admin / $PW"
else
echo "already seeded; post-seed repairs applied (admin password unchanged)"
fi ;;
unavailable)
echo "could not query the database; check postgres: docker compose --env-file docker/dev.env logs postgres" >&2
exit 1 ;;
*) echo "database state is '$state'; refusing to seed" \
"(docker compose --env-file docker/dev.env down -v resets it)" >&2
exit 1 ;;
esac