{ description = "Crypto alert Telegram bot that monitors Bybit prices"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; in { packages.default = pkgs.buildGoModule { pname = "crypto-alert-bot"; version = "0.1.0"; src = ./.; vendorHash = "sha256-toXJCeMHa61YCFIYtsTl4dime015AF5LlB62QmYVaA8="; subPackages = [ "cmd/app" ]; meta = with pkgs.lib; { description = "Telegram bot for Bybit cryptocurrency price alerts"; mainProgram = "app"; }; }; devShells.default = pkgs.mkShell { packages = with pkgs; [ go gopls gotools golangci-lint go-migrate # provides the `migrate` CLI ]; shellHook = '' echo "crypto-alert-bot dev shell" echo " Run: CONFIG_PATH=./internal/config/local.yml go run ./cmd/app/main.go" ''; }; } ) // { # NixOS module — import this in your NixOS config to run the bot as a service. # # The module manages PostgreSQL automatically (creates the DB and user, # connects via Unix socket with peer auth). The only settings you must # provide are the Telegram token file and optionally log/provider tuning. # # Minimal example: # services.crypto-alert-bot = { # enable = true; # telegramTokenFile = "/run/secrets/telegram-token"; # }; nixosModules.default = { config, lib, pkgs, ... }: with lib; let cfg = config.services.crypto-alert-bot; svcUser = "crypto-alert-bot"; # The config YAML is fully static (no secrets); the Telegram token is # read from a file at ExecStartPre time and written into a per-run copy. staticConfig = pkgs.writeText "crypto-alert-bot-base.yml" '' logger: service_name: "${cfg.logServiceName}" encoding: "${cfg.logEncoding}" level: "${cfg.logLevel}" postgresql: address: "/run/postgresql" user: "${svcUser}" db_name: "${cfg.dbName}" telegram: token: "__TELEGRAM_TOKEN__" providers: bybit: base_url: "${cfg.bybitBaseUrl}" ''; # Runs as root (+ prefix) before the main process. Copies the static # config into the runtime directory and splices in the Telegram token. configSetupScript = pkgs.writeShellScript "crypto-alert-bot-setup-config" '' set -euo pipefail token=$(< ${lib.escapeShellArg cfg.telegramTokenFile}) install -m 600 -o ${svcUser} ${staticConfig} /run/crypto-alert-bot/config.yml sed -i "s|__TELEGRAM_TOKEN__|$token|" /run/crypto-alert-bot/config.yml ''; in { options.services.crypto-alert-bot = { enable = mkEnableOption "crypto alert bot"; package = mkOption { type = types.package; default = self.packages.${pkgs.system}.default; defaultText = literalExpression "self.packages.\${system}.default"; description = "The crypto-alert-bot package to use."; }; dbName = mkOption { type = types.str; default = svcUser; description = "PostgreSQL database name (created automatically)."; }; telegramTokenFile = mkOption { type = types.path; description = "Path to a file containing the Telegram bot token (e.g. managed by agenix or sops-nix)."; }; logServiceName = mkOption { type = types.str; default = "alert-bot"; description = "Service name printed in log lines."; }; logEncoding = mkOption { type = types.enum [ "console" "json" ]; default = "json"; description = "Log output encoding."; }; logLevel = mkOption { type = types.enum [ "debug" "info" "warn" "error" ]; default = "info"; description = "Minimum log level."; }; bybitBaseUrl = mkOption { type = types.str; default = "https://api.bybit.com"; description = "Bybit REST API base URL."; }; }; config = mkIf cfg.enable { # Create a stable system user whose name matches the PostgreSQL role # so that peer (Unix socket) authentication works without a password. users.users.${svcUser} = { isSystemUser = true; group = svcUser; description = "Crypto alert bot service user"; }; users.groups.${svcUser} = {}; # Provision the database and role automatically. services.postgresql = { enable = true; ensureDatabases = [ cfg.dbName ]; ensureUsers = [{ name = svcUser; ensureDBOwnership = true; }]; }; systemd.services.crypto-alert-bot = { description = "Crypto Alert Telegram Bot"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "postgresql.service" ]; requires = [ "postgresql.service" ]; serviceConfig = { # '+' prefix → root, so we can write to the RuntimeDirectory # before switching to the unprivileged service user. ExecStartPre = "+${configSetupScript}"; ExecStart = "${cfg.package}/bin/app"; Environment = "CONFIG_PATH=/run/crypto-alert-bot/config.yml"; User = svcUser; Group = svcUser; RuntimeDirectory = "crypto-alert-bot"; RuntimeDirectoryMode = "0700"; Restart = "on-failure"; RestartSec = "5s"; NoNewPrivileges = true; PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; }; }; }; }; }; }