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