Compare commits

...

4 Commits

Author SHA1 Message Date
d2d473c137 feat: Generate definitions for config
Some checks failed
Build and deploy / Deploy container (push) Blocked by required conditions
Build and deploy / build (push) Has been cancelled
2025-10-17 03:15:01 +02:00
a0ed373971 refactor: Move definition writing into separate function
Some checks failed
Build and deploy / Deploy container (push) Blocked by required conditions
Build and deploy / build (push) Has been cancelled
2025-10-17 03:12:40 +02:00
5e13dff2b5 chore: Move main.rs to bin/automation.rs 2025-10-17 03:08:37 +02:00
ba818c6b60 refactor(config)!: Setup for expanding lua config return
Moves the application config out of automation_lib and sets up the
config return type for further expansion.
2025-10-17 03:08:21 +02:00
9 changed files with 101 additions and 56 deletions

1
Cargo.lock generated
View File

@@ -99,6 +99,7 @@ dependencies = [
"git-version", "git-version",
"google_home", "google_home",
"inventory", "inventory",
"lua_typed",
"mlua", "mlua",
"reqwest", "reqwest",
"rumqttc", "rumqttc",

View File

@@ -77,6 +77,7 @@ config = { version = "0.15.15", default-features = false, features = [
] } ] }
git-version = "0.3.9" git-version = "0.3.9"
google_home = { workspace = true } google_home = { workspace = true }
lua_typed = { workspace = true }
inventory = { workspace = true } inventory = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
reqwest = { workspace = true } reqwest = { workspace = true }

View File

@@ -1,4 +1,3 @@
use std::net::{Ipv4Addr, SocketAddr};
use std::time::Duration; use std::time::Duration;
use lua_typed::Typed; use lua_typed::Typed;
@@ -30,29 +29,6 @@ impl From<MqttConfig> for MqttOptions {
} }
} }
#[derive(Debug, Deserialize)]
pub struct FulfillmentConfig {
pub openid_url: String,
#[serde(default = "default_fulfillment_ip")]
pub ip: Ipv4Addr,
#[serde(default = "default_fulfillment_port")]
pub port: u16,
}
impl From<FulfillmentConfig> for SocketAddr {
fn from(fulfillment: FulfillmentConfig) -> Self {
(fulfillment.ip, fulfillment.port).into()
}
}
fn default_fulfillment_ip() -> Ipv4Addr {
[0, 0, 0, 0].into()
}
fn default_fulfillment_port() -> u16 {
7878
}
#[derive(Debug, Clone, Deserialize, Typed)] #[derive(Debug, Clone, Deserialize, Typed)]
pub struct InfoConfig { pub struct InfoConfig {
pub name: String, pub name: String,

View File

@@ -21,10 +21,6 @@ local function mqtt_automation(topic)
return "automation/" .. topic return "automation/" .. topic
end end
local fulfillment = {
openid_url = "https://login.huizinga.dev/api/oidc",
}
local mqtt_client = require("automation:mqtt").new(device_manager, { local mqtt_client = require("automation:mqtt").new(device_manager, {
host = ((host == "zeus" or host == "hephaestus") and "olympus.lan.huizinga.dev") or "mosquitto", host = ((host == "zeus" or host == "hephaestus") and "olympus.lan.huizinga.dev") or "mosquitto",
port = 8883, port = 8883,
@@ -748,4 +744,9 @@ device_manager:schedule("0 0 20 * * *", function()
bedroom_air_filter:set_on(false) bedroom_air_filter:set_on(false)
end) end)
return fulfillment ---@type Config
return {
fulfillment = {
openid_url = "https://login.huizinga.dev/api/oidc",
},
}

12
definitions/config.lua Normal file
View File

@@ -0,0 +1,12 @@
-- DO NOT MODIFY, FILE IS AUTOMATICALLY GENERATED
---@meta
---@class FulfillmentConfig
---@field openid_url string
---@field ip string?
---@field port integer?
local FulfillmentConfig
---@class Config
---@field fulfillment FulfillmentConfig
local Config

View File

@@ -1,29 +1,23 @@
#![feature(iter_intersperse)] #![feature(iter_intersperse)]
mod config;
mod secret;
mod version;
mod web;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::Path; use std::path::Path;
use std::process; use std::process;
use ::config::{Environment, File}; use ::config::{Environment, File};
use automation_lib::config::FulfillmentConfig; use automation::config::{Config, Setup};
use automation::secret::EnvironmentSecretFile;
use automation::version::VERSION;
use automation::web::{ApiError, User};
use automation_lib::device_manager::DeviceManager; use automation_lib::device_manager::DeviceManager;
use axum::extract::{FromRef, State}; use axum::extract::{FromRef, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::routing::post; use axum::routing::post;
use axum::{Json, Router}; use axum::{Json, Router};
use config::Config;
use google_home::{GoogleHome, Request, Response}; use google_home::{GoogleHome, Request, Response};
use mlua::LuaSerdeExt; use mlua::LuaSerdeExt;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use web::{ApiError, User};
use crate::secret::EnvironmentSecretFile;
use crate::version::VERSION;
// Force automation_devices to link so that it gets registered as a module // Force automation_devices to link so that it gets registered as a module
extern crate automation_devices; extern crate automation_devices;
@@ -76,7 +70,7 @@ async fn app() -> anyhow::Result<()> {
info!(version = VERSION, "automation_rs"); info!(version = VERSION, "automation_rs");
let config: Config = ::config::Config::builder() let setup: Setup = ::config::Config::builder()
.add_source( .add_source(
File::with_name(&format!("{}.toml", std::env!("CARGO_PKG_NAME"))).required(false), File::with_name(&format!("{}.toml", std::env!("CARGO_PKG_NAME"))).required(false),
) )
@@ -138,12 +132,12 @@ async fn app() -> anyhow::Result<()> {
lua.register_module("automation:device_manager", device_manager.clone())?; lua.register_module("automation:device_manager", device_manager.clone())?;
lua.register_module("automation:variables", lua.to_value(&config.variables)?)?; lua.register_module("automation:variables", lua.to_value(&setup.variables)?)?;
lua.register_module("automation:secrets", lua.to_value(&config.secrets)?)?; lua.register_module("automation:secrets", lua.to_value(&setup.secrets)?)?;
let entrypoint = Path::new(&config.entrypoint); let entrypoint = Path::new(&setup.entrypoint);
let fulfillment_config: mlua::Value = lua.load(entrypoint).eval_async().await?; let config: mlua::Value = lua.load(entrypoint).eval_async().await?;
let fulfillment_config: FulfillmentConfig = lua.from_value(fulfillment_config)?; let config: Config = lua.from_value(config)?;
// Create google home fulfillment route // Create google home fulfillment route
let fulfillment = Router::new().route("/google_home", post(fulfillment)); let fulfillment = Router::new().route("/google_home", post(fulfillment));
@@ -152,12 +146,12 @@ async fn app() -> anyhow::Result<()> {
let app = Router::new() let app = Router::new()
.nest("/fulfillment", fulfillment) .nest("/fulfillment", fulfillment)
.with_state(AppState { .with_state(AppState {
openid_url: fulfillment_config.openid_url.clone(), openid_url: config.fulfillment.openid_url.clone(),
device_manager, device_manager,
}); });
// Start the web server // Start the web server
let addr: SocketAddr = fulfillment_config.into(); let addr: SocketAddr = config.fulfillment.into();
info!("Server started on http://{addr}"); info!("Server started on http://{addr}");
let listener = TcpListener::bind(addr).await?; let listener = TcpListener::bind(addr).await?;
axum::serve(listener, app).await?; axum::serve(listener, app).await?;

View File

@@ -1,32 +1,57 @@
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::Write; use std::io::Write;
use automation::config::{Config, FulfillmentConfig};
use automation_lib::Module; use automation_lib::Module;
use lua_typed::Typed;
use tracing::{info, warn}; use tracing::{info, warn};
extern crate automation_devices; extern crate automation_devices;
fn main() -> std::io::Result<()> { fn write_definitions(filename: &str, definitions: &str) -> std::io::Result<()> {
tracing_subscriber::fmt::init();
let definitions_directory = let definitions_directory =
std::path::Path::new(std::env!("CARGO_MANIFEST_DIR")).join("definitions"); std::path::Path::new(std::env!("CARGO_MANIFEST_DIR")).join("definitions");
fs::create_dir_all(&definitions_directory)?; fs::create_dir_all(&definitions_directory)?;
let mut file = File::create(definitions_directory.join(filename))?;
file.write_all(b"-- DO NOT MODIFY, FILE IS AUTOMATICALLY GENERATED\n")?;
file.write_all(definitions.as_bytes())?;
// Make sure we have a trailing new line
if !definitions.ends_with("\n") {
file.write_all(b"\n")?;
}
Ok(())
}
fn config_definitions() -> String {
let mut output = "---@meta\n\n".to_string();
output +=
&FulfillmentConfig::generate_full().expect("FulfillmentConfig should have a definition");
output += "\n";
output += &Config::generate_full().expect("FulfillmentConfig should have a definition");
output
}
fn main() -> std::io::Result<()> {
tracing_subscriber::fmt::init();
for module in inventory::iter::<Module> { for module in inventory::iter::<Module> {
if let Some(definitions) = module.definitions() { if let Some(definitions) = module.definitions() {
info!(name = module.get_name(), "Generating definitions"); info!(name = module.get_name(), "Generating definitions");
let filename = format!("{}.lua", module.get_name()); let filename = format!("{}.lua", module.get_name());
let mut file = File::create(definitions_directory.join(filename))?; write_definitions(&filename, &definitions)?;
file.write_all(b"-- DO NOT MODIFY, FILE IS AUTOMATICALLY GENERATED\n")?;
file.write_all(definitions.as_bytes())?;
file.write_all(b"\n")?;
} else { } else {
warn!(name = module.get_name(), "No definitions"); warn!(name = module.get_name(), "No definitions");
} }
} }
write_definitions("config.lua", &config_definitions())?;
Ok(()) Ok(())
} }

View File

@@ -1,9 +1,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::net::{Ipv4Addr, SocketAddr};
use lua_typed::Typed;
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Config { pub struct Setup {
#[serde(default = "default_entrypoint")] #[serde(default = "default_entrypoint")]
pub entrypoint: String, pub entrypoint: String,
#[serde(default)] #[serde(default)]
@@ -15,3 +17,32 @@ pub struct Config {
fn default_entrypoint() -> String { fn default_entrypoint() -> String {
"./config.lua".into() "./config.lua".into()
} }
#[derive(Debug, Deserialize, Typed)]
pub struct FulfillmentConfig {
pub openid_url: String,
#[serde(default = "default_fulfillment_ip")]
#[typed(default)]
pub ip: Ipv4Addr,
#[serde(default = "default_fulfillment_port")]
#[typed(default)]
pub port: u16,
}
#[derive(Debug, Deserialize, Typed)]
pub struct Config {
pub fulfillment: FulfillmentConfig,
}
impl From<FulfillmentConfig> for SocketAddr {
fn from(fulfillment: FulfillmentConfig) -> Self {
(fulfillment.ip, fulfillment.port).into()
}
}
fn default_fulfillment_ip() -> Ipv4Addr {
[0, 0, 0, 0].into()
}
fn default_fulfillment_port() -> u16 {
7878
}

4
src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod config;
pub mod secret;
pub mod version;
pub mod web;