switch config to env vars, add nix module
This commit is contained in:
parent
996da6cf8b
commit
5bfba1b6ff
8 changed files with 206 additions and 32 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -245,6 +245,12 @@ dependencies = [
|
|||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dptree"
|
||||
version = "0.3.0"
|
||||
|
|
@ -450,6 +456,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"axum",
|
||||
"chrono",
|
||||
"dotenvy",
|
||||
"serde",
|
||||
"teloxide",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ tokio = { version = "1", features = ["full"] }
|
|||
teloxide = { version = "0.13", features = ["macros"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
dotenvy = "0.15"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
chrono = "0.4"
|
||||
tracing = "0.1"
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
listen = "127.0.0.1:8123"
|
||||
data_dir = "./data"
|
||||
site_title = "ily.rs"
|
||||
site_url = "https://ily.rs"
|
||||
telegram_bot_token = "REPLACE_ME"
|
||||
telegram_chat_id = 0
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
};
|
||||
|
||||
outputs = { self, nixpkgs, crane, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
(flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
craneLib = crane.mkLib pkgs;
|
||||
|
|
@ -22,5 +22,7 @@
|
|||
devShells.default = craneLib.devShell {
|
||||
packages = with pkgs; [ cargo rustc rust-analyzer ];
|
||||
};
|
||||
});
|
||||
})) // {
|
||||
nixosModules.default = ./module.nix;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
117
module.nix
Normal file
117
module.nix
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
inherit (lib) mkOption mkEnableOption types mkIf mkMerge;
|
||||
cfg = config.services.guestbook;
|
||||
in
|
||||
{
|
||||
options.services.guestbook = {
|
||||
enable = mkEnableOption "guestbook service";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
description = "The guestbook package to use.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8123;
|
||||
description = "Port to listen on (binds to 127.0.0.1).";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/srv/guestbook/data";
|
||||
description = "Directory for guestbook entry files.";
|
||||
};
|
||||
|
||||
siteTitle = mkOption {
|
||||
type = types.str;
|
||||
default = "guestbook";
|
||||
description = "Site title shown in nav and page title.";
|
||||
};
|
||||
|
||||
siteUrl = mkOption {
|
||||
type = types.str;
|
||||
description = "Base URL of the main site (for absolute nav links).";
|
||||
};
|
||||
|
||||
telegramChatId = mkOption {
|
||||
type = types.int;
|
||||
description = "Telegram chat ID for moderation messages.";
|
||||
};
|
||||
|
||||
telegramBotTokenFile = mkOption {
|
||||
type = types.path;
|
||||
description = "Path to a file containing the Telegram bot token.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "guestbook";
|
||||
description = "User to run the service as.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "guestbook";
|
||||
description = "Group to run the service as.";
|
||||
};
|
||||
|
||||
caddy = {
|
||||
enable = mkEnableOption "Caddy reverse proxy for guestbook";
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
description = "Domain for the Caddy virtual host.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable (mkMerge [
|
||||
{
|
||||
systemd.services.guestbook = {
|
||||
description = "Guestbook for ${cfg.siteTitle}";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
BOOK_PORT = toString cfg.port;
|
||||
BOOK_DATA_DIR = cfg.dataDir;
|
||||
BOOK_SITE_TITLE = cfg.siteTitle;
|
||||
BOOK_SITE_URL = cfg.siteUrl;
|
||||
BOOK_TELEGRAM_CHAT_ID = toString cfg.telegramChatId;
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStartPre = "+${pkgs.writeShellScript "guestbook-prepare" ''
|
||||
mkdir -p ${cfg.dataDir}/entries
|
||||
chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
|
||||
''}";
|
||||
Restart = "on-failure";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ReadWritePaths = [ cfg.dataDir ];
|
||||
};
|
||||
script = ''
|
||||
export BOOK_TELEGRAM_BOT_TOKEN="$(< "${cfg.telegramBotTokenFile}")"
|
||||
exec ${cfg.package}/bin/guestbook
|
||||
'';
|
||||
};
|
||||
|
||||
users.users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
|
||||
users.groups.${cfg.group} = {};
|
||||
}
|
||||
|
||||
(mkIf cfg.caddy.enable {
|
||||
services.caddy.virtualHosts.${cfg.caddy.domain}.extraConfig = ''
|
||||
reverse_proxy localhost:${toString cfg.port}
|
||||
encode zstd gzip
|
||||
'';
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
use serde::Deserialize;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub listen: String,
|
||||
pub port: u16,
|
||||
pub data_dir: PathBuf,
|
||||
pub site_title: String,
|
||||
pub site_url: String,
|
||||
|
|
@ -12,10 +12,28 @@ pub struct Config {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let config: Config = toml::from_str(&content)?;
|
||||
Ok(config)
|
||||
pub fn listen_addr(&self) -> String {
|
||||
format!("127.0.0.1:{}", self.port)
|
||||
}
|
||||
|
||||
pub fn from_env() -> Result<Self, String> {
|
||||
Ok(Config {
|
||||
port: env::var("BOOK_PORT")
|
||||
.unwrap_or_else(|_| "8123".into())
|
||||
.parse()
|
||||
.map_err(|_| "BOOK_PORT must be a number")?,
|
||||
data_dir: env::var("BOOK_DATA_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("./data")),
|
||||
site_title: env::var("BOOK_SITE_TITLE").unwrap_or_else(|_| "guestbook".into()),
|
||||
site_url: env::var("BOOK_SITE_URL").map_err(|_| "BOOK_SITE_URL is required")?,
|
||||
telegram_bot_token: env::var("BOOK_TELEGRAM_BOT_TOKEN")
|
||||
.map_err(|_| "BOOK_TELEGRAM_BOT_TOKEN is required")?,
|
||||
telegram_chat_id: env::var("BOOK_TELEGRAM_CHAT_ID")
|
||||
.map_err(|_| "BOOK_TELEGRAM_CHAT_ID is required")?
|
||||
.parse()
|
||||
.map_err(|_| "BOOK_TELEGRAM_CHAT_ID must be an integer")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24,20 +42,54 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_config() {
|
||||
let toml_str = r#"
|
||||
listen = "127.0.0.1:8123"
|
||||
data_dir = "/var/lib/guestbook"
|
||||
site_title = "ily.rs"
|
||||
site_url = "https://ily.rs"
|
||||
telegram_bot_token = "123:ABC"
|
||||
telegram_chat_id = 12345
|
||||
"#;
|
||||
let config: Config = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(config.listen, "127.0.0.1:8123");
|
||||
assert_eq!(config.data_dir, PathBuf::from("/var/lib/guestbook"));
|
||||
assert_eq!(config.site_title, "ily.rs");
|
||||
assert_eq!(config.site_url, "https://ily.rs");
|
||||
fn test_from_env() {
|
||||
env::set_var("BOOK_PORT", "9999");
|
||||
env::set_var("BOOK_DATA_DIR", "/tmp/gb");
|
||||
env::set_var("BOOK_SITE_TITLE", "test.rs");
|
||||
env::set_var("BOOK_SITE_URL", "https://test.rs");
|
||||
env::set_var("BOOK_TELEGRAM_BOT_TOKEN", "123:ABC");
|
||||
env::set_var("BOOK_TELEGRAM_CHAT_ID", "12345");
|
||||
|
||||
let config = Config::from_env().unwrap();
|
||||
assert_eq!(config.port, 9999);
|
||||
assert_eq!(config.listen_addr(), "127.0.0.1:9999");
|
||||
assert_eq!(config.data_dir, PathBuf::from("/tmp/gb"));
|
||||
assert_eq!(config.site_title, "test.rs");
|
||||
assert_eq!(config.site_url, "https://test.rs");
|
||||
assert_eq!(config.telegram_chat_id, 12345);
|
||||
|
||||
// Clean up
|
||||
env::remove_var("BOOK_PORT");
|
||||
env::remove_var("BOOK_DATA_DIR");
|
||||
env::remove_var("BOOK_SITE_TITLE");
|
||||
env::remove_var("BOOK_SITE_URL");
|
||||
env::remove_var("BOOK_TELEGRAM_BOT_TOKEN");
|
||||
env::remove_var("BOOK_TELEGRAM_CHAT_ID");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_defaults() {
|
||||
env::set_var("BOOK_SITE_URL", "https://test.rs");
|
||||
env::set_var("BOOK_TELEGRAM_BOT_TOKEN", "123:ABC");
|
||||
env::set_var("BOOK_TELEGRAM_CHAT_ID", "12345");
|
||||
|
||||
let config = Config::from_env().unwrap();
|
||||
assert_eq!(config.port, 8123);
|
||||
assert_eq!(config.data_dir, PathBuf::from("./data"));
|
||||
assert_eq!(config.site_title, "guestbook");
|
||||
|
||||
env::remove_var("BOOK_SITE_URL");
|
||||
env::remove_var("BOOK_TELEGRAM_BOT_TOKEN");
|
||||
env::remove_var("BOOK_TELEGRAM_CHAT_ID");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_required() {
|
||||
env::remove_var("BOOK_SITE_URL");
|
||||
env::remove_var("BOOK_TELEGRAM_BOT_TOKEN");
|
||||
env::remove_var("BOOK_TELEGRAM_CHAT_ID");
|
||||
|
||||
let result = Config::from_env();
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ use teloxide::prelude::*;
|
|||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let config = config::Config::load("config.toml").expect("failed to load config.toml");
|
||||
let listen = config.listen.clone();
|
||||
dotenvy::dotenv().ok();
|
||||
let config = config::Config::from_env().expect("failed to load config");
|
||||
let listen = config.listen_addr();
|
||||
let entries_dir = config.data_dir.join("entries");
|
||||
let chat_id = ChatId(config.telegram_chat_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub fn render_page(site_title: &str, site_url: &str, entries: &[Entry], form_htm
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>guestbook - {site_title}</title>
|
||||
<title>{site_title}</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue