mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 19:04:02 +00:00
feat(infra): implement multi-environment deployment strategy
- Update CD workflow for branch-based environments (main -> Prod, dev -> Dev) - Support Terraform workspaces for environment isolation - Add manage_runtime_secrets flag to prevent accidental secret destruction - Add infra management and secret setup utility scripts - Prefix GitHub deployer identity with environment name - Synchronize bot environment variables with latest runtime config
This commit is contained in:
44
scripts/ops/import-shared-resources.sh
Executable file
44
scripts/ops/import-shared-resources.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
PROJECT_ID="gen-lang-client-0200379851"
|
||||
REGION="europe-west1"
|
||||
WORKSPACE="${1:-prod}" # Takes first argument, defaults to 'prod'
|
||||
|
||||
# Change directory to terraform folder
|
||||
cd infra/terraform || exit 1
|
||||
|
||||
echo "--- Shared Resource Import Utility ---"
|
||||
echo "Target Project: $PROJECT_ID"
|
||||
echo "Target Workspace: $WORKSPACE"
|
||||
|
||||
# 1. Ensure the workspace exists and is selected
|
||||
terraform workspace select "$WORKSPACE" || terraform workspace new "$WORKSPACE"
|
||||
|
||||
# 2. Construct Resource IDs
|
||||
echo -e "\nConstructing Resource IDs..."
|
||||
REPO_ID="projects/$PROJECT_ID/locations/$REGION/repositories/household-bot"
|
||||
POOL_ID="projects/$PROJECT_ID/locations/global/workloadIdentityPools/github-pool"
|
||||
PROV_ID="projects/$PROJECT_ID/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
|
||||
|
||||
echo "1. Repository Resource ID: $REPO_ID"
|
||||
echo "2. Identity Pool Resource ID: $POOL_ID"
|
||||
echo "3. Provider Resource ID: $PROV_ID"
|
||||
|
||||
# 3. Perform the Imports
|
||||
echo -e "\nStarting Terraform Imports..."
|
||||
|
||||
# Import Repository
|
||||
echo -e "\n--- Importing Artifact Registry ---"
|
||||
terraform import -input=false -var-file="$WORKSPACE.tfvars" google_artifact_registry_repository.containers "$REPO_ID"
|
||||
|
||||
# Import Workload Identity Pool
|
||||
echo -e "\n--- Importing Workload Identity Pool ---"
|
||||
terraform import -input=false -var-file="$WORKSPACE.tfvars" 'google_iam_workload_identity_pool.github[0]' "$POOL_ID"
|
||||
|
||||
# Import Workload Identity Provider
|
||||
echo -e "\n--- Importing Workload Identity Provider ---"
|
||||
terraform import -input=false -var-file="$WORKSPACE.tfvars" 'google_iam_workload_identity_pool_provider.github[0]' "$PROV_ID"
|
||||
|
||||
echo -e "\n--- Import Complete for $WORKSPACE! ---"
|
||||
echo "You can now run: bun run infra:apply:$WORKSPACE"
|
||||
103
scripts/ops/setup-test-secrets.ts
Normal file
103
scripts/ops/setup-test-secrets.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { $ } from 'bun'
|
||||
|
||||
const PROJECT_ID = 'gen-lang-client-0200379851'
|
||||
|
||||
async function secretExists(name: string): Promise<boolean> {
|
||||
const result =
|
||||
(await $`gcloud secrets describe ${name} --project=${PROJECT_ID}`.quiet().exitCode) === 0
|
||||
return result
|
||||
}
|
||||
|
||||
async function createSecret(name: string, value: string) {
|
||||
console.log(`\n[Checking] ${name}...`)
|
||||
if (await secretExists(name)) {
|
||||
console.log(`[Skipping] ${name} already exists. If you want to change it, use the GCP console.`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[Creating] ${name} for the first time...`)
|
||||
await $`echo -n ${value} | gcloud secrets create ${name} --data-file=- --replication-policy="automatic" --project=${PROJECT_ID}`.quiet()
|
||||
console.log(`[Success] ${name} is ready.`)
|
||||
} catch (err) {
|
||||
console.error(`[Error] Failed to setup ${name}:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('--- Production & Test Environment Secret Setup ---')
|
||||
console.log(`Target Project: ${PROJECT_ID}`)
|
||||
|
||||
// 1. PRODUCTION Bot Token
|
||||
let prodBotToken = ''
|
||||
if (!(await secretExists('telegram-bot-token'))) {
|
||||
prodBotToken = prompt('1. Enter your PRODUCTION Telegram Bot Token (the original one):') || ''
|
||||
}
|
||||
|
||||
// 2. PRODUCTION Database URL
|
||||
let prodDbUrl = ''
|
||||
if (!(await secretExists('database-url'))) {
|
||||
prodDbUrl = prompt('2. Enter your PRODUCTION Supabase DATABASE_URL (for public schema):') || ''
|
||||
}
|
||||
|
||||
// 3. TEST Bot Token
|
||||
let testBotToken = ''
|
||||
if (!(await secretExists('telegram-bot-token-test'))) {
|
||||
testBotToken = prompt('3. Enter your TEST Telegram Bot Token (from @BotFather):') || ''
|
||||
}
|
||||
|
||||
// 4. TEST Database URL (Derived from prod if not exists)
|
||||
let testDbUrlPrompt = ''
|
||||
if (!(await secretExists('database-url-test'))) {
|
||||
testDbUrlPrompt =
|
||||
prompt(
|
||||
'4. Enter your TEST Supabase DATABASE_URL (or leave empty to reuse prod with ?options=-csearch_path=test):'
|
||||
) || ''
|
||||
}
|
||||
|
||||
// 5. OpenAI API Key (Shared)
|
||||
let openaiKey = ''
|
||||
if (!(await secretExists('openai-api-key'))) {
|
||||
openaiKey = prompt('5. Enter your OpenAI API Key:') || ''
|
||||
}
|
||||
|
||||
// Logic for test DB URL
|
||||
const testDbUrl =
|
||||
testDbUrlPrompt ||
|
||||
(prodDbUrl &&
|
||||
(prodDbUrl.includes('?')
|
||||
? `${prodDbUrl}&options=-csearch_path%3Dtest`
|
||||
: `${prodDbUrl}?options=-csearch_path%3Dtest`))
|
||||
|
||||
// Logic for prod DB URL
|
||||
const finalProdDbUrl =
|
||||
prodDbUrl &&
|
||||
(prodDbUrl.includes('?')
|
||||
? `${prodDbUrl}&options=-csearch_path%3Dpublic`
|
||||
: `${prodDbUrl}?options=-csearch_path%3Dpublic`)
|
||||
|
||||
// Generate random secrets (Always safe to recreate if missing)
|
||||
const webhookSecret = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64')
|
||||
const schedulerSecret = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64')
|
||||
|
||||
console.log('\nStarting GCP operations...')
|
||||
|
||||
if (prodBotToken) await createSecret('telegram-bot-token', prodBotToken.trim())
|
||||
if (finalProdDbUrl) await createSecret('database-url', finalProdDbUrl.trim())
|
||||
if (testBotToken) await createSecret('telegram-bot-token-test', testBotToken.trim())
|
||||
if (testDbUrl) await createSecret('database-url-test', testDbUrl.trim())
|
||||
if (openaiKey) await createSecret('openai-api-key', openaiKey.trim())
|
||||
|
||||
// Create unique secrets per environment if missing
|
||||
await createSecret('telegram-webhook-secret-test', webhookSecret)
|
||||
await createSecret('scheduler-shared-secret-test', schedulerSecret)
|
||||
await createSecret(
|
||||
'telegram-webhook-secret',
|
||||
Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64')
|
||||
)
|
||||
await createSecret(
|
||||
'scheduler-shared-secret',
|
||||
Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('base64')
|
||||
)
|
||||
|
||||
console.log('\n--- Setup Complete! ---')
|
||||
console.log('You can now run the import commands and then infra:apply:prod')
|
||||
Reference in New Issue
Block a user