main
  1{
  2  lib,
  3  stdenv,
  4  globals,
  5}:
  6let
  7  # Service category definitions with metadata
  8  serviceCategories = {
  9    media = {
 10      title = "Media Services";
 11      services = [
 12        "jellyfin"
 13        "jellyseerr"
 14        "immich"
 15      ];
 16      descriptions = {
 17        jellyfin = "Media server for movies, TV shows, and music";
 18        jellyseerr = "Request management for media";
 19        immich = "Photo and video backup";
 20      };
 21    };
 22
 23    downloads = {
 24      title = "Download & Management";
 25      services = [
 26        "transmission"
 27        "sonarr"
 28        "radarr"
 29        "lidarr"
 30        "bazarr"
 31      ];
 32      descriptions = {
 33        transmission = "BitTorrent client";
 34        sonarr = "TV show management";
 35        radarr = "Movie management";
 36        lidarr = "Music management";
 37        bazarr = "Subtitle management";
 38      };
 39    };
 40
 41    utilities = {
 42      title = "Utilities";
 43      services = [
 44        "kiwix"
 45        "n8n"
 46        "paperless"
 47        "grafana"
 48      ];
 49      descriptions = {
 50        kiwix = "Offline Wikipedia and content";
 51        n8n = "Workflow automation";
 52        paperless = "Document management";
 53        grafana = "Monitoring and metrics";
 54      };
 55    };
 56  };
 57
 58  # Extract syncthing machines from globals
 59  syncthingMachines = lib.filterAttrs (
 60    _name: machine: machine ? syncthing && machine.syncthing ? folders
 61  ) globals.machines;
 62
 63  # Generate syncthing service entries
 64  syncthingServices = [
 65    {
 66      name = "Syncthing (Overview)";
 67      url = "https://syncthing.sbr.pm";
 68      description = "File synchronization overview";
 69    }
 70  ]
 71  ++ (lib.mapAttrsToList (name: _machine: {
 72    name = "Syncthing (${name})";
 73    url = "https://syncthing.sbr.pm/${name}";
 74    description = "Syncthing on ${name}";
 75  }) syncthingMachines);
 76
 77  # Generate service entries from service list
 78  mkServiceEntry =
 79    category: serviceName:
 80    let
 81      # Capitalize first letter
 82      capitalize = str: (lib.toUpper (lib.substring 0 1 str)) + (lib.substring 1 (-1) str);
 83      displayName = capitalize serviceName;
 84      description =
 85        category.descriptions.${serviceName} or "Service on ${globals.services.${serviceName}.host}";
 86    in
 87    {
 88      name = displayName;
 89      url = "https://${serviceName}.sbr.pm";
 90      inherit description;
 91    };
 92
 93  # Generate all service entries for a category
 94  mkCategoryServices =
 95    category: map (serviceName: mkServiceEntry category serviceName) category.services;
 96
 97  # Generate service list HTML
 98  mkServiceList =
 99    services:
100    lib.concatMapStringsSep "\n" (service: ''
101      <div class="service-card">
102        <h3><a href="${service.url}">${service.name}</a></h3>
103        <p>${service.description}</p>
104      </div>
105    '') services;
106
107  # Generate category section HTML
108  mkCategorySection =
109    categoryId: category:
110    let
111      services = if categoryId == "sync" then syncthingServices else mkCategoryServices category;
112    in
113    ''
114      <section class="category">
115        <h2>${category.title}</h2>
116        <div class="service-grid">
117          ${mkServiceList services}
118        </div>
119      </section>
120    '';
121
122  # Generate the complete HTML page
123  htmlContent = ''
124    <!DOCTYPE html>
125    <html lang="en">
126    <head>
127      <meta charset="UTF-8">
128      <meta name="viewport" content="width=device-width, initial-scale=1.0">
129      <title>Services Dashboard</title>
130      <style>
131        * {
132          margin: 0;
133          padding: 0;
134          box-sizing: border-box;
135        }
136
137        body {
138          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
139          line-height: 1.6;
140          color: #333;
141          background: #f5f5f5;
142          padding: 2rem;
143        }
144
145        .container {
146          max-width: 1200px;
147          margin: 0 auto;
148          background: white;
149          padding: 2rem;
150          border-radius: 8px;
151          box-shadow: 0 2px 8px rgba(0,0,0,0.1);
152        }
153
154        h1 {
155          color: #2c3e50;
156          margin-bottom: 2rem;
157          padding-bottom: 1rem;
158          border-bottom: 2px solid #3498db;
159        }
160
161        .category {
162          margin-bottom: 3rem;
163        }
164
165        h2 {
166          color: #34495e;
167          margin-bottom: 1rem;
168          font-size: 1.5rem;
169        }
170
171        .service-grid {
172          display: grid;
173          grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
174          gap: 1rem;
175        }
176
177        .service-card {
178          background: #f8f9fa;
179          padding: 1.5rem;
180          border-radius: 6px;
181          border-left: 4px solid #3498db;
182          transition: transform 0.2s, box-shadow 0.2s;
183        }
184
185        .service-card:hover {
186          transform: translateY(-2px);
187          box-shadow: 0 4px 12px rgba(0,0,0,0.1);
188        }
189
190        .service-card h3 {
191          font-size: 1.1rem;
192          margin-bottom: 0.5rem;
193        }
194
195        .service-card a {
196          color: #2980b9;
197          text-decoration: none;
198          font-weight: 500;
199        }
200
201        .service-card a:hover {
202          color: #3498db;
203          text-decoration: underline;
204        }
205
206        .service-card p {
207          color: #7f8c8d;
208          font-size: 0.9rem;
209        }
210
211        @media (max-width: 768px) {
212          body {
213            padding: 1rem;
214          }
215
216          .container {
217            padding: 1rem;
218          }
219
220          .service-grid {
221            grid-template-columns: 1fr;
222          }
223        }
224      </style>
225    </head>
226    <body>
227      <div class="container">
228        <h1>Services Dashboard</h1>
229
230        ${mkCategorySection "media" serviceCategories.media}
231        ${mkCategorySection "downloads" serviceCategories.downloads}
232        ${mkCategorySection "sync" {
233          title = "File Synchronization";
234        }}
235        ${mkCategorySection "utilities" serviceCategories.utilities}
236      </div>
237    </body>
238    </html>
239  '';
240in
241stdenv.mkDerivation {
242  pname = "homepage";
243  version = "1.0.0";
244
245  dontUnpack = true;
246  dontBuild = true;
247
248  installPhase = ''
249    runHook preInstall
250
251    mkdir -p $out
252    cat > $out/index.html << 'EOF'
253    ${htmlContent}
254    EOF
255
256    runHook postInstall
257  '';
258
259  meta = {
260    description = "Simple HTML homepage listing all services from globals.nix";
261    license = lib.licenses.mit;
262    platforms = lib.platforms.all;
263  };
264}