feat: add asciinema server module and CLI tool
All checks were successful
ci / treefmt (push) Successful in 1m59s

This commit is contained in:
Ceferino Patino 2026-06-27 16:04:16 -05:00
commit 34be676dd3
Signed by: c4patino
SSH key fingerprint: SHA256:Wu+cU1t+7zVT9wzew/4meNmeulo66NzMqc22MdDBgXI
13 changed files with 246 additions and 60 deletions

172
flake.lock generated
View file

@ -1,5 +1,25 @@
{ {
"nodes": { "nodes": {
"asciinema": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1782462228,
"narHash": "sha256-uVOzyBA0KorKZRxC8QEjaO6FdFSLmhDwdXxutisVyD8=",
"owner": "asciinema",
"repo": "asciinema-server",
"rev": "38a908d8cf3312bbdfd18cf47d58eb42046a2655",
"type": "github"
},
"original": {
"owner": "asciinema",
"ref": "main",
"repo": "asciinema-server",
"type": "github"
}
},
"base16": { "base16": {
"inputs": { "inputs": {
"fromYaml": "fromYaml" "fromYaml": "fromYaml"
@ -86,7 +106,7 @@
"deploy-rs": { "deploy-rs": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs_2",
"utils": "utils" "utils": "utils"
}, },
"locked": { "locked": {
@ -105,7 +125,7 @@
}, },
"devshell": { "devshell": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_3"
}, },
"locked": { "locked": {
"lastModified": 1768818222, "lastModified": 1768818222,
@ -326,14 +346,14 @@
}, },
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems_3" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1694529238, "lastModified": 1731533236,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -344,7 +364,7 @@
}, },
"flake-utils-plus": { "flake-utils-plus": {
"inputs": { "inputs": {
"flake-utils": "flake-utils" "flake-utils": "flake-utils_2"
}, },
"locked": { "locked": {
"lastModified": 1715533576, "lastModified": 1715533576,
@ -363,7 +383,25 @@
}, },
"flake-utils_2": { "flake-utils_2": {
"inputs": { "inputs": {
"systems": "systems_6" "systems": "systems_4"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_7"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1731533236,
@ -457,7 +495,7 @@
"impermanence": { "impermanence": {
"inputs": { "inputs": {
"home-manager": "home-manager_2", "home-manager": "home-manager_2",
"nixpkgs": "nixpkgs_3" "nixpkgs": "nixpkgs_4"
}, },
"locked": { "locked": {
"lastModified": 1769548169, "lastModified": 1769548169,
@ -476,8 +514,8 @@
"nix-minecraft": { "nix-minecraft": {
"inputs": { "inputs": {
"flake-compat": "flake-compat_2", "flake-compat": "flake-compat_2",
"nixpkgs": "nixpkgs_4", "nixpkgs": "nixpkgs_5",
"systems": "systems_2" "systems": "systems_3"
}, },
"locked": { "locked": {
"lastModified": 1780375694, "lastModified": 1780375694,
@ -516,15 +554,15 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1743014863, "lastModified": 1780336545,
"narHash": "sha256-jAIUqsiN2r3hCuHji80U7NNEafpIMBXiwKlSrjWMlpg=", "narHash": "sha256-vhVhuXzFrIOfcssC/9hDHx7MHzDKjF3keHuREOQqQiQ=",
"owner": "NixOS", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "bd3bac8bfb542dbde7ffffb6987a1a1f9d41699f", "rev": "4df1b885d76a54e1aa1a318f8d16fd6005b6401f",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "nixos",
"ref": "nixpkgs-unstable", "ref": "nixpkgs-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
@ -562,6 +600,22 @@
} }
}, },
"nixpkgs_10": { "nixpkgs_10": {
"locked": {
"lastModified": 1780336545,
"narHash": "sha256-vhVhuXzFrIOfcssC/9hDHx7MHzDKjF3keHuREOQqQiQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4df1b885d76a54e1aa1a318f8d16fd6005b6401f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_11": {
"locked": { "locked": {
"lastModified": 1770107345, "lastModified": 1770107345,
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=", "narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
@ -578,6 +632,22 @@
} }
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": {
"lastModified": 1743014863,
"narHash": "sha256-jAIUqsiN2r3hCuHji80U7NNEafpIMBXiwKlSrjWMlpg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bd3bac8bfb542dbde7ffffb6987a1a1f9d41699f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1762156382, "lastModified": 1762156382,
"narHash": "sha256-Yg7Ag7ov5+36jEFC1DaZh/12SEXo6OO3/8rqADRxiqs=", "narHash": "sha256-Yg7Ag7ov5+36jEFC1DaZh/12SEXo6OO3/8rqADRxiqs=",
@ -593,7 +663,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_3": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1768564909, "lastModified": 1768564909,
"narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=", "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
@ -609,7 +679,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_4": { "nixpkgs_5": {
"locked": { "locked": {
"lastModified": 1769461804, "lastModified": 1769461804,
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
@ -625,7 +695,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_5": { "nixpkgs_6": {
"locked": { "locked": {
"lastModified": 1780902259, "lastModified": 1780902259,
"narHash": "sha256-q8yYEC5f1mFlQO9RGna4LTc9QrcvWunX6FYp83munkQ=", "narHash": "sha256-q8yYEC5f1mFlQO9RGna4LTc9QrcvWunX6FYp83munkQ=",
@ -641,7 +711,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_6": { "nixpkgs_7": {
"locked": { "locked": {
"lastModified": 1770107345, "lastModified": 1770107345,
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=", "narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
@ -657,7 +727,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_7": { "nixpkgs_8": {
"locked": { "locked": {
"lastModified": 1775710090, "lastModified": 1775710090,
"narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=", "narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=",
@ -673,7 +743,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_8": { "nixpkgs_9": {
"locked": { "locked": {
"lastModified": 1767026758, "lastModified": 1767026758,
"narHash": "sha256-7fsac/f7nh/VaKJ/qm3I338+wAJa/3J57cOGpXi0Sbg=", "narHash": "sha256-7fsac/f7nh/VaKJ/qm3I338+wAJa/3J57cOGpXi0Sbg=",
@ -689,27 +759,11 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_9": {
"locked": {
"lastModified": 1780336545,
"narHash": "sha256-vhVhuXzFrIOfcssC/9hDHx7MHzDKjF3keHuREOQqQiQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4df1b885d76a54e1aa1a318f8d16fd6005b6401f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixvim": { "nixvim": {
"inputs": { "inputs": {
"flake-parts": "flake-parts_3", "flake-parts": "flake-parts_3",
"nixpkgs": "nixpkgs_9", "nixpkgs": "nixpkgs_10",
"systems": "systems_7" "systems": "systems_8"
}, },
"locked": { "locked": {
"lastModified": 1780235872, "lastModified": 1780235872,
@ -752,6 +806,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"asciinema": "asciinema",
"deploy-rs": "deploy-rs", "deploy-rs": "deploy-rs",
"devshell": "devshell", "devshell": "devshell",
"disko": "disko", "disko": "disko",
@ -760,7 +815,7 @@
"impermanence": "impermanence", "impermanence": "impermanence",
"nix-minecraft": "nix-minecraft", "nix-minecraft": "nix-minecraft",
"nixos-wsl": "nixos-wsl", "nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs_5", "nixpkgs": "nixpkgs_6",
"nixpkgs-unstable": "nixpkgs-unstable", "nixpkgs-unstable": "nixpkgs-unstable",
"snowfall-lib": "snowfall-lib", "snowfall-lib": "snowfall-lib",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
@ -827,7 +882,7 @@
"nixpkgs" "nixpkgs"
], ],
"nur": "nur", "nur": "nur",
"systems": "systems_4", "systems": "systems_5",
"tinted-kitty": "tinted-kitty", "tinted-kitty": "tinted-kitty",
"tinted-schemes": "tinted-schemes", "tinted-schemes": "tinted-schemes",
"tinted-tmux": "tinted-tmux", "tinted-tmux": "tinted-tmux",
@ -909,6 +964,21 @@
} }
}, },
"systems_5": { "systems_5": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_6": {
"locked": { "locked": {
"lastModified": 1689347949, "lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
@ -923,7 +993,7 @@
"type": "github" "type": "github"
} }
}, },
"systems_6": { "systems_7": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
@ -938,7 +1008,7 @@
"type": "github" "type": "github"
} }
}, },
"systems_7": { "systems_8": {
"locked": { "locked": {
"lastModified": 1774449309, "lastModified": 1774449309,
"narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=", "narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=",
@ -1020,7 +1090,7 @@
}, },
"treefmt-nix": { "treefmt-nix": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_6" "nixpkgs": "nixpkgs_7"
}, },
"locked": { "locked": {
"lastModified": 1780220602, "lastModified": 1780220602,
@ -1038,7 +1108,7 @@
}, },
"treefmt-nix_2": { "treefmt-nix_2": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_10" "nixpkgs": "nixpkgs_11"
}, },
"locked": { "locked": {
"lastModified": 1780220602, "lastModified": 1780220602,
@ -1056,7 +1126,7 @@
}, },
"utils": { "utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems_2"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1731533236,
@ -1075,8 +1145,8 @@
"walker": { "walker": {
"inputs": { "inputs": {
"elephant": "elephant", "elephant": "elephant",
"nixpkgs": "nixpkgs_7", "nixpkgs": "nixpkgs_8",
"systems": "systems_5" "systems": "systems_6"
}, },
"locked": { "locked": {
"lastModified": 1777789924, "lastModified": 1777789924,
@ -1096,7 +1166,7 @@
"inputs": { "inputs": {
"crane": "crane", "crane": "crane",
"flake-parts": "flake-parts_2", "flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_8", "nixpkgs": "nixpkgs_9",
"xremap": "xremap_2" "xremap": "xremap_2"
}, },
"locked": { "locked": {
@ -1132,7 +1202,7 @@
}, },
"yumevim-nix": { "yumevim-nix": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils_3",
"nixpkgs": [ "nixpkgs": [
"nixpkgs-unstable" "nixpkgs-unstable"
], ],

View file

@ -13,9 +13,11 @@
deploy-rs.url = "github:serokell/deploy-rs"; deploy-rs.url = "github:serokell/deploy-rs";
devshell.url = "github:numtide/devshell"; devshell.url = "github:numtide/devshell";
treefmt-nix.url = "github:numtide/treefmt-nix";
asciinema.url = "github:asciinema/asciinema-server/main";
impermanence.url = "github:nix-community/impermanence"; impermanence.url = "github:nix-community/impermanence";
nix-minecraft.url = "github:Infinidoge/nix-minecraft"; nix-minecraft.url = "github:Infinidoge/nix-minecraft";
treefmt-nix.url = "github:numtide/treefmt-nix";
walker.url = "github:abenz1267/walker"; walker.url = "github:abenz1267/walker";
xremap.url = "github:xremap/nix-flake"; xremap.url = "github:xremap/nix-flake";

View file

@ -42,6 +42,7 @@ in {
}; };
tools = { tools = {
asciinema = enabled;
presenterm = enabled; presenterm = enabled;
rustypaste = enabled; rustypaste = enabled;
}; };

View file

@ -37,6 +37,7 @@ in {
}; };
tools = { tools = {
asciinema = enabled;
presenterm = enabled; presenterm = enabled;
rustypaste = enabled; rustypaste = enabled;
}; };

View file

@ -0,0 +1,32 @@
{
config,
lib,
namespace,
pkgs,
...
}: let
inherit (lib) mkIf mkEnableOption;
inherit (lib.${namespace}) getAttrByNamespace mkOptionsWithNamespace;
base = "${namespace}.cli.tools.asciinema";
cfg = getAttrByNamespace config base;
in {
options = mkOptionsWithNamespace base {
enable = mkEnableOption "asciinema";
};
config = mkIf cfg.enable {
home = {
packages = with pkgs; [
asciinema
];
file = let
crypt = "${config.snowfallorg.user.home.directory}/dotfiles/secrets/crypt";
in {
".config/asciinema/config.toml".source =
"${crypt}/asciinema/config.toml"
|> config.lib.file.mkOutOfStoreSymlink;
};
};
};
}

View file

@ -86,6 +86,7 @@ in {
tsuki = [ tsuki = [
"forgejo" "forgejo"
"grafana" "grafana"
"asciinema"
"terraform" "terraform"
"vaultwarden" "vaultwarden"
]; ];

View file

@ -60,6 +60,11 @@ in {
}; };
tsuki = { tsuki = {
asciinema = {
port = 4000;
public = true;
websocket.enable = true;
};
dash = { dash = {
port = 5150; port = 5150;
internal = true; internal = true;
@ -73,7 +78,10 @@ in {
port = 8096; port = 8096;
internal = true; internal = true;
public = true; public = true;
websocket = true; websocket = {
enable = true;
path = "/socket";
};
}; };
paste = { paste = {
port = 5100; port = 5100;

View file

@ -0,0 +1,66 @@
{
config,
inputs,
lib,
namespace,
pkgs,
...
}: let
inherit (lib) mkIf mkForce;
inherit (lib.${namespace}) getAttrByNamespace getIn hostHasService readJsonOrEmpty resolveDatabaseHost resolveDatabaseIP resolveServicePort;
inherit (config.networking) hostName;
networkCfg = getAttrByNamespace config "${namespace}.services.networking";
pgCfg = getAttrByNamespace config "${namespace}.services.storage.postgresql";
isEnabled = hostHasService networkCfg.network-services hostName "asciinema";
port = resolveServicePort networkCfg.network-services "asciinema" 4000;
in {
imports = [
inputs.asciinema.nixosModules.default
];
config = mkIf isEnabled {
services.asciinema = {
enable = true;
database.createLocally = false;
environment = {
BIND_ALL = true;
PORT = port;
URL_HOST = "asciinema.cpatino.com";
URL_SCHEME = "https";
SIGN_UP_DISABLED = true;
};
environmentFile = let
secrets = readJsonOrEmpty "${inputs.self}/secrets/crypt/secrets.json";
ip = resolveDatabaseIP networkCfg.devices pgCfg.databases "asciinema";
password = getIn "postgresql.asciinema.password" secrets;
in
pkgs.writeText "asciinema.env" ''
DATABASE_URL=ecto://asciinema:${password}@${ip}:5600/asciinema
'';
};
networking.firewall.allowedTCPPorts = [port];
systemd.services.asciinema-server = mkIf (resolveDatabaseHost pgCfg.databases "asciinema" == hostName) {
after = ["postgresql.service" "pgbouncer.service"];
requires = ["postgresql.service" "pgbouncer.service"];
serviceConfig = {
RestartSec = mkForce "1s";
};
};
${namespace}.services.storage.impermanence.folders = [
{
directory = "/var/lib/asciinema";
user = "asciinema";
group = "asciinema";
mode = "700";
}
];
};
}

View file

@ -5,7 +5,7 @@
... ...
}: let }: let
inherit (lib) types mkOption optional optionalString concatStringsSep; inherit (lib) types mkOption optional optionalString concatStringsSep;
inherit (lib.${namespace}) getAttrByNamespace mkOptionsWithNamespace resolveHostIP flattenHostServices mkRequiredOpt mkBoolOpt mkOptAttrset mkListOpt; inherit (lib.${namespace}) getAttrByNamespace mkOptionsWithNamespace resolveHostIP flattenHostServices mkRequiredOpt mkBoolOpt mkOpt mkOptAttrset mkListOpt;
base = "${namespace}.services.networking"; base = "${namespace}.services.networking";
cfg = getAttrByNamespace config base; cfg = getAttrByNamespace config base;
@ -24,7 +24,12 @@ in {
port = mkRequiredOpt port "Local port of the service. If not specified, the module's default is used."; port = mkRequiredOpt port "Local port of the service. If not specified, the module's default is used.";
public = mkBoolOpt false "Whether the service should be publicly accessible over *.cpatino.com."; public = mkBoolOpt false "Whether the service should be publicly accessible over *.cpatino.com.";
internal = mkBoolOpt false "Whether the service should be internally accessible over *.yumeami.sh."; internal = mkBoolOpt false "Whether the service should be internally accessible over *.yumeami.sh.";
websocket = mkBoolOpt false "Whether to enable websocket endpoints for the service."; websocket = mkOpt (submodule {
options = {
enable = mkBoolOpt false "Whether to enable websocket endpoints for the service.";
path = mkOpt str "/ws/" "WebSocket path prefix for the service.";
};
}) {} "WebSocket configuration for the service.";
}; };
})); }));
default = {}; default = {};

View file

@ -70,10 +70,10 @@
''; '';
websocketConfig = optionalString service.websocket '' websocketConfig = optionalString service.websocket.enable ''
# --- ${name} (websocket access) --- # --- ${name} (websocket access) ---
ProxyPass /socket ws://${hostIP}:${p}/socket connectiontimeout=30 timeout=300 retry=0 ProxyPass ${service.websocket.path} ws://${hostIP}:${p}${service.websocket.path} connectiontimeout=30 timeout=300 retry=0
ProxyPassReverse /socket ws://${hostIP}:${p}/socket ProxyPassReverse ${service.websocket.path} ws://${hostIP}:${p}${service.websocket.path}
''; '';
in { in {

View file

@ -5,8 +5,8 @@
namespace, namespace,
... ...
}: let }: let
inherit (lib) types mkIf mkEnableOption mkOption concatLists; inherit (lib) types mkIf mkEnableOption concatLists;
inherit (lib.${namespace}) getAttrByNamespace mkOptionsWithNamespace mkOpt mkRequiredOpt mkNullableOpt mkListOpt; inherit (lib.${namespace}) getAttrByNamespace mkOptionsWithNamespace mkRequiredOpt mkNullableOpt mkListOpt;
base = "${namespace}.services.storage.impermanence"; base = "${namespace}.services.storage.impermanence";
cfg = getAttrByNamespace config base; cfg = getAttrByNamespace config base;
in { in {

Binary file not shown.

Binary file not shown.