Skip to content

centricle/n8n

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fleet Helm: n8n as a Portfolio Nervous System

I manage 30+ indie web products across Netlify, Supabase, and GitHub. I built thehelm.fyi from scratch to monitor all of it: Express 5, EJS, HTMX 2.x, Tailwind 4 CLI, deployed on Fly.io. Hand-rolled fleet dashboard. Real problem, real solution.

Wanted to try n8n so I rebuilt it there this afternoon.

Fleet Helm workflow

What It Does

A single workflow runs daily at midnight. Four data sources fire in parallel:

  • Uptime monitoring: HTTP pings to 22 production URLs, checking for 200 responses
  • Netlify deployments: Queries all 30+ sites for latest deploy status and timestamps
  • Supabase user counts: Pulls profile counts from 5 auth-enabled apps via REST API
  • GitHub activity: Fetches all repositories, last push dates, open issues

Everything merges into a single stream. Claude (Sonnet) synthesizes the raw data into a fleet health report: what's healthy, what's stale, what needs attention, and the top 3 actions for the day.

Why Bother?

thehelm.fyi works. But it took a while to build and requires a running server, a deployment pipeline, ongoing maintenance, and context-switching into a dashboard to check fleet status. I wanted to try n8n, and had previously kicked around the idea of coding up some Claude API tooling to just give me a couple sentences instead of needing to remember to check my local helm dashboard, but there's a lot of plumbing there. Cron job, Slack integration or Resend config or whatever.

The n8n workflow took an afternoon. It runs autonomously. It produces a smarter report because Claude can synthesize across data sources in ways that templated HTML can't. And it's extensible without a deployment: Add a Slack node for alerts, an Ollama node for local AI triage, an MCP Server trigger so Claude Code can query fleet status from the terminal. Pretty sure all that's doable.

The platform eliminated the infrastructure tax. The workflow is the product.

Architecture

                   [Scheduled Trigger]
                    (Midnight, daily)
                           |
      +-------------+-------------+--------------+
      |            |              |              |
[Get domains] [Netlify API] [Supabase API] [GitHub API]
      |            |              |              |
[HTTP Requests]    |              |              |
(22 prod URLs)     |              |              |
     |             |              |              |
     +-------------+--------------+--------------+
                           |
                        [Merge]
                       (41 items)
                           |
                        [Claude]
                        (Sonnet)
                           |
                        [Report]

The Code

The workflow JSON is in workflows/fleet-helm.json (credentials replaced with placeholders). Here's what's inside the Code nodes.

Uptime Check: URL List

Generates one item per production URL. The HTTP Request node downstream executes once per item automatically.

const sites = [
  { name: 'centricle', url: 'https://centricle.com' },
  { name: 'mileweave', url: 'https://mileweave.us' },
  { name: 'picweave', url: 'https://picweave.us' },
  { name: 'batteries', url: 'https://batteries.fyi' },
  { name: 'cables', url: 'https://cables.fyi' },
  { name: 'fasteners', url: 'https://fasteners.fyi' },
  { name: 'iron', url: 'https://iron.fyi' },
  { name: 'mahogany', url: 'https://mahogany.fyi' },
  { name: 'wings', url: 'https://wings.fyi' },
  { name: 'packlife', url: 'https://thepacklife.us' },
  { name: 'yappyhour', url: 'https://yappyhour.us' },
  { name: 'thoughtstream', url: 'https://thoughtstream.us' },
  { name: 'thoughtweave', url: 'https://thoughtweave.us' },
  { name: 'dipshit', url: 'https://dipshit.fyi' },
  { name: 'doomscrolling', url: 'https://doomscrolling.us' },
  { name: 'flailspin', url: 'https://flailspin.com' },
  { name: 'whoami', url: 'https://whoami.fyi' },
  { name: 'splitsmart', url: 'https://splitsmart.us' },
  { name: 'choremode', url: 'https://choremode.us' },
  { name: 'propdock', url: 'https://propdock.us' },
  { name: 'verbatim', url: 'https://verbatim.fyi' },
  { name: 'tidewaterweb', url: 'https://tidewaterweb.com' },
];

return sites.map(site => ({ json: site }));

Netlify Deployments

Hits the Netlify API directly to pull all sites and their latest deploy. The native Netlify node only handles one site at a time; this gets all 30+ in a single Code node.

const token = process.env.NETLIFY_TOKEN; // configured in n8n credentials

const sitesRes = await fetch('https://api.netlify.com/api/v1/sites?per_page=100', {
  headers: { 'Authorization': `Bearer ${token}` },
});
const sites = await sitesRes.json();

const results = [];
for (const site of sites) {
  const deploysRes = await fetch(
    `https://api.netlify.com/api/v1/sites/${site.id}/deploys?per_page=1`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );
  const deploys = await deploysRes.json();
  const latest = deploys[0] || {};

  results.push({
    json: {
      name: site.name,
      url: site.ssl_url || site.url,
      lastDeploy: latest.created_at || 'never',
      deployStatus: latest.state || 'unknown',
      updatedAt: site.updated_at,
    },
  });
}

return results;

Supabase User Counts

Queries the profiles table across multiple Supabase projects using HEAD requests with Prefer: count=exact. Each project has its own URL and service role key.

const projects = [
  { name: 'mileweave', url: 'https://YOUR_PROJECT.supabase.co', key: 'YOUR_SERVICE_KEY' },
  { name: 'picweave', url: 'https://YOUR_PROJECT.supabase.co', key: 'YOUR_SERVICE_KEY' },
  // ... additional projects
];

const results = [];
for (const project of projects) {
  try {
    const response = await fetch(
      `${project.url}/rest/v1/profiles?select=count`,
      {
        method: 'HEAD',
        headers: {
          'apikey': project.key,
          'Authorization': `Bearer ${project.key}`,
          'Prefer': 'count=exact',
        },
      }
    );
    const count = response.headers.get('content-range')?.split('/')[1] || '0';
    results.push({ json: { name: project.name, userCount: parseInt(count), status: 'ok' } });
  } catch (err) {
    results.push({ json: { name: project.name, userCount: 0, status: 'error', error: err.message } });
  }
}

return results;

AI Synthesis Prompt

All 41 items from the Merge node are passed to Claude Sonnet as raw JSON. The node runs with "Execute Once" enabled so it makes a single API call instead of 41.

The prompt:

You are a fleet operations analyst for ksmith's portfolio of 30+ indie web
products. Analyze the following aggregated data and produce a concise fleet
health report.

## Raw Fleet Data
{{ JSON.stringify($input.all().map(item => item.json), null, 2) }}

Produce a report with:
1. **Fleet Overview**: Total sites, how many healthy vs stale vs down
2. **Attention Needed**: Sites with failed deploys, sites not deployed in 30+ days, any downtime
3. **Growth Signals**: User count changes, active repos, recent deploys
4. **Recommendation**: Top 3 actions for today

Be direct and concise. No fluff.

Sample Output

From a live run on April 5, 2026:

Fleet Overview: 30 properties analyzed. 26 healthy (87%), 3 stale (10%), 1 down (3%).

Attention Needed: thehelm.fyi showing SSL timeout (Fly.io instance sleeping). Thoughtweave still on coming-soon page. Multiple CLI tools active in repos but low public visibility.

Growth Signals: batteries.fyi, cables.fyi, fasteners.fyi showing consistent updates through March 2026. 8 repositories pushed within last 30 days. New projects (thehelm, package-stacker, deink) recently created.

Recommendations: (1) Fix thehelm.fyi deployment. (2) Decide on Thoughtweave launch timeline. (3) Use the Helm dashboard to automate this fleet health analysis.

What I'd Build Next

These are real next steps, not hand-waving. The architecture supports all of them without restructuring.

  • Ollama node for local AI classification (dormant/healthy/critical) where latency and cost matter more than synthesis quality. Already running Ollama locally.
  • Conditional routing after classification: site-down alerts go to Slack immediately, dormant flags get batched into a weekly digest, growth signals get logged.
  • MCP Server trigger exposing the fleet status as a tool. Claude Code calls fleet_status from the terminal and gets a live report. n8n becomes invisible infrastructure inside the dev workflow.
  • Heritage Radar (second workflow): RSS feeds from classic car, boat, and aviation auction sites, classified by AI into content buckets for iron.fyi, mahogany.fyi, and wings.fyi. Human-in-the-loop approval via Slack before committing to GitHub and auto-deploying.

Self-Hosting

# docker-compose.yml
services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    ports:
      - "5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    environment:
      - N8N_HOST=n8n.test
      - WEBHOOK_URL=http://n8n.test
      - N8N_SECURE_COOKIE=false

volumes:
  n8n_data:

Running via OrbStack on macOS with Caddy reverse proxy at http://n8n.test.

About

Built by ksmith

License

MIT

About

Fleet Helm: n8n as a portfolio nervous system

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors