A personal podcast client
1{ self, ... }:
2{
3 flake.nixosModules.default =
4 {
5 config,
6 lib,
7 pkgs,
8 ...
9 }:
10 let
11 cfg = config.services.podcasts;
12 podcasts = self.outputs.packages."${pkgs.stdenv.hostPlatform.system}".default;
13 stateDirectory = "/var/lib/podcasts/";
14 podcastDir = "${cfg.annexDir}/${cfg.podcastSubdir}";
15 commonServiceConfig = {
16 StateDirectory = "podcasts";
17 # TODO more hardening
18 PrivateTmp = true;
19 RemoveIPC = true;
20 NoNewPrivileges = true;
21 ProtectSystem = "strict";
22 ProtectHome = if lib.hasPrefix "/home" podcastDir then "tmpfs" else "true";
23 RestrictSUIDSGID = true;
24 };
25 environment = {
26 PODCASTS_ANNEX_DIR = podcastDir;
27 PODCASTS_DATA_DIR = cfg.dataDir;
28 PODCASTS_DOMAIN = "https://podcasts.peterrice.xyz";
29 };
30 in
31 {
32 options = {
33 services.podcasts = with lib; {
34 annexDir = mkOption {
35 type = types.str;
36 default = stateDirectory + "annex";
37 };
38 podcastSubdir = mkOption {
39 type = types.str;
40 default = "hosted-podcasts";
41 };
42 dataDir = mkOption {
43 type = types.str;
44 default = stateDirectory;
45 };
46 fetch = {
47 enable = mkEnableOption "fetch-podcasts";
48 user = mkOption {
49 type = types.str;
50 default = "podcasts";
51 };
52 group = mkOption {
53 type = types.str;
54 default = "podcasts";
55 };
56 startAt = mkOption {
57 type = with types; either str (listOf str);
58 default = "daily";
59 };
60 };
61 serve = {
62 enable = mkEnableOption "serve-podcasts";
63 user = mkOption {
64 type = types.str;
65 default = "podcasts";
66 };
67 group = mkOption {
68 type = types.str;
69 default = "podcasts";
70 };
71 bind = mkOption {
72 type = types.str;
73 default = "127.0.0.1:5998";
74 };
75 timeout = mkOption {
76 type = types.int;
77 default = 30;
78 };
79 };
80 };
81 };
82 config = lib.mkIf (cfg.fetch.enable || cfg.serve.enable) {
83 systemd.services.fetch-podcasts = {
84 inherit (cfg.fetch) enable startAt;
85 inherit environment;
86 path = [
87 pkgs.git
88 pkgs.git-annex
89 ];
90 serviceConfig = commonServiceConfig // {
91 User = cfg.fetch.user;
92 Group = cfg.fetch.group;
93 Type = "oneshot";
94 BindPaths = [
95 cfg.annexDir
96 cfg.dataDir
97 ];
98 ExecStart = "${podcasts}/bin/fetch-podcasts";
99 };
100 };
101 systemd.services.serve-podcasts = {
102 inherit (cfg.serve) enable;
103 serviceConfig = commonServiceConfig // {
104 User = cfg.serve.user;
105 Group = cfg.serve.group;
106 BindReadOnlyPaths = [
107 podcastDir
108 cfg.dataDir
109 ];
110 ExecStart = ''
111 ${podcasts.python.pkgs.gunicorn}/bin/gunicorn -b ${cfg.serve.bind} -t ${toString cfg.serve.timeout} podcasts.serve:app
112 '';
113 };
114 environment = environment // {
115 PYTHONPATH = "${podcasts.python.pkgs.makePythonPath podcasts.propagatedBuildInputs}:${podcasts.outPath}/${podcasts.python.sitePackages}";
116 };
117 wantedBy = [ "multi-user.target" ];
118 after = [ "network.target" ] ++ lib.optional cfg.fetch.enable "fetch-podcasts.timer";
119 };
120
121 users.users = lib.mkIf (cfg.fetch.user == "podcasts" || cfg.serve.user == "podcasts") {
122 podcasts = {
123 isSystemUser = true;
124 group = "podcasts";
125 home = stateDirectory;
126 };
127 };
128
129 users.groups = lib.mkIf (cfg.fetch.group == "podcasts" || cfg.serve.group == "podcasts") {
130 podcasts = { };
131 };
132 };
133 };
134}