diff --git a/deploy/bootstrap.sh b/deploy/bootstrap.sh index 3f30641..630b045 100755 --- a/deploy/bootstrap.sh +++ b/deploy/bootstrap.sh @@ -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 @@ -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" @@ -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 \ @@ -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:-}" \ --arg skip_telemetron "$SKIP_TELEMETRON" \ --arg primary "$PRIMARY" \ --arg daily_driver "$DAILY_DRIVER" \ @@ -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, diff --git a/deploy/cloudformation/template.yaml b/deploy/cloudformation/template.yaml index 0ff8566..3179f3a 100644 --- a/deploy/cloudformation/template.yaml +++ b/deploy/cloudformation/template.yaml @@ -26,6 +26,8 @@ Metadata: - KiroFromSecret - TelegramBotTokenSecret - TelegramUser + - KirocrewTgBotTokenSecret + - KirocrewTgUserId - Primary - DailyDriver - CodexModel @@ -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 @@ -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//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}*' Tags: - Key: Name Value: !Sub '${EnvironmentName}-role' @@ -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//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: @@ -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}" @@ -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 @@ -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}" diff --git a/install.sh b/install.sh index 1316f7c..f1d62d8 100755 --- a/install.sh +++ b/install.sh @@ -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). @@ -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[@]} ]] \ @@ -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="" + 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 @@ -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}" diff --git a/packs/kirocrew/install.sh b/packs/kirocrew/install.sh index 12f0d69..c2ea048 100755 --- a/packs/kirocrew/install.sh +++ b/packs/kirocrew/install.sh @@ -35,6 +35,8 @@ PACK_ARG_GATEWAY_PORT="$(pack_config_get gateway-port "5476")" PACK_ARG_START_GATEWAY="$(pack_config_get start-gateway "true")" PACK_ARG_KIROCREW_HOME="$(pack_config_get kirocrew-home "")" PACK_ARG_PROFILE="$(pack_config_get profile "")" +PACK_ARG_TG_BOT_TOKEN="$(pack_config_get telegram-bot-token "")" +PACK_ARG_TG_USER_ID="$(pack_config_get telegram-user-id "")" # ── Help ────────────────────────────────────────────────────────────────────── usage() { @@ -712,6 +714,58 @@ if [[ "${PACK_ARG_PROFILE:-}" == "builder" ]]; then chmod 600 "${KIROCREW_DENIED_FILE}" ok "denied_commands.json written to ${KIROCREW_DENIED_FILE} (builder profile, disable_all=true, merge-preserving)" fi + +# Step 15c: Telegram channel wiring (opt-in via wizard) +# When the operator opted in during the pre-deploy wizard, PACK_CONFIG carries +# telegram-bot-token and telegram-user-id. Write both: +# - TELEGRAM_BOT_TOKEN= to ~/.kiro/crew/.env (env var; kirocrew +# prefers this over the config.bot_token fallback so the secret stays out +# of config.json — see docs/telegram-integration.md) +# - telegram.enabled = true, telegram.allowed_user_ids = [] merged +# into ~/.kiro/crew/config.json via jq (upgrade-safe: preserves other keys) +# Done BEFORE the gateway starts so it picks up the channel on first boot. +if [[ -n "${PACK_ARG_TG_BOT_TOKEN:-}" && -n "${PACK_ARG_TG_USER_ID:-}" ]]; then + step "Wiring KiroCrew Telegram channel (bot token + allowed user ID)" + KIRO_USER="${KIRO_USER:-ec2-user}" + KIRO_USER_HOME="$(getent passwd "${KIRO_USER}" | cut -d: -f6 2>/dev/null || echo "/home/${KIRO_USER}")" + KIROCREW_ENV_DIR="${KIROCREW_HOME:-${KIRO_USER_HOME}/.kiro/crew}" + KIROCREW_ENV_FILE="${KIROCREW_ENV_DIR}/.env" + KIROCREW_MAIN_CFG="${KIROCREW_ENV_DIR}/config.json" + + # 1) TELEGRAM_BOT_TOKEN in the .env file (idempotent: strip any prior line first) + ( umask 077 + mkdir -p "${KIROCREW_ENV_DIR}" + if [[ -f "${KIROCREW_ENV_FILE}" ]]; then + grep -v '^TELEGRAM_BOT_TOKEN=' "${KIROCREW_ENV_FILE}" > "${KIROCREW_ENV_FILE}.tmp" 2>/dev/null || true + mv "${KIROCREW_ENV_FILE}.tmp" "${KIROCREW_ENV_FILE}" + fi + printf 'TELEGRAM_BOT_TOKEN=%s\n' "${PACK_ARG_TG_BOT_TOKEN}" >> "${KIROCREW_ENV_FILE}" + ) + chmod 600 "${KIROCREW_ENV_FILE}" + + # 2) Merge telegram.enabled + allowed_user_ids into config.json + # User IDs are numeric per Telegram / KiroCrew (allowed_user_ids: [123456789]). + if [[ -f "${KIROCREW_MAIN_CFG}" ]]; then + tmp_cfg="$(mktemp)" + if jq --argjson uid "${PACK_ARG_TG_USER_ID}" \ + '.telegram = ((.telegram // {}) + {enabled: true, allowed_user_ids: [$uid]})' \ + "${KIROCREW_MAIN_CFG}" > "${tmp_cfg}" 2>/dev/null; then + mv "${tmp_cfg}" "${KIROCREW_MAIN_CFG}" + else + rm -f "${tmp_cfg}" + warn "jq merge failed on ${KIROCREW_MAIN_CFG}; writing telegram-only config as fallback (existing keys will be lost)" + printf '{\n "telegram": { "enabled": true, "allowed_user_ids": [%s] }\n}\n' "${PACK_ARG_TG_USER_ID}" > "${KIROCREW_MAIN_CFG}" + fi + else + # First-boot: no config.json yet. Kirocrew will merge this with its defaults. + printf '{\n "telegram": { "enabled": true, "allowed_user_ids": [%s] }\n}\n' "${PACK_ARG_TG_USER_ID}" > "${KIROCREW_MAIN_CFG}" + fi + chown -R "${KIRO_USER}:${KIRO_USER}" "${KIROCREW_ENV_DIR}" 2>/dev/null || true + chmod 600 "${KIROCREW_MAIN_CFG}" + ok "Telegram wired: bot token in ${KIROCREW_ENV_FILE}, allowed_user_ids=[${PACK_ARG_TG_USER_ID}] in ${KIROCREW_MAIN_CFG}" +else + [[ -n "${PACK_ARG_TG_BOT_TOKEN:-}${PACK_ARG_TG_USER_ID:-}" ]] && warn "Telegram: got one of (bot token, user ID) but not both; skipping wiring." +fi # ── Step 16: Install systemd service ───────────────────────────────────────── if [[ "${START_GATEWAY}" == "true" ]]; then step "Installing kirocrew-gateway systemd service"