From 86c681be7089e80b1f75a1aa1792150be674ddac Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Thu, 12 Jun 2025 18:16:43 +0000 Subject: [PATCH] docs: New README, two options to choose from right now (#59) Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/59 Co-authored-by: Jean-Gabriel Gill-Couture Co-committed-by: Jean-Gabriel Gill-Couture --- README.md | 242 +++++++++++++++++--------------------- docs/README.md | 1 + docs/cyborg-metaphor.md | 13 ++ harmony_cli/README.md | 27 +++++ opnsense-config/README.md | 146 +++++++++++++++++++++++ 5 files changed, 293 insertions(+), 136 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/cyborg-metaphor.md create mode 100644 harmony_cli/README.md create mode 100644 opnsense-config/README.md diff --git a/README.md b/README.md index b5c765a..4ebf712 100644 --- a/README.md +++ b/README.md @@ -1,171 +1,141 @@ -# Harmony : Open Infrastructure Orchestration +# Harmony -## Quick demo +[![Build](https://git.nationtech.io/NationTech/harmony/actions/workflows/check.yml/badge.svg)](https://git.nationtech.io/nationtech/harmony) +[![License](https://img.shields.io/badge/license-AGPLv3-blue?style=flat-square)](LICENSE) -`cargo run -p example-tui` +**Open-source infrastructure orchestration that treats your platform like first-class code.** -This will launch Harmony's minimalist terminal ui which embeds a few demo scores. +Harmony unifies project scaffolding, infrastructure provisioning, application deployment, and day-2 operations in **one strongly-typed Rust codebase**. From a developer laptop to a global production cluster, a single source of truth drives the full software lifecycle. -Usage instructions will be displayed at the bottom of the TUI. +--- -`cargo run --bin example-cli -- --help` +## 1 · The Harmony Philosophy -This is the harmony CLI, a minimal implementation +Infrastructure is essential, but it shouldn’t be your core business. Harmony is built on three guiding principles that make modern platforms reliable, repeatable, and easy to reason about. -The current help text: +| Principle | What it means for you | +|-----------|-----------------------| +| **Infrastructure as Resilient Code** | Replace sprawling YAML and bash scripts with type-safe Rust. Test, refactor, and version your platform just like application code. | +| **Prove It Works — Before You Deploy** | Harmony uses the compiler to verify that your application’s needs match the target environment’s capabilities at **compile-time**, eliminating an entire class of runtime outages. | +| **One Unified Model** | Software and infrastructure are a single system. Harmony models them together, enabling deep automation—from bare-metal servers to Kubernetes workloads—with zero context switching. | -```` -Usage: example-cli [OPTIONS] +These principles surface as simple, ergonomic Rust APIs that let teams focus on their product while trusting the platform underneath. -Options: - -y, --yes Run score(s) or not - -f, --filter Filter query - -i, --interactive Run interactive TUI or not - -a, --all Run all or nth, defaults to all - -n, --number Run nth matching, zero indexed [default: 0] - -l, --list list scores, will also be affected by run filter - -h, --help Print help - -V, --version Print version``` +--- -## Core architecture +## 2 · Quick Start -![Harmony Core Architecture](docs/diagrams/Harmony_Core_Architecture.drawio.svg) -```` -## Supporting a new field in OPNSense `config.xml` +The snippet below spins up a complete **production-grade LAMP stack** with monitoring. Swap it for your own scores to deploy anything from microservices to machine-learning pipelines. -Two steps: -- Supporting the field in `opnsense-config-xml` -- Enabling Harmony to control the field +```rust +use harmony::{ + data::Version, + inventory::Inventory, + maestro::Maestro, + modules::{ + lamp::{LAMPConfig, LAMPScore}, + monitoring::monitoring_alerting::MonitoringAlertingStackScore, + }, + topology::{K8sAnywhereTopology, Url}, +}; -We'll use the `filename` field in the `dhcpcd` section of the file as an example. +#[tokio::main] +async fn main() { + // 1. Describe what you want + let lamp_stack = LAMPScore { + name: "harmony-lamp-demo".into(), + domain: Url::Url(url::Url::parse("https://lampdemo.example.com").unwrap()), + php_version: Version::from("8.3.0").unwrap(), + config: LAMPConfig { + project_root: "./php".into(), + database_size: "4Gi".into(), + ..Default::default() + }, + }; -### Supporting the field + // 2. Pick where it should run + let mut maestro = Maestro::::initialize( + Inventory::autoload(), // auto-detect hardware / kube-config + K8sAnywhereTopology::from_env(), // local k3d, CI, staging, prod… + ) + .await + .unwrap(); -As type checking if enforced, every field from `config.xml` must be known by the code. Each subsection of `config.xml` has its `.rs` file. For the `dhcpcd` section, we'll modify `opnsense-config-xml/src/data/dhcpd.rs`. + // 3. Enhance with extra scores (monitoring, CI/CD, …) + let mut monitoring = MonitoringAlertingStackScore::new(); + monitoring.namespace = Some(lamp_stack.config.namespace.clone()); -When a new field appears in the xml file, an error like this will be thrown and Harmony will panic : -``` - Running `/home/stremblay/nt/dir/harmony/target/debug/example-nanodc` -Found unauthorized element filename -thread 'main' panicked at opnsense-config-xml/src/data/opnsense.rs:54:14: -OPNSense received invalid string, should be full XML: () + maestro.register_all(vec![Box::new(lamp_stack), Box::new(monitoring)]); + // 4. Launch an interactive CLI / TUI + harmony_cli::init(maestro, None).await.unwrap(); +} ``` -Define the missing field (`filename`) in the `DhcpInterface` struct of `opnsense-config-xml/src/data/dhcpd.rs`: -``` -pub struct DhcpInterface { - ... - pub filename: Option, +Run it: + +```bash +cargo run ``` -Harmony should now be fixed, build and run. +Harmony analyses the code, shows an execution plan in a TUI, and applies it once you confirm. Same code, same binary—every environment. -### Controlling the field +--- -Define the `xml field setter` in `opnsense-config/src/modules/dhcpd.rs`. -``` -impl<'a> DhcpConfig<'a> { - ... - pub fn set_filename(&mut self, filename: &str) { - self.enable_netboot(); - self.get_lan_dhcpd().filename = Some(filename.to_string()); - } - ... +## 3 · Core Concepts + +| Term | One-liner | +|------|-----------| +| **Score** | Declarative description of the desired state (e.g., `LAMPScore`). | +| **Interpret** | Imperative logic that realises a `Score` on a specific environment. | +| **Topology** | An environment (local k3d, AWS, bare-metal) exposing verified *Capabilities* (Kubernetes, DNS, …). | +| **Maestro** | Orchestrator that compiles Scores + Topology, ensuring all capabilities line up **at compile-time**. | +| **Inventory** | Optional catalogue of physical assets for bare-metal and edge deployments. | + +A visual overview is in the diagram below. + +[Harmony Core Architecture](docs/diagrams/Harmony_Core_Architecture.drawio.svg) + +--- + +## 4 · Install + +Prerequisites: + +* Rust +* Docker (if you deploy locally) +* `kubectl` / `helm` for Kubernetes-based topologies + +```bash +git clone https://git.nationtech.io/nationtech/harmony +cd harmony +cargo build --release # builds the CLI, TUI and libraries ``` -Define the `value setter` in the `DhcpServer trait` in `domain/topology/network.rs` -``` -#[async_trait] -pub trait DhcpServer: Send + Sync { - ... - async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError>; - ... -``` +--- -Implement the `value setter` in each `DhcpServer` implementation. -`infra/opnsense/dhcp.rs`: -``` -#[async_trait] -impl DhcpServer for OPNSenseFirewall { - ... - async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> { - { - let mut writable_opnsense = self.opnsense_config.write().await; - writable_opnsense.dhcp().set_filename(filename); - debug!("OPNsense dhcp server set filename {filename}"); - } +## 5 · Learning More - Ok(()) - } - ... -``` +* **Architectural Decision Records** – dive into the rationale + - [ADR-001 · Why Rust](adr/001-rust.md) + - [ADR-003 · Infrastructure Abstractions](adr/003-infrastructure-abstractions.md) + - [ADR-006 · Secret Management](adr/006-secret-management.md) + - [ADR-011 · Multi-Tenant Cluster](adr/011-multi-tenant-cluster.md) -`domain/topology/ha_cluster.rs` -``` -#[async_trait] -impl DhcpServer for DummyInfra { - ... - async fn set_filename(&self, _filename: &str) -> Result<(), ExecutorError> { - unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) - } - ... -``` +* **Extending Harmony** – write new Scores / Interprets, add hardware like OPNsense firewalls, or embed Harmony in your own tooling (`/docs`). -Add the new field to the DhcpScore in `modules/dhcp.rs` -``` -pub struct DhcpScore { - ... - pub filename: Option, -``` +* **Community** – discussions and roadmap live in [GitLab issues](https://git.nationtech.io/nationtech/harmony/-/issues). PRs, ideas, and feedback are welcome! -Define it in its implementation in `modules/okd/dhcp.rs` -``` -impl OKDDhcpScore { - ... - Self { - dhcp_score: DhcpScore { - ... - filename: Some("undionly.kpxe".to_string()), -``` +--- -Define it in its implementation in `modules/okd/bootstrap_dhcp.rs` -``` -impl OKDDhcpScore { - ... - Self { - dhcp_score: DhcpScore::new( - ... - Some("undionly.kpxe".to_string()), -``` +## 6 · License -Update the interpret (function called by the `execute` fn of the interpret) so it now updates the `filename` field value in `modules/dhcp.rs` -``` -impl DhcpInterpret { - ... - let filename_outcome = match &self.score.filename { - Some(filename) => { - let dhcp_server = Arc::new(topology.dhcp_server.clone()); - dhcp_server.set_filename(&filename).await?; - Outcome::new( - InterpretStatus::SUCCESS, - format!("Dhcp Interpret Set filename to {filename}"), - ) - } - None => Outcome::noop(), - }; +Harmony is released under the **GNU AGPL v3**. - if next_server_outcome.status == InterpretStatus::NOOP - && boot_filename_outcome.status == InterpretStatus::NOOP - && filename_outcome.status == InterpretStatus::NOOP +> We choose a strong copyleft license to ensure the project—and every improvement to it—remains open and benefits the entire community. Fork it, enhance it, even out-innovate us; just keep it open. - ... +See [LICENSE](LICENSE) for the full text. - Ok(Outcome::new( - InterpretStatus::SUCCESS, - format!( - "Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}]", - self.score.boot_filename, self.score.boot_filename, self.score.filename - ) - ... -``` +--- + +*Made with ❤️ & 🦀 by the NationTech and the Harmony community* diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..a125fce --- /dev/null +++ b/docs/README.md @@ -0,0 +1 @@ +Not much here yet, see the `adr` folder for now. More to come in time! diff --git a/docs/cyborg-metaphor.md b/docs/cyborg-metaphor.md new file mode 100644 index 0000000..d74536e --- /dev/null +++ b/docs/cyborg-metaphor.md @@ -0,0 +1,13 @@ +## Conceptual metaphor : The Cyborg and the Central Nervous System + +At the heart of Harmony lies a core belief: in modern, decentralized systems, **software and infrastructure are not separate entities.** They are a single, symbiotic organism—a cyborg. + +The software is the electronics, the "mind"; the infrastructure is the biological host, the "body". They live or die, thrive or sink together. + +Traditional approaches attempt to manage this complex organism with fragmented tools: static YAML for configuration, brittle scripts for automation, and separate Infrastructure as Code (IaC) for provisioning. This creates a disjointed system that struggles to scale or heal itself, making it inadequate for the demands of fully automated, enterprise-grade clusters. + +Harmony's goal is to provide the **central nervous system for this cyborg**. We aim to achieve the full automation of complex, decentralized clouds by managing this integrated entity holistically. + +To achieve this, a tool must be both robust and powerful. It must manage the entire lifecycle—deployment, upgrades, failure recovery, and decommissioning—with precision. This requires full control over application packaging and a deep, intrinsic integration between the software and the infrastructure it inhabits. + +This is why Harmony uses a powerful, living language like Rust. It replaces static, lifeless configuration files with a dynamic, breathing codebase. It allows us to express the complex relationships and behaviors of a modern distributed system, enabling the creation of truly automated, resilient, and powerful platforms that can thrive. diff --git a/harmony_cli/README.md b/harmony_cli/README.md new file mode 100644 index 0000000..370a14a --- /dev/null +++ b/harmony_cli/README.md @@ -0,0 +1,27 @@ +## Quick demo + +`cargo run -p example-tui` + +This will launch Harmony's minimalist terminal ui which embeds a few demo scores. + +Usage instructions will be displayed at the bottom of the TUI. + +`cargo run --bin example-cli -- --help` + +This is the harmony CLI, a minimal implementation + +The current help text: + +``` +Usage: example-cli [OPTIONS] + +Options: + -y, --yes Run score(s) or not + -f, --filter Filter query + -i, --interactive Run interactive TUI or not + -a, --all Run all or nth, defaults to all + -n, --number Run nth matching, zero indexed [default: 0] + -l, --list list scores, will also be affected by run filter + -h, --help Print help + -V, --version Print version``` + diff --git a/opnsense-config/README.md b/opnsense-config/README.md new file mode 100644 index 0000000..c31dc23 --- /dev/null +++ b/opnsense-config/README.md @@ -0,0 +1,146 @@ +## Supporting a new field in OPNSense `config.xml` + +Two steps: +- Supporting the field in `opnsense-config-xml` +- Enabling Harmony to control the field + +We'll use the `filename` field in the `dhcpcd` section of the file as an example. + +### Supporting the field + +As type checking if enforced, every field from `config.xml` must be known by the code. Each subsection of `config.xml` has its `.rs` file. For the `dhcpcd` section, we'll modify `opnsense-config-xml/src/data/dhcpd.rs`. + +When a new field appears in the xml file, an error like this will be thrown and Harmony will panic : +``` + Running `/home/stremblay/nt/dir/harmony/target/debug/example-nanodc` +Found unauthorized element filename +thread 'main' panicked at opnsense-config-xml/src/data/opnsense.rs:54:14: +OPNSense received invalid string, should be full XML: () + +``` + +Define the missing field (`filename`) in the `DhcpInterface` struct of `opnsense-config-xml/src/data/dhcpd.rs`: + +```rust +pub struct DhcpInterface { + ... + pub filename: Option, +``` + +Harmony should now be fixed, build and run. + +### Controlling the field + +Define the `xml field setter` in `opnsense-config/src/modules/dhcpd.rs`. + +```rust +impl<'a> DhcpConfig<'a> { + ... + pub fn set_filename(&mut self, filename: &str) { + self.enable_netboot(); + self.get_lan_dhcpd().filename = Some(filename.to_string()); + } + ... +``` + +Define the `value setter` in the `DhcpServer trait` in `domain/topology/network.rs` + +```rust +#[async_trait] +pub trait DhcpServer: Send + Sync { + ... + async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError>; + ... +``` + +Implement the `value setter` in each `DhcpServer` implementation. +`infra/opnsense/dhcp.rs`: + +```rust +#[async_trait] +impl DhcpServer for OPNSenseFirewall { + ... + async fn set_filename(&self, filename: &str) -> Result<(), ExecutorError> { + { + let mut writable_opnsense = self.opnsense_config.write().await; + writable_opnsense.dhcp().set_filename(filename); + debug!("OPNsense dhcp server set filename {filename}"); + } + + Ok(()) + } + ... +``` + +`domain/topology/ha_cluster.rs` +```rust +#[async_trait] +impl DhcpServer for DummyInfra { + ... + async fn set_filename(&self, _filename: &str) -> Result<(), ExecutorError> { + unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA) + } + ... +``` + +Add the new field to the DhcpScore in `modules/dhcp.rs` + +```rust +pub struct DhcpScore { + ... + pub filename: Option, +``` + +Define it in its implementation in `modules/okd/dhcp.rs` + +```rust +impl OKDDhcpScore { + ... + Self { + dhcp_score: DhcpScore { + ... + filename: Some("undionly.kpxe".to_string()), +``` + +Define it in its implementation in `modules/okd/bootstrap_dhcp.rs` + +```rust +impl OKDDhcpScore { + ... + Self { + dhcp_score: DhcpScore::new( + ... + Some("undionly.kpxe".to_string()), +``` + +Update the interpret (function called by the `execute` fn of the interpret) so it now updates the `filename` field value in `modules/dhcp.rs` + +```rust +impl DhcpInterpret { + ... + let filename_outcome = match &self.score.filename { + Some(filename) => { + let dhcp_server = Arc::new(topology.dhcp_server.clone()); + dhcp_server.set_filename(&filename).await?; + Outcome::new( + InterpretStatus::SUCCESS, + format!("Dhcp Interpret Set filename to {filename}"), + ) + } + None => Outcome::noop(), + }; + + if next_server_outcome.status == InterpretStatus::NOOP + && boot_filename_outcome.status == InterpretStatus::NOOP + && filename_outcome.status == InterpretStatus::NOOP + + ... + + Ok(Outcome::new( + InterpretStatus::SUCCESS, + format!( + "Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}]", + self.score.boot_filename, self.score.boot_filename, self.score.filename + ) + ... +```