Package with nix and add systemd unit example

This commit is contained in:
oxalica 2024-08-31 15:40:41 -04:00
parent a63d0df443
commit e84b13c876
9 changed files with 287 additions and 3 deletions

2
.gitignore vendored
View file

@ -3,3 +3,5 @@
*.key *.key
config.toml config.toml
result
result-*

View file

@ -20,7 +20,9 @@ use tokio::runtime::Runtime;
/// NB. Sync with docs of [`User::url`]. /// NB. Sync with docs of [`User::url`].
const KEY_URL_SUBPATH: &str = "/.well-known/blah/key"; const KEY_URL_SUBPATH: &str = "/.well-known/blah/key";
/// Control or manage Blah Chat Server.
#[derive(Debug, clap::Parser)] #[derive(Debug, clap::Parser)]
#[clap(about, version = option_env!("CFG_RELEASE").unwrap_or(env!("CARGO_PKG_VERSION")))]
struct Cli { struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Command, command: Command,

View file

@ -0,0 +1,46 @@
[Unit]
Description=Blah Chat Server
After=network.target
[Service]
Type=notify
ExecStart=/usr/bin/blahd serve --config ${CONFIGURATION_DIRECTORY}/blahd.toml
ConfigurationDirectory=blahd
StateDirectory=blahd
Restart=always
RestartSec=10s
# Permission and capabilities
DynamicUser=yes
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# 0640 / 0750
UMask=0027
# Sandboxing
# Mostly copied from: https://github.com/NixOS/nixpkgs/blob/6414ef7ca3bf18ec4f9628d09ccc1eb030276ee2/nixos/modules/services/web-servers/nginx/default.nix#L1246
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
PrivateDevices=yes
PrivateMounts=yes
PrivateUsers=yes
ProcSubset=pid
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectProc=invisible
ProtectProc=invisible
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged

View file

@ -1,8 +1,12 @@
# The example configuration file, required options are documented as
# `(Required)`, other options are optional and the example value given here is
# the default value.
[database] [database]
# (Required)
# The path to the main SQLite database. # The path to the main SQLite database.
# It will be created and initialized if not exist. # The file will be created and initialized if not exist, but missing directory
path = "/path/to/db.sqlite" # will not.
path = "/var/lib/blahd/db.sqlite"
[server] [server]

View file

@ -11,9 +11,11 @@ pub struct Config {
pub server: ServerConfig, pub server: ServerConfig,
} }
#[serde_inline_default]
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct DatabaseConfig { pub struct DatabaseConfig {
#[serde_inline_default("/var/lib/blahd/db.sqlite".into())]
pub path: PathBuf, pub path: PathBuf,
} }

View file

@ -31,7 +31,9 @@ mod middleware;
mod config; mod config;
mod utils; mod utils;
/// Blah Chat Server
#[derive(Debug, clap::Parser)] #[derive(Debug, clap::Parser)]
#[clap(about, version = option_env!("CFG_RELEASE").unwrap_or(env!("CARGO_PKG_VERSION")))]
enum Cli { enum Cli {
/// Run the server with given configuration. /// Run the server with given configuration.
Serve { Serve {

48
flake.lock generated Normal file
View file

@ -0,0 +1,48 @@
{
"nodes": {
"naersk": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1721727458,
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
"owner": "nix-community",
"repo": "naersk",
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1724819573,
"narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "71e91c409d1e654808b2621f28a327acfdad8dc2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"naersk": "naersk",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

80
flake.nix Normal file
View file

@ -0,0 +1,80 @@
rec {
description = "Blah Chat Server";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
naersk = {
url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
self,
nixpkgs,
naersk,
}:
let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs lib.systems.flakeExposed;
rev = self.rev or (lib.warn "Git changes are not committed" (self.dirtyRev or "dirty"));
in
{
packages = eachSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
naersk' = pkgs.callPackage naersk { };
in
rec {
default = blahd;
blahd = pkgs.callPackage (
{
pkg-config,
openssl,
sqlite,
}:
naersk'.buildPackage rec {
pname = "blahd";
src = ./.;
version = "git-${rev}";
CFG_RELEASE = version;
nativeBuildInputs = [
pkg-config
];
buildInputs = [
openssl
sqlite
];
cargoBuildOptions = opts: opts ++ [
"--package=blahd"
"--package=blahctl"
];
postInstall = ''
mkdir -p $out/etc/systemd/system
substitute ./blahd/blahd.example.service $out/etc/systemd/system/blahd.service \
--replace-fail '/usr/bin/blahd' "$out/bin/blahd"
'';
meta = {
inherit description;
homepage = "https://github.com/Blah-IM/blahrs";
};
}
) { };
}
);
nixosModules = rec {
default = blahd;
blahd = import ./nix/module.nix {
inherit self;
};
};
};
}

98
nix/module.nix Normal file
View file

@ -0,0 +1,98 @@
{ self }:
{
lib,
config,
pkgs,
...
}:
let
inherit (lib)
literalMD
mdDoc
mkEnableOption
mkIf
mkOption
types
;
cfg = config.services.blahd;
toml = pkgs.formats.toml { };
mkConfigFile =
name: config:
(toml.generate name config).overrideAttrs (old: {
buildCommand =
old.buildCommand
+ ''
${lib.getBin cfg.package}/bin/blahd validate --config $out
'';
});
settingsType = types.submodule {
freeformType = toml.type;
# TODO: Auto-generate these options? Now only required options are documented.
options = {
database.path = mkOption {
type = types.path;
default = "/var/lib/blahd/db.sqlite";
};
server.listen = mkOption {
type = types.str;
example = "localhost:8080";
};
server.base_url = mkOption {
type = types.str;
example = "http://localhost:8080";
};
};
};
in
{
options.services.blahd = {
enable = mkEnableOption "Blah Chat Server";
package = mkOption {
description = mdDoc "The blahd package to use.";
type = types.package;
default = self.packages.${pkgs.system}.blahd;
defaultText = literalMD "blahd package from its flake output";
};
settings = mkOption {
description = ''
blahd configuration.
Will be ignored if `settingsFile` is non-null.
'';
type = settingsType;
};
settingsFile = mkOption {
description = ''
blahd configuration file path.
If non-null, this will be used and `settings` will be ignored.
'';
type = types.nullOr types.path;
defaultText = literalMD "generated from `settings`";
default = mkConfigFile "blahd.toml" cfg.settings;
};
};
config = mkIf cfg.enable {
systemd.packages = [ cfg.package ];
environment.systemPackages = [ cfg.package ];
systemd.services."blahd" = {
overrideStrategy = "asDropin";
wantedBy = [ "multi-user.target" ];
restartIfChanged = false;
stopIfChanged = false;
};
environment.etc."blahd/blahd.toml".source = cfg.settingsFile;
};
}