add resources flow and secure config
Document resources, extend list actions, and wire Nix settings/token handling to support new resource saves and UI tweaks.
This commit is contained in:
parent
47a712b4ae
commit
6f3c5a6894
4 changed files with 916 additions and 85 deletions
6
AGENTS.md
Normal file
6
AGENTS.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## NixOS Module Notes
|
||||||
|
|
||||||
|
- `services.readlater-bot.settings` is rendered to TOML without the token; the token must come from `services.readlater-bot.tokenFile` and is combined at runtime in `/run/readlater-bot/config.toml` to keep secrets out of the Nix store.
|
||||||
|
- If you override `services.readlater-bot.user`/`group`, ensure the group exists; otherwise systemd fails at step GROUP. Defaults only auto-create the `readlater-bot` user/group when you keep the defaults.
|
||||||
11
SPEC.md
11
SPEC.md
|
|
@ -17,6 +17,16 @@ Read Later behavior
|
||||||
- New items are prepended (inserted immediately after any preamble).
|
- New items are prepended (inserted immediately after any preamble).
|
||||||
- Deduping: exact full-block match (entire entry text). If identical block exists, skip add and inform user.
|
- Deduping: exact full-block match (entire entry text). If identical block exists, skip add and inform user.
|
||||||
- Add acknowledgment: send Saved. and auto-delete after 5s.
|
- Add acknowledgment: send Saved. and auto-delete after 5s.
|
||||||
|
- After single-item or multi-item saves, delete the user's original message.
|
||||||
|
|
||||||
|
Resources behavior
|
||||||
|
- /add <text> prompts for Reading list vs Resource.
|
||||||
|
- Add Resource is available in the selected item view; it does not change the current view.
|
||||||
|
- Resource adds prompt for a target .md file in resources_path (or a new filename).
|
||||||
|
- New resource entry is prepended to the chosen file as: `- (Auto-Resource): <message contents>`.
|
||||||
|
- Preserve additional lines after the first.
|
||||||
|
- Deduping: exact full-block match (entire entry text). If identical block exists, skip add and inform user.
|
||||||
|
- Resource acknowledgment: send Added to resources. and auto-delete after 5s.
|
||||||
|
|
||||||
Finished Reading behavior
|
Finished Reading behavior
|
||||||
- Mark Finished moves an entry: remove from Read Later, prepend to Finished (no separators).
|
- Mark Finished moves an entry: remove from Read Later, prepend to Finished (no separators).
|
||||||
|
|
@ -63,6 +73,7 @@ Config
|
||||||
- user_id (Telegram user ID)
|
- user_id (Telegram user ID)
|
||||||
- read_later_path (absolute path)
|
- read_later_path (absolute path)
|
||||||
- finished_path (absolute path)
|
- finished_path (absolute path)
|
||||||
|
- resources_path (absolute path to resources directory)
|
||||||
- data_dir (absolute path)
|
- data_dir (absolute path)
|
||||||
- retry_interval_seconds (default 30, configurable)
|
- retry_interval_seconds (default 30, configurable)
|
||||||
|
|
||||||
|
|
|
||||||
75
flake.nix
75
flake.nix
|
|
@ -38,6 +38,16 @@
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.services.readlater-bot;
|
cfg = config.services.readlater-bot;
|
||||||
|
tomlFormat = pkgs.formats.toml { };
|
||||||
|
defaultSettings = {
|
||||||
|
data_dir = "/var/lib/readlater-bot";
|
||||||
|
retry_interval_seconds = 30;
|
||||||
|
};
|
||||||
|
mergedSettings = defaultSettings // cfg.settings;
|
||||||
|
settingsFile = tomlFormat.generate "readlater-bot.toml" mergedSettings;
|
||||||
|
runtimeConfig = "/run/readlater-bot/config.toml";
|
||||||
|
useRuntimeConfig = cfg.configFile == null;
|
||||||
|
configPath = if useRuntimeConfig then runtimeConfig else cfg.configFile;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.services.readlater-bot = {
|
options.services.readlater-bot = {
|
||||||
|
|
@ -47,22 +57,81 @@
|
||||||
default = self.packages.${pkgs.system}.default;
|
default = self.packages.${pkgs.system}.default;
|
||||||
description = "Package providing the bot binary.";
|
description = "Package providing the bot binary.";
|
||||||
};
|
};
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = tomlFormat.type;
|
||||||
|
default = { };
|
||||||
|
description = "TOML settings without the token.";
|
||||||
|
};
|
||||||
|
tokenFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Path to a file containing the Telegram bot token.";
|
||||||
|
};
|
||||||
configFile = lib.mkOption {
|
configFile = lib.mkOption {
|
||||||
type = lib.types.path;
|
type = lib.types.nullOr lib.types.str;
|
||||||
description = "Path to TOML config file.";
|
default = null;
|
||||||
|
description = "Path to a TOML config file (bypasses settings/tokenFile).";
|
||||||
|
};
|
||||||
|
user = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "readlater-bot";
|
||||||
|
description = "User account for the bot service.";
|
||||||
|
};
|
||||||
|
group = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "readlater-bot";
|
||||||
|
description = "Group for the bot service.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.configFile != null || cfg.tokenFile != null;
|
||||||
|
message = "services.readlater-bot: set tokenFile with settings, or provide configFile.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !(cfg.settings ? token);
|
||||||
|
message = "services.readlater-bot: do not set settings.token; use tokenFile.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = cfg.configFile == null || (cfg.settings == { } && cfg.tokenFile == null);
|
||||||
|
message = "services.readlater-bot: when configFile is set, do not set settings or tokenFile.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
users.users = lib.mkIf (cfg.user == "readlater-bot") {
|
||||||
|
readlater-bot = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = cfg.group;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
users.groups = lib.mkIf (cfg.group == "readlater-bot") {
|
||||||
|
readlater-bot = { };
|
||||||
|
};
|
||||||
|
|
||||||
systemd.services.readlater-bot = {
|
systemd.services.readlater-bot = {
|
||||||
description = "Read Later Telegram bot";
|
description = "Read Later Telegram bot";
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "network-online.target" ];
|
after = [ "network-online.target" ];
|
||||||
wants = [ "network-online.target" ];
|
wants = [ "network-online.target" ];
|
||||||
|
preStart = lib.optionalString useRuntimeConfig ''
|
||||||
|
umask 0077
|
||||||
|
{
|
||||||
|
printf 'token = "%s"\n' "$(cat ${cfg.tokenFile})"
|
||||||
|
cat ${settingsFile}
|
||||||
|
} > ${runtimeConfig}
|
||||||
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${cfg.package}/bin/readlater-bot --config ${cfg.configFile}";
|
ExecStart = "${cfg.package}/bin/readlater-bot --config ${configPath}";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = 5;
|
RestartSec = 5;
|
||||||
|
User = cfg.user;
|
||||||
|
Group = cfg.group;
|
||||||
|
RuntimeDirectory = "readlater-bot";
|
||||||
|
RuntimeDirectoryMode = "0700";
|
||||||
|
StateDirectory = "readlater-bot";
|
||||||
|
StateDirectoryMode = "0700";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
909
src/main.rs
909
src/main.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue