Skip to content

::once

once is a BigConfig package for ONCE. This BigConfig package is an infrastructure automation tool that simplifies the provisioning and configuration of cloud resources using OpenTofu and Ansible. The audience is the vibe coder who wants to deploy his vibe coded application with a “one-click” experience.

It is built on top of big-config, leveraging its workflow and configuration management capabilities.

  • End-to-End Orchestration: A seamless six-stage workflow:
    1. Infrastructure: Provisioning with OpenTofu.
    2. SMTP: Email infrastructure with OpenTofu (Resend).
    3. DNS: Domain configuration with OpenTofu (Cloudflare), including automatic SMTP records.
    4. SMTP Post-Verification: Finalizing SMTP setup (e.g., domain verification) with OpenTofu.
    5. Remote Config: System configuration with Ansible on the remote host.
    6. Local Config: Finalizing setup with Ansible on the local machine.
  • Multi-Cloud Support: Native templates for:
    • Hetzner Cloud (hcloud)
    • Oracle Cloud Infrastructure (oci)
    • No-Infra (no-infra): For when the server is already there.
  • Dynamic Inventory: Automatically bridge the gap by generating Ansible inventory directly from OpenTofu outputs.
  • SMTP Testing Ready: Automatically installs s-nail and configures .mailrc on the remote host for immediate SMTP verification.
  • Environment Overrides: Support for overriding any configuration parameter via environment variables (e.g., BC_VAR_RESEND_PASSWORD).
  • Configurable Workflows: Execute complex multi-step processes like tofu init/apply followed by multiple ansible-playbook runs.

To use once, you need the following tools installed:

  • Clojure: The core engine.
  • Babashka: Recommended for running CLI tasks.
  • OpenTofu: For infrastructure management.
  • Ansible: For configuration management.
  • Cloud Credentials: e.g., HCLOUD_TOKEN, CLOUDFLARE_API_TOKEN, RESEND_API_KEY, or OCI configuration.

You can override any parameter defined in options.clj using environment variables prefixed with BC_VAR_. The variable name is converted to lowercase, and underscores or dots are replaced with hyphens.

Example:

Terminal window
export BC_VAR_RESEND_PASSWORD="your-smtp-password"
export BC_VAR_DOMAIN="example.com"

These will be automatically merged into the workflow parameters.

The easiest way to interact with once is through the provided Babashka tasks.

Clone the repository and configure your options:

Terminal window
git clone https://github.com/amiorin/once
cd once
# Edit your chosen provider options
edit src/clj/io/github/amiorin/once/options.clj

In bb.edn, you can switch the active profile by changing the require statement:

;; bb.edn
:requires [...
;; Switch between oci, hcloud, or no-infra
[io.github.amiorin.once.options :refer [oci] :rename {oci options}]
...]

The once task handles the full lifecycle. You can pass multiple commands:

  • Full Setup: bb once create (Tofu -> Tofu SMTP -> Tofu DNS -> Tofu SMTP Post -> Ansible -> Ansible Local)
  • Tear Down: bb once delete (Tofu DNS Destroy -> Tofu SMTP Post Destroy -> Tofu SMTP Destroy -> Tofu Destroy)
  • Sequential: bb once delete create (Clean slate redeploy)

You can also run the underlying tools individually. Most tasks require a render step first to generate the necessary config files from templates into the .dist/ directory.

  • OpenTofu (Infrastructure):
    Terminal window
    bb tofu render tofu:init tofu:apply:-auto-approve
  • OpenTofu (SMTP):
    Terminal window
    bb tofu-smtp render tofu:init tofu:apply:-auto-approve
  • OpenTofu (DNS):
    Terminal window
    bb tofu-dns render tofu:init tofu:apply:-auto-approve
  • OpenTofu (SMTP Post-Verification):
    Terminal window
    bb tofu-smtp-post render tofu:init tofu:apply:-auto-approve
  • Remote Ansible:
    Terminal window
    bb ansible render -- ansible-playbook main.yml
  • Local Ansible:
    Terminal window
    bb ansible-local render -- ansible-playbook main.yml

You can trigger workflows directly from a Clojure REPL:

(require '[io.github.amiorin.once.package :as once])
(require '[io.github.amiorin.once.options :as options])
;; Run the "create" workflow using OCI profile
(once/once* "create" options/oci)
  1. Template Rendering: big-config takes templates from src/resources and your options to generate valid Tofu and Ansible files in .dist/.
  2. Infrastructure Hook: When create runs, it first executes OpenTofu to provision resources.
  3. Inventory & Config Bridging: The Tofu output (like the new server IP or SMTP records) is captured using tofu output --json and injected into the DNS configuration and Ansible inventory generation logic.
  4. Configuration: Ansible then connects to the new host using the dynamically generated inventory to apply your playbooks.
  • src/clj/.../once/:
    • package.clj: Defines the high-level create/delete workflows.
    • params.clj: Logic for extracting parameters from Tofu outputs.
    • tools.clj: Implementation details for Tofu, Tofu SMTP, Tofu DNS, and Ansible wrappers.
    • options.clj: Where you define your cloud profiles and credentials.
  • src/resources/.../once/tools/:
    • tofu/: Multi-cloud .tf templates.
    • tofu-smtp/: SMTP configuration templates (Resend).
    • tofu-dns/: DNS configuration templates (Cloudflare).
    • tofu-smtp-post/: SMTP post-verification templates (Resend).
    • ansible/: Remote system playbooks.
    • ansible-local/: Local machine configuration playbooks.

If you are contributing to once, you can use the following task to keep the code clean:

Terminal window
bb tidy

This uses clojure-lsp to clean namespaces and format the source code.

Copyright © 2026 Alberto Miorin

Distributed under the MIT License.