Skip to content

IPFS.NINJA — Sunset Plan

Started 2026-09-11. Reason: SaaS is deeply unprofitable at current scale — 3 real paying customers ($43/mo MRR) against $450–700/mo AWS run-rate, and the free-tier is dominated by abuse ("most users just want to use IPFS for cracking"). Rather than pursue growth, we're winding down the SaaS wrapper and running a minimal service for the remaining paying customers only.

KEEP set — the paying customers we continue to serve

GUIDPlanEmailNotes
b428d488-e071-70f4-ad7e-bdd36405ea38karmadaniel-CEO@DPMF.Technology (Daniel Monks)Active daily
c418d448-d071-7081-b73e-8b675e1330cbkarmasaori22nishida@gmail.com (saori)15 days idle but subscribed
74888468-70e1-7005-1119-63a75ebee6b0bodhiinfo@tatout.io (Simon)Zero activity — verify still valid
14c80498-d0f1-7066-e64a-878b1a035ffdnirvanaignacio.coll+ipfstest_1@gmail.com (Ignacio Test)Internal test account, not real revenue
d4980408-f0d1-70da-9caa-2def95069904bodhi (TRIAL)muhammadolawale27@gmail.com (Muhammad)Added 2026-09-11 — trial user with a working 10k-file snapshot (TK-1789031159249). Kept because his ticket was resolved yesterday with a live snapshot CID. Re-evaluate at trial expiry (~2026-09-16)

If this list changes: update all four places (they're all hardcoded because Lambda@Edge doesn't support env vars):

  • src/BWS-IPFS-Apex_gateway.mjsKEEP_USER_GUIDS
  • src/@common/shared.mjsSUNSET_KEEP_USER_GUIDS
  • This document
  • The EventBridge disable list below if a new paying customer needs a schedule re-enabled

Phase 0 — executed 2026-09-11 (all reversible)

Code changes

  • src/BWS-IPFS-User_presignup.mjs — early-return with a friendly error when SIGNUPS_DISABLED=1
  • src/BWS-IPFS-Apex_gateway.mjsddbAttributed() now projects USER_GUID and returns true only if that GUID is in KEEP_USER_GUIDS. All other CIDs get the existing branded 410 page
  • src/@common/shared.mjsvalidateStorage() throws CreditsError with a "service is winding down" message when SUNSET_UPLOADS_LOCKED=1 and the caller isn't in the KEEP set
  • frontend/src/pages/SignUpPage.tsx — renders a "signups are closed" notice when SUNSET_MODE=true

Deploy-time env vars added

  • SIGNUPS_DISABLED=1 on prod-bws-api-ipfs-upload-User-Presignup
  • SUNSET_UPLOADS_LOCKED=1 — Globals section, so every Serverless::Function inherits it; live on New-Call, New-Api-Call, Upload-Large-Init, Upload-Large-Complete, Pin-CID-Call, Folder-Snapshot-Call
  • SUNSET_CHECKOUT_LOCKED=1 on prod-bws-api-ipfs-upload-Stripe-Checkout-Call — rejects every new checkout attempt. Portal + webhook + reconciler are untouched so existing paying customers can still manage or cancel their subscription and Stripe continues to bill them normally.

EventBridge rules disabled (29 rules)

Kept: billing, health, safety-net (badbits, pin-safety-scan, threat-hash-import), paying-user maintenance (gateway-bandwidth, ipns-republish, pin-resolve, storage/bandwidth-reconcile), ops (auto-code-deploy-watcher, codepipeline-failure, deploy-status-updater, cost-rate-watcher, stripe-reconciler, manual-grant-reconciler).

Disabled:

  • Retention / lifecycle emails: lifecycle-emails, no-upload-reminder, hour1-recovery, followup-email, retention-day3, retention-day14, retention-day30, trial-reactivation-nudge, weekly-digest-send, newsletter-send, power-user-tease
  • Trial funnel: trial-expire, trial-signup-rate-check, checkout-help-watcher
  • Signup-time safety infra (no more signups): abuse-ip-scan, ip-reputation-sync, paid-user-allowlist-sweeper, waf-metrics-replayer
  • SEO / marketing: ipfs-gsc-snapshot
  • Analytics for churned population: behavior-aggregate, attribution-sync, storage-warning, bandwidth-warning, bandwidth-forecast, sla-breach-pager, auto-close-stale-tickets
  • Redundant probes: dht-announce-canary, gateway-customdomain-canary
  • The $1094 killer (CLAUDE.md incident): cid-bw-quota-enforcer — safe to disable now, no free users to enforce quota against

Re-enable = aws events enable-rule --name <name> per rule.

Cost impact (per CLAUDE.md)

  • Immediate: ~$100/mo run-rate cut (workflow invocations that no longer fire)
  • Follow-on (once P0 propagates through CloudFront cache): ~$50–100/mo more from CloudFront + ECS egress dropping as non-paying CIDs return 410 instead of resolving
  • Combined P0 saving: ~$150–200/mo. New projection: ~$400–500/mo, down from ~$690/mo
  • All backed by env-var flips + rule-state flips. Full rollback in ≤ 10 minutes.

Backout signal

  • CloudWatch alarm on Health-Rollup (existing, unaffected) — if the KEEP-set customers see an outage, existing paging fires
  • Cost-Rate-Watcher (kept enabled) — surfaces any anomalous spend
  • Muhammad's snapshot ticket TK-1789031159249 — his URL (bafybeifxyvxwao3xxgbof5qgfegvdyxqb5wbzun4ybh5pyr6q7huhjw65i) will 410 after cache expiry because he's on trial (not KEEP). Since his files were snapshotted yesterday and he's a trial-only user, that's intended behavior once the sunset takes effect

Phase 1 — Customer communication (Day 1–3)

Not started. Four emails to write:

  1. KEEP set — 1:1 heads-up that their service continues; no functional change; support latency may lengthen
  2. 9 trial users (incl. Muhammad) — trial ends normally; not renewable; export CAR window
  3. 194 expired users — service discontinued; 30-day CAR-export window; deletion date
  4. Follow-up on TK-1789031159249 — Muhammad's snapshot works but his trial ends normally

Phase 2 — Free-tier and trial data expiration (Day 3–33)

Not started. 30-day CAR-export window before purge.

Phase 3 — Data purge (Day 33)

Not started. Delete every non-KEEP user's files, folders, keys, gateways, IPNS keys, upload tokens; Kubo pin/rm + repo/gc on their CIDs.

Phase 4 — Infrastructure downsize (Day 33+)

Not started. ECS 3 → 1 task, drop unused CloudFront distributions, drop DDB tables (e2e-runs, behavior-events, seo-tracking, health-history, deploy-status, ip-reputation, retention counters, campaign-visits, rate-limit-events), delete inactive Secrets, delete acquisition Lambdas + their CloudWatch alarms + their scheduled rules from IaC.

Every P4 cut needs its own CLAUDE.md-compliant cost analysis and explicit approval before executing.

Phase 5 — Final state (post-Day 60)

Target run-rate: ~$50–80/mo serving 3–5 paying customers.

Reversal — how to bring the SaaS back

If the shutdown reverses (a big customer signs, a different strategy emerges):

  1. Set SIGNUPS_DISABLED= (empty) on prod-bws-api-ipfs-upload-User-Presignup
  2. Set SUNSET_UPLOADS_LOCKED= (empty) on the Globals block of node-lambdas.yml and redeploy (or empty on each affected Lambda directly)
  3. Flip SUNSET_MODE = false in frontend/src/pages/SignUpPage.tsx and redeploy
  4. In Apex_gateway.mjs, revert ddbAttributed() to the pre-sunset shape (delete the KEEP_USER_GUIDS gate)
  5. Re-enable EventBridge rules — the sunset commit's message lists all 29 by name; aws events enable-rule --name <n> each

Phase 0b — bandwidth cost-anomaly hardening (executed 2026-09-11)

Post-P0 audit (see SUNSET_AUDIT_2026-09-11.md) surfaced that Kubo's libp2p swarm port and one unaliased CloudFront distribution were still unmetered vectors. Fixes shipped immediately, all reversible:

A — closed libp2p swarm inbound

  • Removed tcp 4001 0.0.0.0/0 from Kubo task security group sg-016d22e417aae3c25
  • Kubo now runs in client mode — outbound peer connections still work, no peer can pull our pinset via Bitswap
  • Revert: aws ec2 authorize-security-group-ingress --group-id sg-016d22e417aae3c25 --protocol tcp --port 4001 --cidr 0.0.0.0/0

B1 — disabled unaliased CloudFront distribution

  • E2V22RZSULORO9 (no alias, direct ALB backend) — flipped Enabled=false
  • Revert: get-distribution-config + flip Enabled=true + update-distribution with the returned ETag
  • Reverted 2026-09-16: re-enabled after IPFS_URL secret was found still pointing at d39la1r504sgpg.cloudfront.net (this distribution's domain). Every New-Api-Call invocation was failing with getaddrinfo ENOTFOUND after ~5s and returning 500 to the caller — 382 failures in a 2h window drove prod-lambda-total-duration-velocity into ALARM. The KEEP-only apex gate + SUNSET_UPLOADS_LOCKED cover the BW-leak risk this distribution was originally hardened against, so re-enabling is safe under sunset. Watch AWS/CloudFront BytesDownloaded on E2V22RZSULORO9 — it should stay near zero because no alias points at it and only our own Lambdas call its domain.

B2 — tightened AWS Budgets to sunset levels

  • prod-total-monthly: $3500 → $500
  • prod-cloudfront-monthly: $4000 → $50
  • Unchanged: bws-nodes-prod_200-cost $1000, prod-account-hardstop $10000

B3 — Kubo cluster egress alarm

  • New CloudWatch alarm prod-kubo-swarm-tx-anomaly
  • Metric: ECS/ContainerInsights NetworkTxBytes on bws-nodes-ipfs-cluster, sum > 2 GB in 5 min
  • Actions: prod-platform-kill-switch SNS topic (alarm AND ok state changes) → same channel that already reaches the operator on other kill-switch events
  • Rationale: with the swarm port closed and non-KEEP CIDs 410'd at CloudFront, sustained multi-GB egress means something we don't expect is happening — cheaper to page than debug post-mortem

Phase 3 — data purge (scripts/sunset/purge-non-keep.sh)

The purge script now lives at scripts/sunset/purge-non-keep.sh. Three modes:

  • dry-run — safe, prints counts (# users to purge, # CIDs to unpin, GB reclaimed)
  • commit — destructive, guarded by both a date check (refuses before 2026-10-11) and a typed 'PURGE' confirmation; sets FORCE=1 to override the date check
  • verify — post-purge sanity: sample-fetches each KEEP customer's newest CID and reports HTTP status

The date guard is important: our sunset emails told non-paying users they had a 30-day CAR-export window after the 2026-09-11 shutdown communication. 2026-10-11 is the earliest we should run commit. Running the script dry-run any time is safe.