Commit 32cce66f authored by xuwang's avatar xuwang
Browse files

feat(renovate): default bot to Maintainer; RENOVATE_BOT_NAME for multiple bots



- Default RENOVATE_BOT_ACCESS_LEVEL to 40 (Maintainer) for both the bot token and
  its group membership, so it can automerge protected branches out of the box
  (set 30 for least privilege).
- Add RENOVATE_BOT_NAME (default renovate-bot): the token name and the default
  token file (~/.otica-tokens/<name>.token) and Vault path segment. Use distinct
  names to run several bots for different repo trees, each managed (create/list/
  rotate/revoke) independently. create/sa-create/rotate now share _store_token,
  so group/project bots also get Vault storage.

Changelog: changed
Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 0089d522
Loading
Loading
Loading
Loading
+28 −2
Original line number Diff line number Diff line
@@ -229,12 +229,38 @@ Created with your admin token (`GITLAB_TOKEN` / `~/.gitlab-token`, Owner/Maintai

| Variable | Default | Purpose |
| --- | --- | --- |
| `RENOVATE_BOT_NAME` | `renovate-bot` | Bot identifier — token name + default file/Vault path. Set distinct names to run **multiple bots** (see below) |
| `RENOVATE_BOT_GROUP` | _(empty)_ | Group full path → create a group token (else project) |
| `RENOVATE_BOT_REPO` | origin | Project for project-token / `ci-set` |
| `RENOVATE_BOT_TOKEN_FILE` | `~/.otica-tokens/renovate.token` | Where the token is stored |
| `RENOVATE_BOT_ACCESS_LEVEL` | `30` (Developer) | `40` (Maintainer) if the bot must automerge protected branches |
| `RENOVATE_BOT_TOKEN_FILE` | `~/.otica-tokens/<name>.token` | Where the token is stored |
| `RENOVATE_BOT_ACCESS_LEVEL` | `40` (Maintainer) | `30` (Developer) for least privilege; Maintainer is needed to automerge protected branches |
| `RENOVATE_BOT_EXPIRES` | +365 days | Token expiry (`YYYY-MM-DD`); used for new and rotated tokens |

### Multiple bots (one per repo tree)

A group/project token only covers one tree, so for several trees run a bot **per
tree**, distinguished by `RENOVATE_BOT_NAME` (each gets its own token, file, and
Vault path). Pair each with its group and admin-config subset:

```bash
# bot + scan for the gcp tree
make renovate-bot-create  RENOVATE_BOT_NAME=renovate-bot-gcp     RENOVATE_BOT_GROUP=gcp
make renovate-admin-run   RENOVATE_BOT_NAME=renovate-bot-gcp \
                          RENOVATE_TOKEN_FILE=~/.otica-tokens/renovate-bot-gcp.token \
                          RENOVATE_ADMIN_CONFIG=renovate-admin.gcp.json

# bot + scan for the irt-dcs tree
make renovate-bot-create  RENOVATE_BOT_NAME=renovate-bot-irt-dcs RENOVATE_BOT_GROUP=irt-dcs
make renovate-admin-run   RENOVATE_BOT_NAME=renovate-bot-irt-dcs \
                          RENOVATE_TOKEN_FILE=~/.otica-tokens/renovate-bot-irt-dcs.token \
                          RENOVATE_ADMIN_CONFIG=renovate-admin.irt-dcs.json
```

`list` / `rotate` / `revoke` all act on the bot named by `RENOVATE_BOT_NAME`, so
each tree's bot is managed independently. (For sub-repos spanning groups with **no**
common parent, the single cross-group service account below is usually simpler than
one bot per group.)

**Use it for the scan** — point Renovate at the bot token (it outranks the ambient
`GITLAB_TOKEN`):

+5 −2
Original line number Diff line number Diff line
@@ -35,9 +35,12 @@ RENOVATE_AUTODISCOVER_FILTER ?=
RENOVATE_ADMIN_CONFIG        ?= renovate-admin.json
# Optional dedicated Renovate bot identity (access token). Group token if
# RENOVATE_BOT_GROUP is set (multi-repo scan), else a project token (per-repo CI).
# RENOVATE_BOT_NAME identifies the bot — use distinct names to run several bots
# for different repo trees (each gets its own token, file, and Vault path).
RENOVATE_BOT_NAME            ?= renovate-bot
RENOVATE_BOT_GROUP           ?=
RENOVATE_BOT_REPO            ?=
RENOVATE_BOT_TOKEN_FILE      ?= ${HOME}/.otica-tokens/renovate.token
RENOVATE_BOT_TOKEN_FILE      ?= ${HOME}/.otica-tokens/${RENOVATE_BOT_NAME}.token
RENOVATE_BOT_ACCESS_LEVEL    ?=
RENOVATE_BOT_EXPIRES         ?=
# Cross-group service-account bot (sub-repos spanning several top-level groups).
@@ -47,7 +50,7 @@ RENOVATE_BOT_ADMIN_TOKEN_FILE ?=
# sa-create can also store the token in Vault via RENOVATE_TOKEN_VAULT_PATH (the
# same var the scan reads from) — declared in renovate.mk.
export RENOVATE_AUTODISCOVER_FILTER RENOVATE_ADMIN_CONFIG \
       RENOVATE_BOT_GROUP RENOVATE_BOT_REPO RENOVATE_BOT_TOKEN_FILE \
       RENOVATE_BOT_NAME RENOVATE_BOT_GROUP RENOVATE_BOT_REPO RENOVATE_BOT_TOKEN_FILE \
       RENOVATE_BOT_ACCESS_LEVEL RENOVATE_BOT_EXPIRES \
       RENOVATE_BOT_USER RENOVATE_BOT_GROUPS RENOVATE_BOT_ADMIN_TOKEN_FILE

+30 −38
Original line number Diff line number Diff line
@@ -23,14 +23,17 @@
#                                      # project from the stored bot token
#
# Tunables:
#   RENOVATE_BOT_NAME         bot identifier — token name + default file/Vault path
#                             (default renovate-bot). Use distinct names to run
#                             multiple bots for different repo trees.
#   RENOVATE_BOT_GROUP        group full path -> create a GROUP token (else project)
#   RENOVATE_BOT_REPO         project path for project/ci-set (default: origin)
#   RENOVATE_BOT_TOKEN_FILE   where to store the token (default ~/.otica-tokens/renovate.token)
#   RENOVATE_BOT_ACCESS_LEVEL 30=Developer (default) | 40=Maintainer (for automerge)
#   RENOVATE_BOT_TOKEN_FILE   where to store the token (default ~/.otica-tokens/<name>.token)
#   RENOVATE_BOT_ACCESS_LEVEL 40=Maintainer (default) | 30=Developer
#   RENOVATE_BOT_EXPIRES      YYYY-MM-DD (default +365 days)
#   RENOVATE_TOKEN_VAULT_PATH  also store the created token in Vault at this path
#                             (sa-create); defaults to
#                             ${SEC_PATH}/gitlab/renovate_bot/<user>/token when SEC_PATH set.
#   RENOVATE_TOKEN_VAULT_PATH  also store the token in Vault at this path (create /
#                             sa-create / rotate); defaults to
#                             ${SEC_PATH}/gitlab/renovate_bot/<name>/token when SEC_PATH set.
#                             Read it back with: vault-read.sh <path>
###############################################################################
THIS_DIR=$(cd "$(dirname "$0")" && pwd)
@@ -43,10 +46,13 @@ _host="$(_remote_url | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#[:/].*$##')"
GITLAB_TOKEN="${GITLAB_TOKEN:-}"                 # predeclare for set -u in gitlab.sh
source "${THIS_DIR}/gitlab.sh"                   # GITLAB_API, get_project_id, token

NAME="renovate-bot"
LEVEL="${RENOVATE_BOT_ACCESS_LEVEL:-30}"
# RENOVATE_BOT_NAME identifies the bot — the token name, and (by default) the
# local file and Vault path. Use distinct names to run multiple bots for
# different repo trees (e.g. renovate-bot-gcp, renovate-bot-irt-dcs).
NAME="${RENOVATE_BOT_NAME:-renovate-bot}"
LEVEL="${RENOVATE_BOT_ACCESS_LEVEL:-40}"          # 40=Maintainer (default), 30=Developer
EXPIRES="${RENOVATE_BOT_EXPIRES:-$(date -v+365d +%F 2>/dev/null || date -d '+365 days' +%F)}"
TOKEN_FILE="${RENOVATE_BOT_TOKEN_FILE:-${HOME}/.otica-tokens/renovate.token}"
TOKEN_FILE="${RENOVATE_BOT_TOKEN_FILE:-${HOME}/.otica-tokens/${NAME}.token}"
hdr=(--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}")

default_repo() {
@@ -64,7 +70,6 @@ else
fi

cmd_create() {
    mkdir -p "$(dirname "${TOKEN_FILE}")"; chmod 700 "$(dirname "${TOKEN_FILE}")" 2>/dev/null || true
    local resp tok user
    resp=$(curl -s "${hdr[@]}" -X POST "${api}" \
        --data "name=${NAME}" \
@@ -72,13 +77,12 @@ cmd_create() {
        --data "access_level=${LEVEL}" --data "expires_at=${EXPIRES}")
    tok=$(echo "${resp}" | jq -r '.token // empty')
    [ -n "${tok}" ] || err "create failed on ${KIND} ${TARGET}: $(echo "${resp}" | jq -r '.message // .error // .')"
    printf '%s' "${tok}" > "${TOKEN_FILE}"; chmod 600 "${TOKEN_FILE}"
    user=$(curl -s --header "PRIVATE-TOKEN: ${tok}" "${GITLAB_API}/user" | jq -r '.username // "?"')
    echo "Created ${NAME} on ${KIND} ${TARGET}"
    echo "  user:    ${user}   access:${LEVEL}   expires:${EXPIRES}"
    echo "  stored:  ${TOKEN_FILE}"
    echo "  scan:    point Renovate at it -> RENOVATE_TOKEN_FILE=${TOKEN_FILE}"
    echo "  ci/cd:   renovate-bot.sh ci-set   (sets the RENOVATE_TOKEN CI variable)"
    _store_token "${tok}"
    echo "  scan:    RENOVATE_TOKEN_FILE=${TOKEN_FILE}  (or RENOVATE_TOKEN_VAULT_PATH)"
    echo "  ci/cd:   renovate-bot.sh ci-set"
}

cmd_list() {
@@ -128,30 +132,16 @@ cmd_sa_create() {
    ahdr=(--header "PRIVATE-TOKEN: ${atok}")
    resp=$(curl -s "${ahdr[@]}" -X POST "${GITLAB_API}/service_accounts" \
        --data-urlencode "name=${RENOVATE_BOT_SA_NAME:-Renovate Bot}" \
        --data "username=${RENOVATE_BOT_USER:-renovate-bot}")
        --data "username=${RENOVATE_BOT_USER:-${NAME}}")
    sid=$(echo "${resp}" | jq -r '.id // empty')
    suser=$(echo "${resp}" | jq -r '.username // empty')
    [ -n "${sid}" ] || err "service-account create failed (needs instance admin): $(echo "${resp}" | jq -r '.message // .error // .')"
    mkdir -p "$(dirname "${TOKEN_FILE}")"; chmod 700 "$(dirname "${TOKEN_FILE}")" 2>/dev/null || true
    resp=$(curl -s "${ahdr[@]}" -X POST "${GITLAB_API}/service_accounts/${sid}/personal_access_tokens" \
        --data "name=renovate-bot" --data "scopes[]=api" --data "scopes[]=write_repository" --data "expires_at=${EXPIRES}")
        --data "name=${NAME}" --data "scopes[]=api" --data "scopes[]=write_repository" --data "expires_at=${EXPIRES}")
    tok=$(echo "${resp}" | jq -r '.token // empty')
    [ -n "${tok}" ] || err "SA token create failed: $(echo "${resp}" | jq -r '.message // .error // .')"
    printf '%s' "${tok}" > "${TOKEN_FILE}"; chmod 600 "${TOKEN_FILE}"
    echo "Created service account: ${suser} (id=${sid})"
    echo "  token stored: ${TOKEN_FILE}  (expires ${EXPIRES})"
    # Optionally also store the token in Vault. Enabled by RENOVATE_TOKEN_VAULT_PATH
    # (the SAME var the scan reads from), defaulting to
    # ${SEC_PATH}/gitlab/renovate_bot/<user>/token when SEC_PATH is set.
    local vpath="${RENOVATE_TOKEN_VAULT_PATH:-}"
    [ -z "${vpath}" ] && [ -n "${SEC_PATH:-}" ] && vpath="${SEC_PATH}/gitlab/renovate_bot/${suser}/token"
    if [ -n "${vpath}" ]; then
        if "${THIS_DIR}/vault-write.sh" "${vpath}" "${tok}" >/dev/null; then
            echo "  vault:   ${vpath}  (read with: vault-read.sh ${vpath})"
        else
            echo "  vault:   FAILED to write ${vpath} (check vault login / VAULT_ADDR)" >&2
        fi
    fi
    _store_token "${tok}"
    echo "  next: renovate-bot.sh add-groups   RENOVATE_BOT_USER=${suser}"
    echo "        then point scans at RENOVATE_TOKEN_FILE=${TOKEN_FILE}"
}
@@ -169,7 +159,7 @@ groups_from_config() {
cmd_add_groups() {
    local user="${RENOVATE_BOT_USER:-}" uid groups g gid lvl code
    [ -n "${user}" ] || err "set RENOVATE_BOT_USER to the bot username."
    lvl="${RENOVATE_BOT_ACCESS_LEVEL:-30}"
    lvl="${RENOVATE_BOT_ACCESS_LEVEL:-40}"
    uid=$(curl -s "${hdr[@]}" "${GITLAB_API}/users?username=${user}" | jq -r '.[0].id // empty')
    [ -n "${uid}" ] || err "user '${user}' not found on ${GITLAB_SERVER}."
    groups="${RENOVATE_BOT_GROUPS:-$(groups_from_config || true)}"
@@ -188,18 +178,20 @@ cmd_add_groups() {
    done
}

# ── Rotation ────────────────────────────────────────────────────────────────────
# Store a token to the local file and (if configured) Vault — shared by rotate.
_store_token() {   # _store_token <token> [vault_default_user]
    local tok="$1" vuser="${2:-}" vpath
# Store a token to the local file and (if configured) Vault — shared by
# create/sa-create/rotate. Vault path: RENOVATE_TOKEN_VAULT_PATH, else
# ${SEC_PATH}/gitlab/renovate_bot/<NAME>/token when SEC_PATH is set.
_store_token() {   # _store_token <token>
    local tok="$1" vpath
    mkdir -p "$(dirname "${TOKEN_FILE}")"; chmod 700 "$(dirname "${TOKEN_FILE}")" 2>/dev/null || true
    printf '%s' "${tok}" > "${TOKEN_FILE}"; chmod 600 "${TOKEN_FILE}"
    echo "  token stored: ${TOKEN_FILE}  (expires ${EXPIRES})"
    vpath="${RENOVATE_TOKEN_VAULT_PATH:-}"
    [ -z "${vpath}" ] && [ -n "${SEC_PATH:-}" ] && [ -n "${vuser}" ] && vpath="${SEC_PATH}/gitlab/renovate_bot/${vuser}/token"
    [ -z "${vpath}" ] && [ -n "${SEC_PATH:-}" ] && vpath="${SEC_PATH}/gitlab/renovate_bot/${NAME}/token"
    if [ -n "${vpath}" ]; then
        "${THIS_DIR}/vault-write.sh" "${vpath}" "${tok}" >/dev/null \
            && echo "  vault:   ${vpath}" || echo "  vault:   FAILED to write ${vpath}" >&2
            && echo "  vault:   ${vpath}  (read with: vault-read.sh ${vpath})" \
            || echo "  vault:   FAILED to write ${vpath} (check vault login / VAULT_ADDR)" >&2
    fi
}

@@ -223,7 +215,7 @@ cmd_rotate() {
        new=$(echo "${resp}" | jq -r '.token // empty')
        [ -n "${new}" ] || err "SA self-rotate failed: $(echo "${resp}" | jq -r '.message // .error // .')"
        echo "Rotated service-account token for ${RENOVATE_BOT_USER} (old revoked)."
        _store_token "${new}" "${RENOVATE_BOT_USER}"
        _store_token "${new}"
    else
        id=$(curl -s "${hdr[@]}" "${api}" | jq -r --arg n "${NAME}" \
            '[.[]? | select(.name==$n and .active==true)] | sort_by(.expires_at) | last | .id // empty')