Releases: iblai/infra-cli
Releases · iblai/infra-cli
Release list
v1.13.0
Summary
Adds Google Cloud as a first-class provider for single-server deployments, alongside AWS — same wizard, same .env flows, same setup step.
- Terraform stack (
templates/gcp/single-server/): VPC + regional subnet, firewall rules (SSH restricted to the operator IP; health checks from Google's probe ranges), Compute Engine VM (Ubuntu 22.04, metadata SSH keys), unmanaged instance group behind a global external Application Load Balancer with a static IP, Google-managed SSL certificate (all platform subdomains, async validation), Cloud DNS A-records (existing zone auto-detected, or created with nameservers printed for registrar delegation) - Health check probes the LMS heartbeat with a
learn.<domain>Host header — GCP accepts only a literal 200, and probing/hits the platform nginx catch-all's 301, marking the backend UNHEALTHY and serving 503 "no healthy upstream" for everything (hit live on the first bootstrap; designed out + regression-tested) CloudProvideraxis onInfraConfig(defaultaws; existing state files deserialize unchanged) +GCPCredentials(ADC or service-account key); runner dispatches templates/tfvars/env on itPROVIDER=gcpnon-interactive path +.env.provision.gcp.example;iblai infra permissions --provider gcp [--check]- One version prompt: setup/resetup ask for the prod-images release tag and resolve the matching
iblai-cli-opstag from its[tool.uv.sources]pin (uv ignores that table on git-URL installs, so the explicit install stays — only the question goes away). Stale3.19.0default removed from every input layer - Fixes: setup prompts crashed on GCP-provisioned states (no AWS credential block);
iblai infra wafnow cleanly rejects non-AWS stacks; GCP env builder surfaces validation errors instead of tracebacks - Docs: README rewrite (quick start, both clouds, ADC vs service-account walkthroughs, sample
.envindex) + step-by-stepdocs/GCP.md - Object storage remains on AWS S3 by design — operators supply credentials at the setup step (documented)
Test plan
- 750 unit tests passing (GCP provider/runner/prompts/env suites mirror the AWS patterns)
terraform validate+fmtclean on the GCP templates- Verified live end-to-end on a real GCP project: provision (37 resources) → full 16-role bootstrap → platform serving over HTTPS behind the LB → teardown clean
🤖 Generated with Claude Code
v1.12.0
[1.12.0] — 2026-06-26
Fixed
- TimescaleDB extension now created during DM bootstrap — the DM postgres image ships TimescaleDB preloaded (
shared_preload_libraries=timescaledb), but the flow only ever ranCREATE EXTENSION vector;timescaledbwas never created, sosetup_timescale_views --full-setup(which ran underignore_errors: true) silently degraded and analytics hypertables were never built. Theibl_dmrole now runsCREATE EXTENSION IF NOT EXISTS timescaledbright after pgvector (idempotent, postgres-superuser). Confirmed against a field environment whose DB had onlyplpgsql+vector. - Microsoft SSO now uses the standard
azuread-oauth2backend — themicrosoft_sso_configrole derived the providerbackend_name(and the/auth/login+/auth/completeSSO URLs) fromplatform_name(e.g.main-oauth2), which is not a registered Azure AD social-auth backend, so sign-in never completed and operators had to hand-fix the LMSOAuth2ProviderConfig. All backend references —OAuth2ProviderConfig.backend_name, theIBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKENDblock (IBL_OAUTH_SSO_NAME/TRACKED_PROVIDERS),other_settings.backend_uri, andIBL_SPA.AUTH.IBL_DIRECT_SSO_URL— now use the constantazuread-oauth2, matching the provider slug and the Azure-registered redirect URI.other_settings.platform_keystill carries the tenantplatform_name. setup_timescale_viewsfailures now surface — replaced the blanketignore_errors: trueon the data_seeding "Setup TimescaleDB views" task with aregister+failed_when(fails on a genuine Python traceback, tolerates benign non-zero exits / idempotent re-runs) and a debug that prints the command output.
Added
IBL_DM.ENABLE_RBAC_GROUP_MANAGEMENT=trueset by default — added to theibl_platform"Enable DM RBAC" block alongside the existingENABLE_RBAC/ENABLE_RBAC_SEEDING/ENABLE_TIMESCALEDBdefaults (previously had to be set by hand).- Azure AD redirect-URI prerequisite documented —
.env.setup.exampleand themicrosoft_sso_configend-of-run confirmation now spell out the exact redirect URI the client must register in their Azure AD app:https://learn.<BASE_DOMAIN>/auth/complete/azuread-oauth2/.
v1.11.0
[1.11.0] — 2026-06-01
Added
- Optional AWS WAFv2 on the single-server ALB — opt-in at provision time via the wizard (a new sub-step of "Domain & Certificates" — default off),
provision-env(ENABLE_WAF=true+WAF_ALLOWED_IPS=…in the.env), orlaunch/launch-env(--enable-waf+--waf-allowed-ips, or matching env keys). Attaches a Regional WAFv2 Web ACL to the ALB with rules tuned for ibl.ai's subdomain layout: admin-only allow rules (gated on an operator IP allowlist) for DM Swagger UI, edX Studio (CMS), Django/admin/, and DM/data; public allow rule forlearn.<base>andapps.learn.<base>; six AWS managed rule groups (IpReputation, KnownBadInputs, Common, SQLi, WordPress, PHP); and a path-traversal block for.git/.env/.htaccess/.svn/.hg/.DS_Store. Total estimated WCU ≈ 1355 (under the 1500 default). Allowlist accepts both bare IPs (auto-suffixed/32) and CIDR. iblai infra wafpost-provision subgroup — toggle WAFv2 on an already-provisioned single-server stack without re-running the wizard. Four commands:enable [<name>](interactive; on a project that already has WAF on, warns and prompts to update the allowlist with current IPs pre-filled),enable-env [<name>] -f .env(non-interactive, readsWAF_ALLOWED_IPS),disable <name> [--yes](Y/N confirm by default;--yesfor CI; removes the Web ACL + IPSet + association, leaves the ALB intact),status [<name>](table of all WAF-eligible projects with no arg, detail panel with one). Rejects multi-server, call-server, bootstrap, and non-createdprojects up-front with a clear error. Subgroup module lives atsrc/iblai_infra/features/waf.py; thefeatures/package docstring documents the pattern for the next optional-feature toggles (SMTP, Stripe, SSO providers) so they can drop in with the sameenable / enable-env / disable / statusshape.TerraformRunner.reapply()— shared helper for re-running Terraform on an existing workspace with the lateststate.config. Re-copies.tftemplates (so template fixes propagate), reads the existingterraform.tfvarsto pin the originalbucket_suffix(prevents accidental S3 bucket renames once the date-stamp window has rolled over), regenerates the rest of tfvars fromstate.config, then runsinit→plan→apply. Returns parsed outputs. Used by both the newiblai infra waf <action>commands and the refactorediblai infra retry.- WAFv2 entries in
REQUIRED_IAM_POLICY+ awafv2:ListWebACLssmoke check incheck_permissions()soiblai infra permissions [--check]surfaces WAF readiness up-front instead of failing mid-apply.
Changed
_generate_tfvars(self, bucket_suffix: str | None = None)— accepts an optional pinned suffix. WhenNone(today's default for firstsetup()), resolves the suffix from AWS as before. When provided, uses the pinned value. Load-bearing change for the newreapply()helper.iblai infra retrynow usesTerraformRunner.reapply()instead of inlining template-copy + init/plan/apply, removing a drift point with the new WAF subgroup. Behaviour is unchanged for operators: the existing failure-recovery guards and Route 53 CNAME conflict cleanup still run.
v1.10.1
v1.10.0
[1.10.0] — 2026-05-20
Added
ibl_tenant_platformansible role — launches a tenantPlatform(Platform + admin User + UserPlatformLink) viarun_launch_stepswhenPLATFORM_NAMEis set to anything other thanmain. NOT a rawPlatform.objects.create()— the state machine fires every after_launch signal (default apps, edX hooks, UserPlatformLink flags). Wired into bothplaybook.yml(setup / setup-env) andlaunch_playbook.yml(launch / launch-env). Skips + logs on re-runs when the tenant already exists. Also writesPLATFORM_NAME=<KEY>(uppercase) at the root of/ibl/config.ymland enforcesPlatform.show_paywall=False+Platform.is_advertising=Falseas defense in depth. Surfaces the generated admin password via theIBLAI_FIXTURE_OUTPUTpipeline — printed once after the Rich Live display tears down, never persisted to disk.- Microsoft SSO writes
IBL_SPA.AUTH—microsoft_sso_confignow also patchesEXTERNAL_IDP_LOGOUT_URLandIBL_DIRECT_SSO_URL(usingmicrosoft_sso_tenant_id, falling back tocommon), then restarts the Auth + Mentor SPAs so the new auth flow takes effect. INSTANCE_RAM_GBhelper + 32 GB memory warning — non-blocking heads-up suggesting 64 GB (e.g.m5.4xlarge/r5.2xlarge) when the operator picks a 32 GB instance. Always shown in the interactive provision wizard andprovision-env; conditional inlaunch/launch-env(only when AI is enabled).- Final
ibl global-proxy reloadadded aspost_tasksin bothplaybook.ymlandlaunch_playbook.yml, so any nginx state touched by SSO roles (edX restarts ingoogle_sso_config/microsoft_sso_config) is reloaded before the playbook exits. RESERVED_ADMIN_USERNAMES+RESERVED_PLATFORM_NAMES—models.pyconstants, surfaced viais_reserved_admin_username()andis_reserved_platform_name()helpers and anInfraConfigmodel_validator.
Changed
- Stripe billing UI off by default —
IBL_SPA.MENTOR.STRIPE_ENABLED=falseandIBL_SPA.MENTOR.ENABLE_ADVERTISING=falseare now written unconditionally byibl_spa(fresh installs) andibl_launch_services(AMI launches). Behavior change: Stripe-using deployments must explicitly flipIBL_SPA.MENTOR.STRIPE_ENABLEDback to'true'post-setup. The previous "always on" SPA flag surfaced billing UI even when Stripe wasn't actually configured. - 100 GB minimum root volume for single / multi server — enforced by Pydantic (
InfraConfigmodel_validator gated onDeploymentType.SINGLE, plusMultiServerConfig.validate_volume_sizes) and matching interactive + CLI + .env input checks. Behavior change: values below 100 GB are now rejected upfront. DefaultComputeConfig.volume_sizebumped 50 → 100. Call-server unchanged (LiveKit only needs ~40 GB). ADMIN_USERNAME=ibl_adminrejected at every input layer — reserved for the SPA OAuth Application owner the platform itself maintains. New default suggestion isplatform_admin. Interactive prompts,.envparsers, and--admin-usernameflag all rejectibl_adminwith a clear reserved-name error. Behavior change: scripted deploys passingADMIN_USERNAME=ibl_adminmust rename.PLATFORM_NAME=mainrejected as an explicit input — unset / blank silently resolves tomain(preserving SSObackend_name=main-oauth2and skipping the tenant launcher). Behavior change: scripted deploys passingPLATFORM_NAME=mainshould drop the line.- README — refreshed against current playbook (16 roles, phase-grouped table), three deployment topologies, sizing guidance, tenant launcher, reserved-name rules. -50 lines net.
Removed
- All references to a specific canonical-client name from comments, docstrings, prompt instructions, error hints, and example .env files. Placeholders:
<client>for monorepo org names,acmefor tenant-key examples.
Fixed
- Slow
_test_ssh()retry-path tests — five tests intests/ansible/test_runner.pyexercise the SSH-retry exhaust path (10 retries × 15 s sleep). They now mocktime.sleepalongside the existingsubprocess.runmock, cutting ~11 minutes off the full suite. Test count: 562 passing in ~1.3 s.
v1.9.0
- New: iblai infra provision-env -f .env — fresh single-server Terraform provision from a .env file, no AMI required (#51)
- New: iblai infra setup-env [] -f .env — non-interactive Ansible bootstrap, supports both provisioned-name and
free-standing-server modes (#53, #54) - New: .env.provision.example and .env.setup.example schema templates
- Fix: ansible-core 2.19 parser error blocking 6 roles (google/microsoft SSO, SMTP, Stripe, data seeding, playwright) — converted
free-form shell: | to dict form - Fix: .env parser now strips inline # comment from unquoted values
- Fix: SMTP_PORT non-numeric values surface a clean error instead of a Python traceback
- Fix: setup-env is idempotent — re-running resumes existing bootstrap state instead of clobbering
- Tests: +55 new unit tests (552 total passing)
- New: iblai infra setup-env [] -f .env — non-interactive Ansible bootstrap, supports both provisioned-name and
v1.7.0
[1.7.0] — 2026-05-06
Added
- Optional Microsoft (Azure AD) SSO setup via a new
microsoft_sso_configansible role. When the operator opts in (Y/N prompt duringiblai infra setup, or--microsoft-sso-client-idforiblai infra launch), the role does two things: (1) patchesIBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKENDin/ibl/config.ymlvia direct Python yaml manipulation (since the block has nested dicts + a list, whichibl config save --setcannot round-trip), runsibl config save, and bounces edX so the new Django settings take effect; (2) creates anOAuth2ProviderConfigrow on the LMS for theazuread-oauth2slug, withbackend_namederived fromplatform_name,sync_learner_profile_data=True, and a Microsoft-specificother_settingsJSON carryingplatform_key,backend_uri, and the Azure AD federatedlogout_url. Idempotent — the heavyibl config save+ edX restart only run when the config block actually differs from the desired state, and theOAuth2ProviderConfigsave usescurrent(slug)to skip when the latest revision already matches SetupConfig.platform_name— top-level field (defaults tomain), prompted at the start of Step 2 (Platform Configuration). Lowercased + stripped on input. Drives both the SSObackend_name(<platform_name>-oauth2) and theother_settings.platform_key. Always populated; the SSO roles read it whether or not their feature flag is enabledSetupConfig.microsoft_sso_*fields —microsoft_sso_enabled,microsoft_sso_client_id,microsoft_sso_client_secret,microsoft_sso_tenant_id,microsoft_sso_organization. Client secret isField(exclude=True)so it never lands instate.json- Launch CLI flags —
--platform-name(defaultmain),--microsoft-sso-client-id(the trigger),--microsoft-sso-client-secret,--microsoft-sso-tenant-id,--microsoft-sso-organization. Same env-var pattern as Stripe / SMTP / Google SSO
v1.5.4
What changed:
- Pin direct runtime dependencies to currently running freeze versions for issue #1633 — updated
pyproject.tomlto exact pins foransible-core==2.19.9,boto3==1.42.97,pydantic==2.13.3,questionary==2.1.1,rich==15.0.0, andtyper==0.25.0, then regenerateduv.lockso lock and install metadata are aligned to the same tested dependency set.
closes: iblai/iblai-platform#1633
v1.3.0
What's Changed:
- resetup command — re-configure existing environments with new domain + rotated secrets
- launch command — non-interactive AMI-based provisioning + platform setup for CI/CD
- launch-env command — launch from .env file with interactive confirmation
- iblai-prod-images integration — CLI ops installed via iblai-images[sumac], all image versions pinned by the package (no more hardcoded tags)
- Database password sync — PostgreSQL and MySQL passwords synced after secret rotation (capture-before-rotate pattern)
- SPA health checks — all SPA launches verified via HTTP with retries
- Ansible progress display — shows current task description instead of just "Running"
- .env.example — template for launch variables with safe placeholders
- Ingress system — pre-provisioned domain endpoint management with lock backends
v1.2.0
[1.2.0] — 2026-03-20
Added
iblai infra bootstrapcommand — set up the IBL platform on any existing server (any cloud, bare metal) without Terraform provisioning- Interactive bootstrap wizard collects server IP, SSH key, domain, image tags, and AWS/GitHub credentials
- Bootstrap projects tracked with
provider="bootstrap"—list,status, anddestroyall work - Destroy guard for bootstrap projects skips Terraform teardown and marks project as destroyed
- "Bootstrap existing server" option in landing screen menu