Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions deploy/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ CODEX_MODEL=""
KIRO_FROM_SECRET=""
TELEGRAM_BOT_TOKEN_SECRET="${TELEGRAM_BOT_TOKEN_SECRET:-}"
TELEGRAM_USER="${TELEGRAM_USER:-}"
KIROCREW_TG_BOT_TOKEN="${KIROCREW_TG_BOT_TOKEN:-}"
KIROCREW_TG_BOT_TOKEN_SECRET="${KIROCREW_TG_BOT_TOKEN_SECRET:-}"
KIROCREW_TG_USER_ID="${KIROCREW_TG_USER_ID:-}"

while [[ $# -gt 0 ]]; do
case "$1" in
Expand Down Expand Up @@ -220,6 +223,21 @@ while [[ $# -gt 0 ]]; do
TELEGRAM_USER="$2"
shift 2
;;
--kirocrew-tg-bot-token)
[[ $# -gt 1 ]] || { echo "ERROR: --kirocrew-tg-bot-token requires a value" >&2; exit 1; }
KIROCREW_TG_BOT_TOKEN="$2"
shift 2
;;
--kirocrew-tg-bot-token-secret)
[[ $# -gt 1 ]] || { echo "ERROR: --kirocrew-tg-bot-token-secret requires a value" >&2; exit 1; }
KIROCREW_TG_BOT_TOKEN_SECRET="$2"
shift 2
;;
--kirocrew-tg-user-id)
[[ $# -gt 1 ]] || { echo "ERROR: --kirocrew-tg-user-id requires a value" >&2; exit 1; }
KIROCREW_TG_USER_ID="$2"
shift 2
;;
--primary)
[[ $# -gt 1 ]] || { echo "ERROR: --primary requires a value" >&2; exit 1; }
PRIMARY="$2"
Expand Down Expand Up @@ -256,6 +274,48 @@ if [[ -z "$PACK_NAME" ]]; then
exit 1
fi

# ── Resolve KiroCrew Telegram bot-token secret (P1 security: token never in UserData) ──
# The installer stores the bot token in Secrets Manager and only passes the
# arn/id through the CFN parameter + UserData. Resolve it here before the
# pack-config JSON is built so the pack side keeps a single plaintext code
# path (telegram-bot-token) regardless of how the token was supplied.
#
# Region source-of-truth (Codex P1 on 79c93b6):
# The installer creates the secret in the CFN STACK region (DEPLOY_REGION),
# but bootstrap.sh's --region flag carries the BEDROCK region — which is
# pinned to us-east-1 whenever the deploy region is outside the Bedrock
# allowlist (e.g. ap-south-1). So we MUST NOT use $REGION here.
# Order of preference:
# 1) STACK_REGION exported by UserData (authoritative).
# 2) IMDSv2 placement/region (works on any EC2, exact stack region).
# 3) AWS_DEFAULT_REGION as a last resort.
# 4) REGION (Bedrock region) only if nothing else is available — same
# behavior as before, kept so single-region deployments in the
# Bedrock allowlist still work when UserData is skipped (dev/manual).
if [[ -z "${KIROCREW_TG_BOT_TOKEN:-}" && -n "${KIROCREW_TG_BOT_TOKEN_SECRET:-}" ]]; then
_kc_tg_region="${STACK_REGION:-}"
if [[ -z "$_kc_tg_region" ]]; then
_kc_imds_token="$(curl -sf -X PUT http://169.254.169.254/latest/api/token \
-H 'X-aws-ec2-metadata-token-ttl-seconds: 60' 2>/dev/null || true)"
if [[ -n "$_kc_imds_token" ]]; then
_kc_tg_region="$(curl -sf -H "X-aws-ec2-metadata-token: ${_kc_imds_token}" \
http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null || true)"
fi
unset _kc_imds_token
fi
_kc_tg_region="${_kc_tg_region:-${AWS_DEFAULT_REGION:-${REGION:-us-east-1}}}"
_kc_tg_resolved="$(aws secretsmanager get-secret-value \
--secret-id "${KIROCREW_TG_BOT_TOKEN_SECRET}" \
--query SecretString --output text \
--region "${_kc_tg_region}" 2>/dev/null || true)"
if [[ -n "${_kc_tg_resolved}" ]]; then
KIROCREW_TG_BOT_TOKEN="${_kc_tg_resolved}"
else
echo "WARN: could not resolve KirocrewTgBotTokenSecret (${KIROCREW_TG_BOT_TOKEN_SECRET}) in region ${_kc_tg_region} — pack will skip Telegram wiring" >&2
fi
unset _kc_tg_resolved _kc_tg_region
fi

# ── Write pack config JSON ────────────────────────────────────────────────────
PACK_CONFIG="/tmp/loki-pack-config.json"
jq -n \
Expand All @@ -274,6 +334,8 @@ jq -n \
--arg from_secret "$KIRO_FROM_SECRET" \
--arg telegram_bot_token_secret "$TELEGRAM_BOT_TOKEN_SECRET" \
--arg telegram_user "$TELEGRAM_USER" \
--arg telegram_bot_token "${KIROCREW_TG_BOT_TOKEN:-}" \
--arg telegram_user_id "${KIROCREW_TG_USER_ID:-}" \
Comment on lines +337 to +338

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Forward Telegram credentials into the instance bootstrap

For both inspected CloudFormation CLI and Console deployment flows, these variables exist only in the local installer process: build_deploy_params has no corresponding parameters, and deploy/cloudformation/template.yaml neither exports them in UserData nor passes them to bootstrap.sh. Consequently, the remote bootstrap expands both values to empty strings here, so packs/kirocrew/install.sh always skips Telegram wiring even after the wizard reports that the credentials were captured.

Useful? React with 👍 / 👎.

--arg skip_telemetron "$SKIP_TELEMETRON" \
--arg primary "$PRIMARY" \
--arg daily_driver "$DAILY_DRIVER" \
Expand All @@ -286,6 +348,8 @@ jq -n \
"from-secret":$from_secret,
telegram_bot_token_secret:$telegram_bot_token_secret,
telegram_user:$telegram_user,
"telegram-bot-token":$telegram_bot_token,
"telegram-user-id":$telegram_user_id,
"skip-telemetron":$skip_telemetron,
primary:$primary,
"daily-driver":$daily_driver,
Expand Down
58 changes: 57 additions & 1 deletion deploy/cloudformation/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Metadata:
- KiroFromSecret
- TelegramBotTokenSecret
- TelegramUser
- KirocrewTgBotTokenSecret
- KirocrewTgUserId
- Primary
- DailyDriver
- CodexModel
Expand Down Expand Up @@ -272,6 +274,17 @@ Parameters:
Default: ''
Description: "Telegram username for bot pairing (roundhouse pack only, without @ prefix)."

KirocrewTgBotTokenSecret:
Type: String
Default: ''
Description: "AWS Secrets Manager secret id/arn holding the KiroCrew Telegram bot token (kirocrew pack only). The instance resolves the secret at bootstrap time via its IAM role — the token itself is never embedded in UserData."

KirocrewTgUserId:
Type: String
Default: ''
Description: "KiroCrew Telegram numeric user ID (kirocrew pack only). Opt-in wiring. Digits only. Not sensitive (same class as TelegramUser)."


Primary:
Type: String
Default: openclaw
Expand Down Expand Up @@ -811,6 +824,32 @@ Resources:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- !If [IsBuilder, 'arn:aws:iam::aws:policy/AdministratorAccess', !Ref 'AWS::NoValue']
- !If [IsAccountAssistant, 'arn:aws:iam::aws:policy/ReadOnlyAccess', !Ref 'AWS::NoValue']
Policies:
# KiroCrew Telegram bot-token secret read access.
# Inline on the role (not a sibling AWS::IAM::Policy) so it is
# attached at role creation — the Instance's DependsOn InstanceProfile
# is then sufficient and there is no race between UserData starting
# and the permission propagating (Codex P1 on 818c0f8).
#
# Installer always passes a plain secret name in the form
# /lowkey/<env>/kirocrew-telegram-bot-token, so the Resource is a
# single well-formed ARN built via !Sub — never mix a plain name
# with a pre-built ARN (Codex P1 on 818c0f8: doing both produced
# arn:...:secret:arn:aws:... which is malformed).
#
# Empty parameter → empty policy statement action; harmless because
# the resource pattern won't match anything, but the statement is
# present on the role so no follow-up IAM propagation is required.
- PolicyName: !Sub '${EnvironmentName}-kirocrew-tg-secret-read'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: ReadKirocrewTelegramBotToken
Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource: !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${KirocrewTgBotTokenSecret}*'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scope the policy only when a Telegram secret is set

When KirocrewTgBotTokenSecret keeps its default empty value—which is every deployment that skips this opt-in—this renders as arn:aws:secretsmanager:<region>:<account>:secret:*, granting the instance role GetSecretValue for every secret. The matching empty substitution in AccountAssistantDenyPolicy at line 929 also exempts every secret from its explicit deny, so an account_assistant can read all account secrets despite that profile's isolation policy; condition both the allow and deny exemption on a nonempty parameter.

Useful? React with 👍 / 👎.

Tags:
- Key: Name
Value: !Sub '${EnvironmentName}-role'
Expand Down Expand Up @@ -881,7 +920,13 @@ Resources:
Action:
- secretsmanager:GetSecretValue
- secretsmanager:GetResourcePolicy
Resource: '*'
NotResource:
# Bootstrap needs to read the KiroCrew Telegram bot-token secret
# (installer-managed, plain name /lowkey/<env>/kirocrew-telegram-bot-token)
# even under account_assistant. Explicit Deny always wins over the
# matching Allow on InstanceRole, so exempt this one ARN pattern
# via NotResource (Codex P1 on 818c0f8).
- !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${KirocrewTgBotTokenSecret}*'
- Sid: DenyS3ObjectAccess
Effect: Deny
Action:
Expand Down Expand Up @@ -1849,6 +1894,13 @@ Resources:
# Export all params as env vars for the bootstrap script
export ACCT_ID="${AWS::AccountId}"
export REGION="${AWS::Region}"
# STACK_REGION is the CFN stack / deployment region — preserved
# separately from BEDROCK_REGION and from bootstrap.sh's --region
# (which is repurposed for the Bedrock region on Bedrock-model deploys).
# Used by resolvers that must hit AWS APIs in the same region where
# the installer created resources (e.g. Secrets Manager for the
# KiroCrew Telegram bot token). Codex P1 on 79c93b6.
export STACK_REGION="${AWS::Region}"
export STACK_NAME="${AWS::StackName}"
export DEFAULT_MODEL="${DefaultModel}"
export BEDROCK_REGION="${BedrockRegion}"
Expand All @@ -1861,6 +1913,8 @@ Resources:
export KIRO_FROM_SECRET="${KiroFromSecret}"
export TELEGRAM_BOT_TOKEN_SECRET="${TelegramBotTokenSecret}"
export TELEGRAM_USER="${TelegramUser}"
export KIROCREW_TG_BOT_TOKEN_SECRET="${KirocrewTgBotTokenSecret}"
export KIROCREW_TG_USER_ID="${KirocrewTgUserId}"
export PACK_NAME="${PackName}"
export PROFILE_NAME="${ProfileName}"
# Publish failure to SSM and signal CFN on any error
Expand Down Expand Up @@ -1905,6 +1959,8 @@ Resources:
--kiro-from-secret "$KIRO_FROM_SECRET" \
--telegram-bot-token-secret "$TELEGRAM_BOT_TOKEN_SECRET" \
--telegram-user "$TELEGRAM_USER" \
--kirocrew-tg-bot-token-secret "$KIROCREW_TG_BOT_TOKEN_SECRET" \
--kirocrew-tg-user-id "$KIROCREW_TG_USER_ID" \
--primary "${Primary}" \
--daily-driver "${DailyDriver}" \
--codex-model "${CodexModel}"
Expand Down
142 changes: 141 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2390,7 +2390,7 @@ collect_security_config() {
# Parameter source-of-truth: single mapping for CFN Console and CFN CLI
# ============================================================================
# ⚠ KEEP THESE TWO ARRAYS IN SYNC — same order, same count
PARAM_CFN_NAMES=(EnvironmentName PackName ProfileName InstanceType DefaultModel ModelMode BedrockRegion LokiWatermark EnableBedrockForm EnableSecurityHub EnableGuardDuty EnableInspector EnableAccessAnalyzer EnableConfigRecorder ExistingVpcId ExistingSubnetId ExistingSubnetId2 RepoBranch KiroFromSecret TelegramBotTokenSecret TelegramUser Primary DailyDriver CodexModel EnableWebUIAuth WebUIAdminEmail EdgeLambdaVersionArn EdgeConfigSecretName EdgeConfigSecretArn SigningKeySecretName SigningKeySecretArn)
PARAM_CFN_NAMES=(EnvironmentName PackName ProfileName InstanceType DefaultModel ModelMode BedrockRegion LokiWatermark EnableBedrockForm EnableSecurityHub EnableGuardDuty EnableInspector EnableAccessAnalyzer EnableConfigRecorder ExistingVpcId ExistingSubnetId ExistingSubnetId2 RepoBranch KiroFromSecret TelegramBotTokenSecret TelegramUser Primary DailyDriver CodexModel EnableWebUIAuth WebUIAdminEmail EdgeLambdaVersionArn EdgeConfigSecretName EdgeConfigSecretArn SigningKeySecretName SigningKeySecretArn KirocrewTgBotTokenSecret KirocrewTgUserId)
PARAM_VALUES=() # populated by build_deploy_params()

# Per-pack default model (passed to CFN DefaultModel / bootstrap.sh --model).
Expand Down Expand Up @@ -2457,6 +2457,8 @@ build_deploy_params() {
"${EDGE_CONFIG_SECRET_ARN:-}"
"${SIGNING_KEY_SECRET_NAME:-}"
"${SIGNING_KEY_SECRET_ARN:-}"
"${KIROCREW_TG_BOT_TOKEN_SECRET:-}"
"${KIROCREW_TG_USER_ID:-}"
)
# Validate parallel arrays are in sync
[[ ${#PARAM_CFN_NAMES[@]} -eq ${#PARAM_VALUES[@]} ]] \
Expand Down Expand Up @@ -3364,6 +3366,109 @@ run_config_and_review() {
build_deploy_params
fi

# Pack-specific: kirocrew Telegram channel setup
# Prompts the operator for a bot token + numeric user ID, both with retry
# loops that accept 'skip' or empty input to bail. Values flow to the pack
# via PACK_CONFIG (bootstrap.sh -> pack_config_get), which then writes:
# - bot token -> ~/.kiro/crew/.env (as TELEGRAM_BOT_TOKEN=...)
# - user ID -> ~/.kiro/crew/config.json ('telegram.allowed_user_ids')
# - enabled -> ~/.kiro/crew/config.json ('telegram.enabled' = true)
# Both writes are gated on both values being present.
KIROCREW_TG_BOT_TOKEN=""
KIROCREW_TG_BOT_TOKEN_SECRET=""
KIROCREW_TG_USER_ID=""
Comment on lines +3377 to +3379

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rebuild parameters after collecting Telegram credentials

When an interactive KiroCrew install opts into Telegram, the normal CloudFormation Console flow and the default CLI flow still deploy empty values because build_deploy_params ran at line 3335 before these assignments and is never called again for KiroCrew; only the unrelated WebUI-auth CLI path happens to rebuild later. Fresh evidence in this revision is that the KirocrewTg* CloudFormation parameters now exist, but PARAM_VALUES remains the stale array created before the prompt, so the wizard reports success while the pack receives no credentials.

Useful? React with 👍 / 👎.

if [[ "${PACK_NAME:-}" == "kirocrew" && "$AUTO_YES" != true ]]; then
if confirm "Connect KiroCrew to Telegram? (chat with your agent from your phone)" "default_no"; then
echo ""
echo -e " ${BOLD}Two things to get from Telegram before continuing:${NC}"
echo ""
echo -e " 1. ${BOLD}Create a bot${NC} — message ${CYAN}@BotFather${NC}, send ${BOLD}/newbot${NC},"
echo -e " and follow the prompts. You'll get a token like ${DIM}123456789:AA…${NC}"
echo ""
echo -e " 2. ${BOLD}Find your user ID${NC} — message ${CYAN}@userinfobot${NC}; it replies with"
echo -e " your number (e.g. ${DIM}123456789${NC}). That's the only account your"
echo -e " bot will answer."
echo ""
echo -e " ${DIM}Press Enter (empty) or type 'skip' at either prompt to skip Telegram setup.${NC}"
echo ""

# --- Bot token: retry loop, format ^[0-9]+:[A-Za-z0-9_-]+$ ---
_KC_TG_ATTEMPTS=0
_KC_TG_MAX=5
while (( _KC_TG_ATTEMPTS < _KC_TG_MAX )); do
_KC_TG_ATTEMPTS=$((_KC_TG_ATTEMPTS + 1))
_KC_TG_INPUT=""
prompt_secret "Telegram bot token" _KC_TG_INPUT ""
_KC_TG_INPUT_LC="$(printf '%s' "$_KC_TG_INPUT" | tr '[:upper:]' '[:lower:]')"
if [[ -z "$_KC_TG_INPUT" ]] || [[ "$_KC_TG_INPUT_LC" == "skip" ]]; then
KIROCREW_TG_BOT_TOKEN=""
break
fi
if [[ "$_KC_TG_INPUT" =~ ^[0-9]+:[A-Za-z0-9_-]+$ ]]; then
KIROCREW_TG_BOT_TOKEN="$_KC_TG_INPUT"
break
fi
_KC_TG_REMAINING=$((_KC_TG_MAX - _KC_TG_ATTEMPTS))
if (( _KC_TG_REMAINING > 0 )); then
warn "Bot token doesn't match expected format (digits:alphanumerics, e.g. 123456789:AA-...). ${_KC_TG_REMAINING} attempt(s) left. Press Enter or type 'skip' to skip."
else
warn "Bot token invalid after ${_KC_TG_MAX} attempts. Skipping Telegram setup."
KIROCREW_TG_BOT_TOKEN=""
fi
done

# --- User ID: retry loop, all-digit (Telegram user IDs are 32-bit+ ints) ---
if [[ -n "$KIROCREW_TG_BOT_TOKEN" ]]; then
_KC_TG_ATTEMPTS=0
while (( _KC_TG_ATTEMPTS < _KC_TG_MAX )); do
_KC_TG_ATTEMPTS=$((_KC_TG_ATTEMPTS + 1))
_KC_TG_INPUT=""
prompt "Your Telegram user ID (numeric)" _KC_TG_INPUT ""
_KC_TG_INPUT_LC="$(printf '%s' "$_KC_TG_INPUT" | tr '[:upper:]' '[:lower:]')"
if [[ -z "$_KC_TG_INPUT" ]] || [[ "$_KC_TG_INPUT_LC" == "skip" ]]; then
KIROCREW_TG_USER_ID=""
KIROCREW_TG_BOT_TOKEN="" # neither goes without both
break
fi
if [[ "$_KC_TG_INPUT" =~ ^[0-9]{5,15}$ ]]; then
KIROCREW_TG_USER_ID="$_KC_TG_INPUT"
break
fi
_KC_TG_REMAINING=$((_KC_TG_MAX - _KC_TG_ATTEMPTS))
if (( _KC_TG_REMAINING > 0 )); then
warn "User ID must be all digits (5-15 chars). ${_KC_TG_REMAINING} attempt(s) left. Press Enter or type 'skip' to skip."
else
warn "User ID invalid after ${_KC_TG_MAX} attempts. Skipping Telegram setup."
KIROCREW_TG_USER_ID=""
KIROCREW_TG_BOT_TOKEN=""
fi
done
fi
unset _KC_TG_ATTEMPTS _KC_TG_MAX _KC_TG_INPUT _KC_TG_INPUT_LC _KC_TG_REMAINING

if [[ -n "$KIROCREW_TG_BOT_TOKEN" && -n "$KIROCREW_TG_USER_ID" ]]; then
# Codex P2 (818c0f8): defer the Secrets Manager write until AFTER
# show_summary confirms the deploy — same pattern Roundhouse and
# kiro-cli already use. Here we only stash the token in a shell var
# and pre-compute the secret name so build_deploy_params can emit
# the correct CFN parameter value; the actual create-secret /
# put-secret-value call happens in the post-confirmation block
# (search for _KC_TG_SECRET_NAME below).
_KC_TG_SECRET_NAME="/lowkey/${ENV_NAME}/kirocrew-telegram-bot-token"
KIROCREW_TG_BOT_TOKEN_SECRET="$_KC_TG_SECRET_NAME"
# NOTE: KIROCREW_TG_BOT_TOKEN stays populated in-memory; it is
# written to Secrets Manager and cleared only after the operator
# confirms deployment.
ok "Telegram setup captured (secret ${KIROCREW_TG_BOT_TOKEN_SECRET} + user ID ${KIROCREW_TG_USER_ID}); token will be stored after you confirm deployment."
# Rebuild PARAM_VALUES so the KirocrewTg* fields are no longer stale
# (Codex P1 on 4c9d1fd: build_deploy_params ran before this wizard).
build_deploy_params
else
info "Skipping Telegram setup — you can enable it later by editing ~/.kiro/crew/config.json on the instance."
fi
fi
fi

# Pack-specific: kiro-cli/kirocrew interactive API key for headless mode
if [[ "${PACK_NAME:-}" == "kiro-cli" || "${PACK_NAME:-}" == "kirocrew" ]]; then
if [[ -z "${KIRO_FROM_SECRET:-}" && "$AUTO_YES" != true ]]; then
Expand Down Expand Up @@ -3505,6 +3610,41 @@ main() {
_telem_pack_selected 2>/dev/null || true
_telem_method_selected 2>/dev/null || true

# KiroCrew Telegram: save bot token to Secrets Manager (deferred until after user confirmation)
# Codex P2 on 818c0f8: the wizard used to write this before show_summary,
# which orphaned a secret if the operator cancelled at the summary or
# chose "Change settings". Matches the Roundhouse + Kiro API-key pattern.
if [[ -n "${KIROCREW_TG_BOT_TOKEN:-}" && -n "${_KC_TG_SECRET_NAME:-}" ]]; then
info "Storing KiroCrew Telegram bot token in Secrets Manager: ${_KC_TG_SECRET_NAME}"
local kc_tg_token_file
kc_tg_token_file=$(mktemp /tmp/lowkey-kc-tg-token.XXXXXX)
chmod 600 "$kc_tg_token_file"
printf '%s' "$KIROCREW_TG_BOT_TOKEN" > "$kc_tg_token_file"
# Restore if in pending-deletion state (same guard as Roundhouse).
aws secretsmanager restore-secret --secret-id "$_KC_TG_SECRET_NAME" --region "$DEPLOY_REGION" >/dev/null 2>&1 || true
local kc_tg_sm_err=""
if kc_tg_sm_err=$(aws secretsmanager create-secret \
--name "$_KC_TG_SECRET_NAME" \
--secret-string "file://${kc_tg_token_file}" \
--description "KiroCrew Telegram bot token for ${ENV_NAME} (managed by lowkey install.sh)" \
--tags "Key=loki:managed,Value=true" "Key=loki:pack,Value=kirocrew" "Key=loki:env,Value=${ENV_NAME}" \
--region "$DEPLOY_REGION" 2>&1); then
ok "KiroCrew Telegram token saved to Secrets Manager"
elif kc_tg_sm_err=$(aws secretsmanager put-secret-value \
--secret-id "$_KC_TG_SECRET_NAME" \
--secret-string "file://${kc_tg_token_file}" \
--region "$DEPLOY_REGION" 2>&1); then
ok "KiroCrew Telegram token updated in Secrets Manager"
else
rm -f "$kc_tg_token_file"
fail "Failed to save KiroCrew Telegram bot token to Secrets Manager: ${kc_tg_sm_err}"
fi
rm -f "$kc_tg_token_file"
# Scrub the plaintext token from installer state — CFN only needs the arn/id.
KIROCREW_TG_BOT_TOKEN=""
unset KIROCREW_TG_BOT_TOKEN _KC_TG_SECRET_NAME
fi

# Roundhouse: save bot token to Secrets Manager (deferred until after user confirmation)
if [[ -n "${_RH_BOT_TOKEN:-}" && -n "${_RH_SECRET_NAME:-}" ]]; then
info "Storing bot token in Secrets Manager: ${_RH_SECRET_NAME}"
Expand Down
Loading