diff --git a/src/setup/autostart.rs b/src/setup/autostart.rs new file mode 100644 index 0000000..af1f77a --- /dev/null +++ b/src/setup/autostart.rs @@ -0,0 +1,14 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Autostart { + pub kiosk: Kiosk +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Kiosk { + pub page: String, + pub user: String +} diff --git a/src/setup/mod.rs b/src/setup/mod.rs new file mode 100644 index 0000000..0d3bb45 --- /dev/null +++ b/src/setup/mod.rs @@ -0,0 +1,45 @@ +//! This module contains definitions to read the setup files. +use anyhow::Context as _; +use serde::de::DeserializeOwned; +use std::{ + fs, + path::{Path, PathBuf} +}; + +pub mod autostart; +pub mod network; +pub mod os; +pub mod packages; + +use autostart::Autostart; +use network::Network; +use os::Os; +use packages::Packages; + +pub struct Setup { + pub os: Os, + pub network: Network, + pub packages: Packages, + pub autostart: Autostart +} + +impl Setup { + fn read(path: &Path, file: &str) -> anyhow::Result { + toml::from_slice(&fs::read(path.join(file)).with_context(|| format!("Failed to read {file}"))?) + .with_context(|| format!("Failed to deserialize {file}")) + } + + pub fn load() -> anyhow::Result { + let path = PathBuf::from("setup"); + let os = Self::read(&path, "os.toml")?; + let network = Self::read(&path, "network.toml")?; + let packages = Self::read(&path, "packages.toml")?; + let autostart = Self::read(&path, "autostart.toml")?; + Ok(Self { + os, + network, + packages, + autostart + }) + } +} diff --git a/src/setup/network.rs b/src/setup/network.rs new file mode 100644 index 0000000..605027c --- /dev/null +++ b/src/setup/network.rs @@ -0,0 +1,33 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Network { + #[serde(default)] + pub tailscale: bool, + + #[serde(default)] + pub wifi: Vec +} + +#[derive(Deserialize)] +//#[serde(deny_unknown_fields)] +pub struct Wifi { + pub ssid: String, + + #[serde(flatten)] + pub security: WifiSecurity +} + +#[derive(Deserialize)] +#[serde(tag = "security")] +pub enum WifiSecurity { + #[serde(rename = "none")] + None, + + #[serde(rename = "WPA-PSK")] + WpaPsk { password: String }, + + #[serde(rename = "WPA-EAP")] + WpaEap { identity: String, password: String } +} diff --git a/src/setup/os.rs b/src/setup/os.rs new file mode 100644 index 0000000..6ada02b --- /dev/null +++ b/src/setup/os.rs @@ -0,0 +1,216 @@ +use serde::{ + de::{self, Deserializer, Visitor}, + Deserialize +}; +use std::{ + borrow::Cow, + fmt::{self, Display, Formatter} +}; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Os { + pub alpine: Alpine, + pub rpi: Rpi, + pub host: Host, + pub user: Vec +} + +pub enum Branch { + Edge, + Version(String) +} + +impl Branch { + pub fn git_branch(&self) -> Cow<'static, str> { + match self { + Self::Edge => "master".into(), + Self::Version(v) => format!("{v}-stable").into() + } + } +} + +impl Default for Branch { + fn default() -> Self { + Self::Version("3.17".into()) + } +} + +impl<'de> Deserialize<'de> for Branch { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de> + { + struct BranchVisitor; + + impl<'de> Visitor<'de> for BranchVisitor { + type Value = Branch; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "the alpine branch, either edge or a version like v3.17") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error + { + if let Some(version) = v.strip_prefix('v') { + Ok(Branch::Version(version.to_owned())) + } else if v == "edge" { + Ok(Branch::Edge) + } else { + Err(E::custom("Invalid branch, expected either edge or a version like v3.17")) + } + } + } + + deserializer.deserialize_str(BranchVisitor) + } +} + +impl Display for Branch { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Version(v) => write!(f, "v{v}"), + Self::Edge => write!(f, "edge") + } + } +} + +#[derive(Default, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Arch { + ArmHf, + #[default] + ArmV7, + Aarch64 +} + +impl Arch { + pub fn cbuild(&self) -> &'static str { + match self { + Self::ArmHf => "armv6-alpine-linux-musleabihf", + Self::ArmV7 => "armv7-alpine-linux-musleabihf", + Self::Aarch64 => "aarch64-alpine-linux-musl" + } + } + + pub fn to_str(&self) -> &'static str { + match self { + Self::ArmHf => "armhf", + Self::ArmV7 => "armv7", + Self::Aarch64 => "aarch64" + } + } + + pub fn is64bit(&self) -> bool { + matches!(self, Self::Aarch64) + } +} + +impl Display for Arch { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.to_str()) + } +} + +fn default_mirror() -> String { + "https://dl-cdn.alpinelinux.org/alpine".into() +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Alpine { + #[serde(default)] + pub branch: Branch, + + #[serde(default)] + pub arch: Arch, + + #[serde(default = "default_mirror")] + pub mirror: String, + + #[serde(default)] + pub extra_repos: Vec, + + #[serde(default)] + pub extra_keys: Vec +} + +fn default_cmdline() -> String { + "dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait"; + "modules=loop,squashfs,sd-mod,usb-storage root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes console=tty1 rootwait" + .into() +} + +fn default_usercfg() -> String { + r#"# modify this file instead of config.txt + +# hide raspberry pi logo on startup +disable_splash=1 +boot_delay=0 + +# enable uart / disable bluetooth +enable_uart=1 +dtoverlay=disable-bt + +# enable HDMI output +dtoverlay=vc4-kms-v3d +display_auto_detect=1 +disable_overscan=1 +"# + .into() +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Rpi { + #[serde(default = "default_cmdline")] + pub cmdline: String, + + #[serde(default = "default_usercfg")] + pub usercfg: String +} + +fn default_hostname() -> String { + "alpi".into() +} + +fn default_keymap() -> String { + "de".into() +} + +fn default_timezone() -> String { + "Europe/Amsterdam".into() +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Host { + #[serde(default = "default_hostname")] + pub hostname: String, + + #[serde(default = "default_keymap")] + pub keymap: String, + pub keymap_variant: Option, + + #[serde(default = "default_timezone")] + pub timezone: String +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct User { + pub name: String, + + pub password: Option, + + #[serde(default)] + pub sudo: bool, + + #[serde(default)] + pub groups: Vec, + + #[serde(default)] + pub authorized_keys: Vec +} diff --git a/src/setup/packages.rs b/src/setup/packages.rs new file mode 100644 index 0000000..ba1a65f --- /dev/null +++ b/src/setup/packages.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Packages { + pub install: Vec, + pub autostart: Vec +}