Compare commits

...

2 commits

Author SHA1 Message Date
47c2c003d1
add accidentially ignored folder 2023-04-05 14:07:04 +02:00
515545cd24
add example setup 2023-04-05 14:06:56 +02:00
10 changed files with 368 additions and 5 deletions

5
.gitignore vendored
View file

@ -1,8 +1,3 @@
# sorry but I don't need my network config public
setup/network.toml
# also other config contains like access tokens so just hide everything
setup/
# rust/cargo
/target/

4
setup/autostart.toml Normal file
View file

@ -0,0 +1,4 @@
# setup an autostarting kiosk that runs a webpage in a browser
[kiosk]
page = "https://time.is"
user = "kiosk"

8
setup/network.toml Normal file
View file

@ -0,0 +1,8 @@
# enable connecting the raspberry pi into your tailscale network
tailscale = true
# add a wifi network
[[wifi]]
ssid = "eduroam"
security = "WPA-PSK"
password = "yoursupersecretpassword"

33
setup/os.toml Normal file
View file

@ -0,0 +1,33 @@
[alpine]
# the alpine version. I recommend always using the latest available
branch = "v3.17"
# aarch64 is required if you want to run some software like a browser
arch = "aarch64"
# unless you have a local mirror that you know will be fast, just leave this empty
mirror = "https://ftp.halifax.rwth-aachen.de/alpine"
# here you can add some extra repositories and their signing keys
extra_repos = []
extra_keys = []
[host]
# set your local keyboard layout
keymap = "de"
keymap_variant = "de-dvorak"
# create a user for you to log in
[[user]]
name = "msrd0"
password = "changeme"
sudo = true
authorized_keys = [
# add your ssh key(s) here
]
# create a user for the autostarting kiosk
[[user]]
name = "kiosk"
password = "changeme"
groups = ["audio", "cdrom", "dialout", "floppy", "input", "uucp", "video"]

7
setup/packages.toml Normal file
View file

@ -0,0 +1,7 @@
# install some extra alpine packages
install = [
"emacs-nox", "tmux",
]
# add openrc units to autostart
autostart = []

14
src/setup/autostart.rs Normal file
View file

@ -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
}

45
src/setup/mod.rs Normal file
View file

@ -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<T: DeserializeOwned>(path: &Path, file: &str) -> anyhow::Result<T> {
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<Self> {
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
})
}
}

33
src/setup/network.rs Normal file
View file

@ -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<Wifi>
}
#[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 }
}

216
src/setup/os.rs Normal file
View file

@ -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<User>
}
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<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, v: &str) -> Result<Self::Value, E>
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<String>,
#[serde(default)]
pub extra_keys: Vec<String>
}
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<String>,
#[serde(default = "default_timezone")]
pub timezone: String
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct User {
pub name: String,
pub password: Option<String>,
#[serde(default)]
pub sudo: bool,
#[serde(default)]
pub groups: Vec<String>,
#[serde(default)]
pub authorized_keys: Vec<String>
}

8
src/setup/packages.rs Normal file
View file

@ -0,0 +1,8 @@
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Packages {
pub install: Vec<String>,
pub autostart: Vec<String>
}