commit b980ef8ee65269bf73eac048ed4924a50f11c411 Author: Elijah Voigt Date: Sun Feb 15 13:41:29 2026 -0800 chore: initial repo scaffolding Set up mono-repo foundation with CLAUDE.md conventions, base Nix flake (Rust + wasm32 target, Trunk, OpenTofu, wrangler, SQLx, Turso), shared common library crate, dual Apache-2.0/MIT licenses, .gitignore, and repo-level README. Co-Authored-By: Claude Opus 4.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..75c6693 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Rust +target/ +**/*.rs.bk + +# Environment / secrets +.env +.env.* + +# Nix +result +result-* + +# OpenTofu / Terraform +.terraform/ +*.tfstate +*.tfstate.* +.terraform.lock.hcl + +# OS +.DS_Store +Thumbs.db + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b729263 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,220 @@ +# CLAUDE.md — Vibesville Mono-Repo + +## Repository Structure + +This is a mono-repo of self-contained HTTP web services. Each service lives in its own top-level folder and is an independent Rust crate (no Cargo workspace). There is no cross-service communication — each service is a standalone application. + +``` +vibesville/ +├── CLAUDE.md +├── README.md # repo-wide overview with descriptions of each project +├── LICENSE-APACHE +├── LICENSE-MIT +├── flake.nix # base Nix flake — all services inherit or extend this +├── flake.lock +├── common/ # shared library crate (types, utilities, etc.) +│ ├── Cargo.toml +│ └── src/ +│ └── lib.rs +├── / +│ ├── Cargo.toml +│ ├── src/ +│ │ ├── main.rs +│ │ └── tests.rs # unit tests module +│ ├── tests/ # integration tests +│ ├── docs/ +│ │ ├── PLANNING.md # development phases and work logs +│ │ └── ARCHITECTURE.md # component overview and interactions +│ ├── infra/ # OpenTofu infrastructure +│ │ └── main.tf +│ ├── flake.nix # service-specific flake (inherits base) +│ └── README.md # what, how, run, test, license, disclaimer +└── ... +``` + +## Tech Stack + +### Language & Tooling + +- **Language:** Rust (all software) +- **Local development:** Nix with Nix Flakes for dependency management +- **Infrastructure:** OpenTofu with the Cloudflare provider (stored in each service's `infra/` subfolder) +- **Formatting:** Default `rustfmt` conventions (no custom `rustfmt.toml`) + +### Backend + +- **Framework:** Tower + Axum +- **Runtime:** Tokio +- **Target:** Cloudflare Workers via [workers-rs](https://github.com/cloudflare/workers-rs) +- **Deployment:** OpenTofu with the Cloudflare provider (prefer over wrangler for infrastructure management) +- **API format:** JSON-encoded request and response payloads +- **API spec:** Each backend service must have an auto-generated OpenAPI spec. _TODO: choose tooling crate._ + +### Frontend + +- **Framework:** Yew (Rust compiled to Wasm) +- **Build tool:** Trunk (`trunk serve` for local dev) +- **Compile target:** `wasm32-unknown-unknown` (must be installed: `rustup target add wasm32-unknown-unknown`) +- **Hosting:** Cloudflare Pages + +### Data + +- **Production database:** Cloudflare D1 (SQLite-compatible) +- **Local database:** Turso (file-backed SQLite) +- **Query layer:** SQLx + +## Shared Code + +The `common/` crate is a shared library for types, utilities, and definitions reused across services. Individual services depend on it via a path dependency in their `Cargo.toml`: + +```toml +[dependencies] +common = { path = "../common" } +``` + +## Chosen Crates + +When a crate is chosen to solve a problem, it becomes the standard for that purpose across all services. Add new entries here as decisions are made. + +| Purpose | Crate | Notes | +|---|---|---| +| | | | + +_This table is intentionally empty. Populate it as crate decisions are made during development._ + +## Secrets & Configuration + +- **Local:** `.env` files for local secrets. **Never commit `.env` files to the repository.** +- **Production:** Wrangler secrets (`wrangler secret put `). +- Add `.env` to `.gitignore` at both the repo root and service level. + +## Running Services Locally + +```sh +# Backend service (from the service directory) +cargo run + +# Frontend service (from the service directory) +trunk serve +``` + +## Validation + +Run these commands **in order** from the service directory before committing changes: + +```sh +cargo fmt # 1. consistent formatting +cargo check # 2. code is syntactically correct +cargo clippy # 3. code follows best practices +cargo test # 4. code is logically valid +``` + +## Git Conventions + +This repository follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary). + +Commit messages must use the format: + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +Common types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `ci`, `build`. + +Use the service name as the scope when the change is scoped to a single service (e.g., `feat(my-service): add user endpoint`). + +## License + +All code in this repository is dual-licensed under: + +- [Apache License, Version 2.0](LICENSE-APACHE) +- [MIT License](LICENSE-MIT) + +This follows standard Rust ecosystem convention. Each service README should reference this dual license. + +## Code Style & Documentation + +### Rust + +- All public functions, structs, enums, and modules must have rustdoc comments describing how they work. +- Include doc-examples where applicable — these double as doctests. +- Unit tests live in a `tests` module (`tests.rs` file) within the crate's root module or relevant submodules. +- Each service has a `tests/` directory for integration-style tests. + +### Infrastructure + +- Every OpenTofu `resource` and `data` block must have a comment explaining its purpose. + +### Release Builds + +Release builds must be compiled with settings to minimize binary size. Add this to each service's `Cargo.toml`: + +```toml +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 +``` + +## Testing + +### Structure + +- **Unit tests:** `src/tests.rs` module (or submodule-local `tests` modules) +- **Integration tests:** `tests/` directory at the service root + +### Coverage + +- Target 80%+ code coverage; higher is better. +- _TODO: determine coverage tooling (e.g., `cargo-tarpaulin`, `cargo-llvm-cov`)._ + +## Project Documentation + +Each service must maintain the following: + +### README.md + +Covers: +- What the project is +- How it works +- How to run it +- How to test it +- License (Apache-2.0 + MIT dual license) +- Disclaimer that the software was written with Claude Code (include the specific model used) + +### docs/PLANNING.md + +- Development phases +- Work logs +- Kept up to date as work progresses + +### docs/ARCHITECTURE.md + +- Component overview +- How components interact + +### Task Tracking + +Tasks are tracked with **Beads**. The markdown docs (PLANNING, ARCHITECTURE) are for human-readable documentation only — not task management. + +## Repo-Wide Files + +| File | Purpose | +|---|---| +| `CLAUDE.md` | This file — conventions and instructions for Claude Code | +| `README.md` | Repo overview with a description of each service | +| `LICENSE-APACHE` | Apache 2.0 license text | +| `LICENSE-MIT` | MIT license text | +| `flake.nix` | Base Nix flake — all service flakes inherit or extend this | +| `common/` | Shared library crate for cross-service types and utilities | + +## Nix + +- All local development uses Nix. +- Dependencies are managed with Nix Flakes. +- The repo root has a base `flake.nix` that services should either use directly or inherit and extend with their own `flake.nix`. diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..3221ad8 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,199 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by the Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding any notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. Please also get an OpenPGP + key ID from a public key server and include it in the notice. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..aced186 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Vibesville Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f950acb --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# Vibesville + +A mono-repo of self-contained HTTP web services built in Rust, targeting Cloudflare's edge platform. + +- **Backend** services run on Cloudflare Workers (via workers-rs + Axum). +- **Frontend** applications compile to Wasm with Yew and deploy to Cloudflare Pages. +- **Data** is stored in Cloudflare D1 (SQLite-compatible), with Turso for local development. +- **Infrastructure** is managed with OpenTofu and the Cloudflare provider. + +## Services + +| Service | Description | +|---|---| +| `common` | Shared library crate — types, utilities, and definitions used across services | + +_Services will be listed here as they are added._ + +## Getting Started + +### Prerequisites + +- [Nix](https://nixos.org/download/) with [Flakes enabled](https://nixos.wiki/wiki/Flakes) + +### Setup + +```sh +# Clone the repository +git clone https://gitea.elijah.run/vibesville/vibesville.git +cd vibesville + +# Enter the dev shell (installs Rust, Trunk, OpenTofu, wrangler, etc.) +nix develop +``` + +The dev shell provides everything needed to build, test, and deploy all services. + +### Running a Service + +```sh +# Backend +cd +cargo run + +# Frontend +cd +trunk serve +``` + +### Validation + +Run from within a service directory, in order: + +```sh +cargo fmt # formatting +cargo check # compilation +cargo clippy # lints +cargo test # tests +``` + +## Repository Layout + +``` +vibesville/ +├── CLAUDE.md # conventions and instructions for Claude Code +├── README.md # this file +├── LICENSE-APACHE +├── LICENSE-MIT +├── flake.nix # base Nix flake +├── common/ # shared library crate +└── / # each service is an independent Rust crate + ├── src/ + ├── tests/ # integration tests + ├── docs/ # PLANNING.md, ARCHITECTURE.md + └── infra/ # OpenTofu infrastructure +``` + +See [CLAUDE.md](CLAUDE.md) for full conventions, code style, and project documentation requirements. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) +- MIT License ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +## Disclaimer + +This software is developed with the assistance of [Claude Code](https://claude.ai/claude-code) by Anthropic. diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..750caf6 --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Shared types, utilities, and definitions for Vibesville services" + +[dependencies] diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..bf2ee21 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,12 @@ +//! Shared types, utilities, and definitions for Vibesville services. +//! +//! This crate provides common building blocks used across multiple services +//! in the Vibesville mono-repo. Services depend on it via a path dependency: +//! +//! ```toml +//! [dependencies] +//! common = { path = "../common" } +//! ``` + +#[cfg(test)] +mod tests; diff --git a/common/src/tests.rs b/common/src/tests.rs new file mode 100644 index 0000000..259d9a9 --- /dev/null +++ b/common/src/tests.rs @@ -0,0 +1 @@ +//! Unit tests for the common crate. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..638a725 --- /dev/null +++ b/flake.lock @@ -0,0 +1,82 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1770843696, + "narHash": "sha256-LovWTGDwXhkfCOmbgLVA10bvsi/P8eDDpRudgk68HA8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2343bbb58f99267223bc2aac4fc9ea301a155a16", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1771125043, + "narHash": "sha256-ldf/s49n6rOAxl7pYLJGGS1N/assoHkCOWdEdLyNZkc=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "4912f951a26dc8142b176be2c2ad834319dc06e8", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..b39d525 --- /dev/null +++ b/flake.nix @@ -0,0 +1,60 @@ +{ + description = "Vibesville — mono-repo of self-contained HTTP web services"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, flake-utils, rust-overlay }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system overlays; + }; + + rustToolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; + targets = [ "wasm32-unknown-unknown" ]; + }; + in + { + devShells.default = pkgs.mkShell { + buildInputs = [ + # Rust + rustToolchain + + # Frontend + pkgs.trunk + pkgs.wasm-bindgen-cli + + # Database + pkgs.sqlx-cli + pkgs.turso-cli + + # Infrastructure + pkgs.opentofu + + # Cloudflare + pkgs.wrangler + + # General + pkgs.pkg-config + pkgs.openssl + ]; + + shellHook = '' + echo "vibesville dev shell loaded" + echo "rust : $(rustc --version)" + echo "trunk : $(trunk --version)" + echo "tofu : $(tofu --version | head -1)" + ''; + }; + } + ); +}