|
| 1 | +#!/bin/bash -eu |
| 2 | +# |
| 3 | +# Copyright (c) 2026 by Delphix. All rights reserved. |
| 4 | +# |
| 5 | +# SPDX-License-Identifier: GPL-2.0-or-later |
| 6 | +# |
| 7 | +# One-time InfluxDB initialization: creates org, bucket, admin token, |
| 8 | +# a read-only token for DCT Smart Proxy, and appends the |
| 9 | +# [[outputs.influxdb_v2]] stanza to /etc/telegraf/telegraf.base. |
| 10 | +# Skips setup if InfluxDB is already initialized. |
| 11 | +# |
| 12 | + |
| 13 | +INFLUXDB_URL="http://127.0.0.1:8086" |
| 14 | +INFLUXDB_CONFIG_DIR="/etc/influxdb" |
| 15 | +INFLUXDB_META_FILE="$INFLUXDB_CONFIG_DIR/influxdb_meta" |
| 16 | +# State file written immediately after /api/v2/setup so the script can resume |
| 17 | +# if it is interrupted before the metadata file is fully written. |
| 18 | +INFLUXDB_SETUP_STATE_FILE="$INFLUXDB_CONFIG_DIR/influxdb_setup_state" |
| 19 | +TELEGRAF_BASE="/etc/telegraf/telegraf.base" |
| 20 | +INFLUXDB_INIT_CONF="$INFLUXDB_CONFIG_DIR/influxdb-init.conf" |
| 21 | + |
| 22 | +# Load tunable configuration (org, bucket, retention, wait parameters). |
| 23 | +# shellcheck source=/etc/influxdb/influxdb-init.conf |
| 24 | +# shellcheck disable=SC1091 |
| 25 | +source "$INFLUXDB_INIT_CONF" |
| 26 | + |
| 27 | +INFLUXDB_ADMIN_USER="admin" |
| 28 | +INFLUXDB_ADMIN_PASSWORD="$(openssl rand -hex 16)" |
| 29 | + |
| 30 | +# |
| 31 | +# Log a message to stderr with a timestamp. |
| 32 | +# |
| 33 | +log() { |
| 34 | + echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" >&2 |
| 35 | +} |
| 36 | + |
| 37 | +# |
| 38 | +# Extract a field from a JSON string using python3. |
| 39 | +# |
| 40 | +json_field() { |
| 41 | + local json="$1" |
| 42 | + local field="$2" |
| 43 | + echo "$json" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())$field)" || |
| 44 | + { log "ERROR: Failed to parse field '$field' from JSON response."; return 1; } |
| 45 | +} |
| 46 | + |
| 47 | +# |
| 48 | +# POST to the InfluxDB HTTP API. Exits with an error if the request fails. |
| 49 | +# |
| 50 | +influx_post() { |
| 51 | + local endpoint="$1" |
| 52 | + local data="$2" |
| 53 | + local auth_header="${3:-}" |
| 54 | + |
| 55 | + local curl_args=(-sf -X POST "$INFLUXDB_URL$endpoint" -H 'Content-Type: application/json' -d "$data") |
| 56 | + [[ -n "$auth_header" ]] && curl_args+=(-H "Authorization: Token $auth_header") |
| 57 | + |
| 58 | + local response |
| 59 | + response=$(curl "${curl_args[@]}") || |
| 60 | + { log "ERROR: HTTP POST to '$endpoint' failed."; return 1; } |
| 61 | + echo "$response" |
| 62 | +} |
| 63 | + |
| 64 | +mkdir -p "$INFLUXDB_CONFIG_DIR" |
| 65 | + |
| 66 | +# Skip if already fully initialized. |
| 67 | +if [[ -f "$INFLUXDB_META_FILE" ]]; then |
| 68 | + log "InfluxDB already initialized, skipping." |
| 69 | + exit 0 |
| 70 | +fi |
| 71 | + |
| 72 | +# |
| 73 | +# Wait for InfluxDB to be ready. |
| 74 | +# |
| 75 | +log "Waiting for InfluxDB to be ready..." |
| 76 | +ready=false |
| 77 | +for i in $(seq 1 "$INFLUXDB_WAIT_RETRIES"); do |
| 78 | + if curl -sf "$INFLUXDB_URL/health" &>/dev/null; then |
| 79 | + ready=true |
| 80 | + break |
| 81 | + fi |
| 82 | + log "InfluxDB not ready yet (attempt $i/$INFLUXDB_WAIT_RETRIES), retrying in ${INFLUXDB_WAIT_INTERVAL}s..." |
| 83 | + sleep "$INFLUXDB_WAIT_INTERVAL" |
| 84 | +done |
| 85 | + |
| 86 | +if [[ "$ready" != "true" ]]; then |
| 87 | + log "ERROR: InfluxDB did not become ready after $((INFLUXDB_WAIT_RETRIES * INFLUXDB_WAIT_INTERVAL))s." |
| 88 | + exit 1 |
| 89 | +fi |
| 90 | +log "InfluxDB is ready." |
| 91 | + |
| 92 | +# |
| 93 | +# Initial setup — creates org, bucket, and returns admin token + IDs. |
| 94 | +# /api/v2/setup is a one-shot operation; if the script is interrupted after |
| 95 | +# this point and re-run, the state file lets us skip setup and reuse the |
| 96 | +# already-created admin token. |
| 97 | +# |
| 98 | +ADMIN_TOKEN="" |
| 99 | +ORG_ID="" |
| 100 | +BUCKET_ID="" |
| 101 | + |
| 102 | +if [[ -f "$INFLUXDB_SETUP_STATE_FILE" ]]; then |
| 103 | + log "Found existing setup state, loading admin token and IDs..." |
| 104 | + while IFS= read -r line; do |
| 105 | + key="${line%%=*}" |
| 106 | + value="${line#*=}" |
| 107 | + case "$key" in |
| 108 | + ADMIN_TOKEN) ADMIN_TOKEN="$value" ;; |
| 109 | + ORG_ID) ORG_ID="$value" ;; |
| 110 | + BUCKET_ID) BUCKET_ID="$value" ;; |
| 111 | + esac |
| 112 | + done <"$INFLUXDB_SETUP_STATE_FILE" |
| 113 | +else |
| 114 | + log "Running initial InfluxDB setup..." |
| 115 | + SETUP_RESPONSE=$(influx_post "/api/v2/setup" "{ |
| 116 | + \"username\": \"$INFLUXDB_ADMIN_USER\", |
| 117 | + \"password\": \"$INFLUXDB_ADMIN_PASSWORD\", |
| 118 | + \"org\": \"$INFLUXDB_ORG\", |
| 119 | + \"bucket\": \"$INFLUXDB_BUCKET\", |
| 120 | + \"retentionPeriodSeconds\": $INFLUXDB_RETENTION_SECONDS |
| 121 | + }") || exit 1 |
| 122 | + |
| 123 | + ADMIN_TOKEN=$(json_field "$SETUP_RESPONSE" "['auth']['token']") || exit 1 |
| 124 | + ORG_ID=$(json_field "$SETUP_RESPONSE" "['org']['id']") || exit 1 |
| 125 | + BUCKET_ID=$(json_field "$SETUP_RESPONSE" "['bucket']['id']") || exit 1 |
| 126 | + |
| 127 | + # Persist admin token + IDs immediately so a subsequent re-run can resume |
| 128 | + # without repeating the one-shot setup call. |
| 129 | + old_umask="$(umask)" |
| 130 | + umask 077 |
| 131 | + tmp_state="$(mktemp "${INFLUXDB_SETUP_STATE_FILE}.XXXXXX")" |
| 132 | + printf 'ADMIN_TOKEN=%s\nORG_ID=%s\nBUCKET_ID=%s\n' \ |
| 133 | + "$ADMIN_TOKEN" "$ORG_ID" "$BUCKET_ID" >"$tmp_state" |
| 134 | + chmod 600 "$tmp_state" |
| 135 | + mv "$tmp_state" "$INFLUXDB_SETUP_STATE_FILE" |
| 136 | + umask "$old_umask" |
| 137 | +fi |
| 138 | + |
| 139 | +# |
| 140 | +# Create a write-only token for Telegraf. |
| 141 | +# |
| 142 | +log "Creating Telegraf write token..." |
| 143 | +WRITE_TOKEN_RESPONSE=$(influx_post "/api/v2/authorizations" "{ |
| 144 | + \"orgID\": \"$ORG_ID\", |
| 145 | + \"description\": \"telegraf-write-token\", |
| 146 | + \"permissions\": [ |
| 147 | + {\"action\": \"write\", \"resource\": {\"type\": \"buckets\", \"id\": \"$BUCKET_ID\", \"orgID\": \"$ORG_ID\"}} |
| 148 | + ] |
| 149 | +}" "$ADMIN_TOKEN") || exit 1 |
| 150 | +WRITE_TOKEN=$(json_field "$WRITE_TOKEN_RESPONSE" "['token']") || exit 1 |
| 151 | + |
| 152 | +# |
| 153 | +# Create a read-only token for DCT Smart Proxy. |
| 154 | +# |
| 155 | +log "Creating DCT Smart Proxy read token..." |
| 156 | +READ_TOKEN_RESPONSE=$(influx_post "/api/v2/authorizations" "{ |
| 157 | + \"orgID\": \"$ORG_ID\", |
| 158 | + \"description\": \"dct-read-token\", |
| 159 | + \"permissions\": [ |
| 160 | + {\"action\": \"read\", \"resource\": {\"type\": \"buckets\", \"id\": \"$BUCKET_ID\", \"orgID\": \"$ORG_ID\"}} |
| 161 | + ] |
| 162 | +}" "$ADMIN_TOKEN") || exit 1 |
| 163 | +READ_TOKEN=$(json_field "$READ_TOKEN_RESPONSE" "['token']") || exit 1 |
| 164 | + |
| 165 | +# |
| 166 | +# Append the [[outputs.influxdb_v2]] stanza to telegraf.base so Telegraf |
| 167 | +# knows where to ship metrics. Input stanzas already exist in the file. |
| 168 | +# |
| 169 | +log "Appending InfluxDB output stanza to $TELEGRAF_BASE..." |
| 170 | +if [[ ! -f "$TELEGRAF_BASE" ]]; then |
| 171 | + log "WARNING: Telegraf base config not found at '$TELEGRAF_BASE'; skipping stanza append." \ |
| 172 | + "Run init_influxdb again once Telegraf is installed to complete Telegraf configuration." |
| 173 | +elif grep -q "^[[:space:]]*#\{0,1\}\[\[outputs\.influxdb_v2\]\]" "$TELEGRAF_BASE"; then |
| 174 | + log "InfluxDB output stanza already present in $TELEGRAF_BASE, skipping." |
| 175 | + chmod 640 "$TELEGRAF_BASE" |
| 176 | +else |
| 177 | + cat >>"$TELEGRAF_BASE" <<EOF |
| 178 | +
|
| 179 | +[[outputs.influxdb_v2]] |
| 180 | + urls = ["http://127.0.0.1:8086"] |
| 181 | + token = "$WRITE_TOKEN" |
| 182 | + organization = "$INFLUXDB_ORG" |
| 183 | + bucket = "$INFLUXDB_BUCKET" |
| 184 | + namepass = ["cpu", "disk", "diskio", "mem", "net", "procstat", "processes", "swap", "system", "zfs"] |
| 185 | +EOF |
| 186 | + # Enforce restrictive permissions so the write token is not world-readable. |
| 187 | + chmod 640 "$TELEGRAF_BASE" |
| 188 | +fi |
| 189 | + |
| 190 | +# |
| 191 | +# Persist org/bucket/admin credentials/tokens so DE APIs can expose them to DCT |
| 192 | +# and so the admin can access the InfluxDB UI. File is chmod 600 (root-only). |
| 193 | +# |
| 194 | +log "Writing InfluxDB metadata to $INFLUXDB_META_FILE..." |
| 195 | +# Use a restrictive umask and a temp file to avoid a window where tokens are |
| 196 | +# readable by non-root users, then atomically move the file into place. |
| 197 | +old_umask="$(umask)" |
| 198 | +umask 077 |
| 199 | +tmp_meta="$(mktemp "${INFLUXDB_META_FILE}.XXXXXX")" |
| 200 | +cat >"$tmp_meta" <<EOF |
| 201 | +INFLUXDB_ORG=$INFLUXDB_ORG |
| 202 | +INFLUXDB_BUCKET=$INFLUXDB_BUCKET |
| 203 | +INFLUXDB_ADMIN_USER=$INFLUXDB_ADMIN_USER |
| 204 | +INFLUXDB_ADMIN_PASSWORD=$INFLUXDB_ADMIN_PASSWORD |
| 205 | +INFLUXDB_WRITE_TOKEN=$WRITE_TOKEN |
| 206 | +INFLUXDB_READ_TOKEN=$READ_TOKEN |
| 207 | +EOF |
| 208 | +chmod 600 "$tmp_meta" |
| 209 | +mv "$tmp_meta" "$INFLUXDB_META_FILE" |
| 210 | +umask "$old_umask" |
| 211 | + |
| 212 | +rm -f "$INFLUXDB_SETUP_STATE_FILE" |
| 213 | +log "InfluxDB initialized successfully." |
0 commit comments