blob: 6b0493d41e6a6415be43d6125d756f3c98b885d3 [file]
#!/bin/bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -e
DATASOURCE_FILE="/etc/grafana/provisioning/datasources/datasource.yml"
# Detect database type
# Priority: DATABASE_TYPE, then legacy MYSQL_*/POSTGRES_* auto-detection
if [ -n "$DATABASE_TYPE" ]; then
MODE="$DATABASE_TYPE"
case "$MODE" in
mysql)
export MYSQL_URL="${DATABASE_HOST:-mysql}:${DATABASE_PORT:-3306}"
export MYSQL_DATABASE="${DATABASE_NAME:-lake}"
export MYSQL_USER="${DATABASE_USER:-merico}"
export MYSQL_PASSWORD="${DATABASE_PASSWORD:-merico}"
;;
postgresql)
export POSTGRES_URL="${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}"
export POSTGRES_DATABASE="${DATABASE_NAME:-lake}"
export POSTGRES_USER="${DATABASE_USER:-merico}"
export POSTGRES_PASSWORD="${DATABASE_PASSWORD:-merico}"
;;
*)
echo "ERROR: DATABASE_TYPE must be 'mysql' or 'postgresql'"
exit 1
;;
esac
else
# Legacy: auto-detect from MYSQL_*/POSTGRES_* vars
if [ -n "$POSTGRES_URL" ]; then
MODE="postgresql"
elif [ -n "$MYSQL_URL" ]; then
MODE="mysql"
else
echo "WARNING: No database vars. Defaulting to mysql."
MODE="mysql"
export MYSQL_URL="mysql:3306"
export MYSQL_DATABASE="lake"
export MYSQL_USER="merico"
export MYSQL_PASSWORD="merico"
fi
fi
echo "Database type: $MODE"
# Remove unused dashboard folder to prevent confusion
if [ "$MODE" = "mysql" ]; then
rm -rf /etc/grafana/dashboards/postgresql
else
rm -rf /etc/grafana/dashboards/mysql
SSL_MODE="${DATABASE_SSL_MODE:-disable}"
echo "SSL Mode: ${SSL_MODE}"
fi
# Locate Homepage.json for the home dashboard. The layout differs by deployment:
# - baked-in image keeps variant subfolders: /etc/grafana/dashboards/<mode>/Homepage.json
# - dev compose bind-mounts the variant folder directly: /etc/grafana/dashboards/Homepage.json
# Probe both so the home dashboard resolves in either case (a wrong path makes
# Grafana 12+ return HTTP 500 "Failed to load home dashboard").
for _hp in \
"/etc/grafana/dashboards/${MODE}/Homepage.json" \
"/etc/grafana/dashboards/Homepage.json"; do
if [ -f "$_hp" ]; then
export GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH="$_hp"
break
fi
done
echo "Homepage: $GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH"
# --- Legacy persistent-volume hygiene -------------------------------------
# This image runs as an arbitrary uid with gid 0 (OpenShift-compatible). Volumes
# created by older dashboard images (uid 472, file mode 0640) can be read-only for
# the current runtime user, which makes Grafana's DB migrations fail with
# "attempt to write a readonly database". Best-effort self-heal here; if it can't
# be fixed (non-root, non-owner) print a clear, actionable error instead of a
# cryptic SQLite crash.
GRAFANA_DATA_DIR="${GF_PATHS_DATA:-/var/lib/grafana}"
GRAFANA_DB="${GRAFANA_DATA_DIR}/grafana.db"
if [ -e "$GRAFANA_DB" ] && [ ! -w "$GRAFANA_DB" ]; then
echo "WARNING: ${GRAFANA_DB} not writable by $(id -u):$(id -g); attempting permission fix..."
chgrp -R 0 "$GRAFANA_DATA_DIR" 2>/dev/null || true
chmod -R g+rwX "$GRAFANA_DATA_DIR" 2>/dev/null || true
fi
if [ -e "$GRAFANA_DB" ] && [ ! -w "$GRAFANA_DB" ]; then
echo "ERROR: ${GRAFANA_DB} is still read-only for gid 0."
echo " The persistent volume was likely created by an older image (uid 472, mode 0640)."
echo " Fix it once from the host, then recreate this container:"
echo " docker run --rm -v <project>_grafana-storage:/data alpine \\"
echo " sh -c 'chgrp -R 0 /data && chmod -R g+rwX /data'"
fi
# Remove the deprecated Angular grafana-piechart-panel plugin if it lingers in a
# legacy volume. Grafana 12+ dropped Angular support and dashboards now use the
# core "piechart" panel; leaving it causes noisy plugin-validation errors.
LEGACY_PIECHART="${GRAFANA_DATA_DIR}/plugins/grafana-piechart-panel"
if [ -d "$LEGACY_PIECHART" ]; then
echo "Removing deprecated grafana-piechart-panel from data volume..."
rm -rf "$LEGACY_PIECHART" 2>/dev/null || true
fi
# --------------------------------------------------------------------------
# Create empty datasource.yml (datasources created via API)
cat > "$DATASOURCE_FILE" << HEADER
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
HEADER
# Admin credentials used by the datasource provisioning API calls below.
# Defaults to Grafana's built-in admin password; override via GF_SECURITY_ADMIN_PASSWORD.
# Exported so Grafana itself and the curl calls use the same value.
export GF_SECURITY_ADMIN_PASSWORD="${GF_SECURITY_ADMIN_PASSWORD:-admin}"
# Start Grafana in background
/run.sh "$@" &
GRAFANA_PID=$!
# Wait for Grafana API
echo "Waiting for Grafana API..."
for i in $(seq 1 60); do
if curl -f -s http://localhost:3000/api/health >/dev/null 2>&1; then
echo "Grafana API ready"
sleep 5 # Extra wait for migrations
break
fi
sleep 2
done
# Grafana 13+ auto-creates empty datasource instances for built-in plugins and
# legacy volumes may contain read-only provisioned datasources with different UIDs.
# Delete ALL existing datasources (by ID, which bypasses read-only flags) so we can
# create a single correctly-configured datasource with the UID our dashboards expect.
echo "Deleting all existing datasources (clean slate)..."
for _id in $(curl -s "http://admin:${GF_SECURITY_ADMIN_PASSWORD}@localhost:3000/api/datasources" 2>/dev/null \
| grep -o '"id":[0-9]*' | sed 's/"id"://g'); do
curl -s -X DELETE "http://admin:${GF_SECURITY_ADMIN_PASSWORD}@localhost:3000/api/datasources/${_id}" 2>&1 || true
done
sleep 2
# Create datasource via API (both MySQL and PostgreSQL)
PAYLOAD_FILE="/tmp/datasource-api.json"
if [ "$MODE" = "mysql" ]; then
cat > "$PAYLOAD_FILE" <<APIJSON
{
"uid": "devlake-mysql-api",
"name": "mysql",
"type": "mysql",
"url": "${MYSQL_URL}",
"database": "${MYSQL_DATABASE}",
"user": "${MYSQL_USER}",
"secureJsonData": {
"password": "${MYSQL_PASSWORD}"
},
"access": "proxy",
"isDefault": true,
"editable": true
}
APIJSON
else
SSL_MODE="${DATABASE_SSL_MODE:-disable}"
cat > "$PAYLOAD_FILE" <<APIJSON
{
"uid": "devlake-postgres-api",
"name": "postgresql",
"type": "grafana-postgresql-datasource",
"url": "${POSTGRES_URL}",
"database": "${POSTGRES_DATABASE}",
"user": "${POSTGRES_USER}",
"secureJsonData": {
"password": "${POSTGRES_PASSWORD}"
},
"jsonData": {
"sslmode": "${SSL_MODE}",
"postgresVersion": 1800,
"database": "${POSTGRES_DATABASE}"
},
"access": "proxy",
"isDefault": true,
"editable": true
}
APIJSON
fi
echo "Creating datasource via API..."
for i in $(seq 1 10); do
RESPONSE=$(curl -s -X POST "http://admin:${GF_SECURITY_ADMIN_PASSWORD}@localhost:3000/api/datasources" \
-H "Content-Type: application/json" \
-d @"$PAYLOAD_FILE" 2>&1)
if echo "$RESPONSE" | grep -q '"id"'; then
echo "Datasource created successfully"
break
elif echo "$RESPONSE" | grep -q "already exists"; then
# A datasource with this name is already present (e.g. provisioned by an
# older image into a persistent volume). It cannot be recreated via the API
# but is functional, so treat this as success instead of retrying.
echo "Datasource already exists; keeping existing one"
break
elif echo "$RESPONSE" | grep -q "database is locked"; then
echo "DB locked, retry $i/10..."
sleep 3
else
echo "API error: $RESPONSE"
sleep 2
fi
done
# Wait for Grafana
wait $GRAFANA_PID