Skip to content

Commit d432aa5

Browse files
committed
Remove dead services, fix platforms, detect external containers
- Delete adnade (domain for sale), antgain (dead) - Mark bytebenefit, spide, passiveapp as dead (websites down) - Fix platform info from research: bytelixir=Windows/Android, blockmesh/gradient/nodepay/teneo=browser-extension, grass=browser-extension/Windows/macOS, wipter=Win/Mac/Linux/Android - Orchestrator now detects externally-deployed containers by matching Docker image names to catalog, not just cashpilot labels - Worker image now includes catalog for image matching
1 parent c784646 commit d432aa5

22 files changed

Lines changed: 137 additions & 539 deletions

Dockerfile.worker

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ WORKDIR /app
4141
COPY --chown=cashpilot:root app/__init__.py ./app/
4242
COPY --chown=cashpilot:root app/worker_api.py ./app/
4343
COPY --chown=cashpilot:root app/orchestrator.py ./app/
44+
COPY --chown=cashpilot:root app/catalog.py ./app/
45+
COPY --chown=cashpilot:root services/ ./services/
4446
COPY entrypoint.sh /entrypoint.sh
4547
RUN chmod +x /entrypoint.sh
4648

README.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ Services CashPilot can deploy and manage automatically via Docker.
7171

7272
| Service | Residential IP | VPS IP | Devices / Acct | Devices / IP | Payout |
7373
|---------|:-:|:-:|:-:|:-:|--------|
74-
| [Adnade](https://adnade.com) ||| Unlimited | 1 | PayPal, Crypto |
75-
| [AntGain](https://antgain.app) ||| Unlimited | 1 | Crypto |
7674
| [Bitping](https://app.bitping.com) ||| Unlimited | 1 | Crypto (SOL) |
7775
| [Earn.fm](https://earn.fm/ref/GEISYB91) ||| Unlimited | 1 | Crypto |
7876
| [EarnApp](https://earnapp.com/i/TSMD9wSm) ||| 15 | 1 | PayPal, Gift Cards |
@@ -101,14 +99,11 @@ These services have no Docker image. CashPilot lists them in the catalog with si
10199
| Service | Residential IP | VPS IP | Devices / Acct | Devices / IP | Payout | Status |
102100
|---------|:-:|:-:|:-:|:-:|--------|--------|
103101
| [BlockMesh](https://blockmesh.xyz) ||| Unlimited | 1 | Crypto (BMESH) | Active |
102+
| [Bytelixir](https://bytelixir.com/r/OYEIRE0VSZBZ) ||| Unlimited | 1 | Crypto | Active |
104103
| [Ebesucher](https://www.ebesucher.com/?ref=geiserx) ||| Unlimited | 1 | PayPal | Active |
105-
| [Bytebenefit](https://bytebenefit.com) ||| Unlimited | 1 | PayPal | Beta |
106-
| [Bytelixir](https://bytelixir.com/r/OYEIRE0VSZBZ) ||| Unlimited | 1 | Crypto | Beta |
107104
| [Gradient Network](https://app.gradient.network/signup?referralCode=YSKMY7) ||| Unlimited | 1 | Crypto (GRADIENT) | Active |
108105
| [Grass](https://app.getgrass.io/register/?referralCode=kn8FNEPnUr2tMqE) ||| Unlimited | 1 | Crypto (GRASS) | Active |
109106
| [Nodepay](https://app.nodepay.ai/register?ref=0wzzyznen64j9zx) ||| Unlimited | 1 | Crypto (NC) | Active |
110-
| [PassiveApp](https://passiveapp.io) ||| Unlimited | 1 | Crypto, PayPal | Beta |
111-
| [Spide](https://spide.app) ||| Unlimited | 1 | Crypto | Active |
112107
| [Teneo Protocol](https://dashboard.teneo.pro/?code=CAqef) ||| Unlimited | 1 | Crypto (TENEO) | Active |
113108
| [Uprock](https://uprock.com) ||| Unlimited | 1 | Crypto | Active |
114109
| [Wipter](https://wipter.com/en/refer-a-friend) ||| Unlimited | 1 | PayPal, Crypto | Active |

app/orchestrator.py

Lines changed: 116 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@
2323
from docker.errors import APIError, DockerException, NotFound
2424

2525
try:
26-
from app.catalog import get_service
26+
from app.catalog import get_service, get_services
2727
except ImportError:
2828
# Worker image doesn't include catalog module
2929
get_service = None # type: ignore[assignment]
30+
get_services = None # type: ignore[assignment]
3031

3132
logger = logging.getLogger(__name__)
3233

@@ -316,8 +317,31 @@ def start_service(slug: str) -> None:
316317
logger.info("Started container %s", container.name)
317318

318319

320+
def _collect_stats(c) -> tuple[float, float]:
321+
"""Collect CPU% and memory for a single container. Returns (cpu_pct, mem_mb)."""
322+
try:
323+
stats = c.stats(stream=False)
324+
cpu_delta = (
325+
stats["cpu_stats"]["cpu_usage"]["total_usage"]
326+
- stats["precpu_stats"]["cpu_usage"]["total_usage"]
327+
)
328+
system_delta = (
329+
stats["cpu_stats"]["system_cpu_usage"]
330+
- stats["precpu_stats"]["system_cpu_usage"]
331+
)
332+
num_cpus = stats["cpu_stats"].get(
333+
"online_cpus",
334+
len(stats["cpu_stats"]["cpu_usage"].get("percpu_usage", [1])),
335+
)
336+
cpu_pct = round((cpu_delta / system_delta) * num_cpus * 100, 2) if system_delta > 0 else 0.0
337+
mem_mb = round(stats["memory_stats"].get("usage", 0) / (1024 * 1024), 1)
338+
return cpu_pct, mem_mb
339+
except (KeyError, ZeroDivisionError, APIError):
340+
return 0.0, 0.0
341+
342+
319343
def get_status() -> list[dict[str, Any]]:
320-
"""Return live status of all cashpilot-managed containers.
344+
"""Return live status of all known containers (labeled + image-matched).
321345
322346
This is SLOW (~1-2s per container) because it calls Docker stats API.
323347
Use get_status_cached() for page loads; this is for background refresh.
@@ -329,37 +353,18 @@ def get_status() -> list[dict[str, Any]]:
329353
except RuntimeError:
330354
return []
331355

332-
containers = client.containers.list(
356+
# Labeled containers (CashPilot-managed)
357+
labeled = client.containers.list(
333358
all=True,
334359
filters={"label": f"{LABEL_MANAGED}=true"},
335360
)
361+
seen_ids: set[str] = set()
336362

337363
results: list[dict[str, Any]] = []
338-
for c in containers:
364+
for c in labeled:
365+
seen_ids.add(c.id)
339366
slug = c.labels.get(LABEL_SERVICE, "unknown")
340-
# Gather resource stats (non-streaming)
341-
cpu_pct = 0.0
342-
mem_mb = 0.0
343-
try:
344-
stats = c.stats(stream=False)
345-
# CPU %
346-
cpu_delta = (
347-
stats["cpu_stats"]["cpu_usage"]["total_usage"] - stats["precpu_stats"]["cpu_usage"]["total_usage"]
348-
)
349-
system_delta = stats["cpu_stats"]["system_cpu_usage"] - stats["precpu_stats"]["system_cpu_usage"]
350-
num_cpus = stats["cpu_stats"].get(
351-
"online_cpus",
352-
len(stats["cpu_stats"]["cpu_usage"].get("percpu_usage", [1])),
353-
)
354-
if system_delta > 0:
355-
cpu_pct = round((cpu_delta / system_delta) * num_cpus * 100, 2)
356-
357-
# Memory
358-
mem_usage = stats["memory_stats"].get("usage", 0)
359-
mem_mb = round(mem_usage / (1024 * 1024), 1)
360-
except (KeyError, ZeroDivisionError, APIError):
361-
pass
362-
367+
cpu_pct, mem_mb = _collect_stats(c)
363368
results.append(
364369
{
365370
"slug": slug,
@@ -375,6 +380,35 @@ def get_status() -> list[dict[str, Any]]:
375380
}
376381
)
377382

383+
# Image-matched containers (deployed externally)
384+
image_map = _build_image_slug_map()
385+
if image_map:
386+
all_containers = client.containers.list(all=True)
387+
for c in all_containers:
388+
if c.id in seen_ids:
389+
continue
390+
image_name = c.image.tags[0] if c.image.tags else ""
391+
slug = image_map.get(image_name, "")
392+
if not slug and image_name:
393+
slug = image_map.get(image_name.split(":")[0], "")
394+
if slug:
395+
seen_ids.add(c.id)
396+
cpu_pct, mem_mb = _collect_stats(c)
397+
results.append(
398+
{
399+
"slug": slug,
400+
"name": c.name,
401+
"status": c.status,
402+
"image": image_name or str(c.image.short_id),
403+
"cpu_percent": cpu_pct,
404+
"memory_mb": mem_mb,
405+
"created": c.attrs.get("Created", ""),
406+
"container_id": c.short_id,
407+
"deployed_by": "external",
408+
"category": "",
409+
}
410+
)
411+
378412
# Update the cache
379413
_status_cache = results
380414
_status_cache_time = time.monotonic()
@@ -401,25 +435,47 @@ def get_status_cached(max_age: int = 600) -> list[dict[str, Any]]:
401435
return get_status_light()
402436

403437

438+
def _build_image_slug_map() -> dict[str, str]:
439+
"""Build a map of Docker image names to service slugs from catalog."""
440+
if not get_services:
441+
return {}
442+
mapping: dict[str, str] = {}
443+
for svc in get_services():
444+
docker_conf = svc.get("docker", {})
445+
image = docker_conf.get("image", "")
446+
if image:
447+
# Map both "image" and "image:latest" to the slug
448+
mapping[image] = svc["slug"]
449+
if ":" not in image:
450+
mapping[f"{image}:latest"] = svc["slug"]
451+
return mapping
452+
453+
404454
def get_status_light() -> list[dict[str, Any]]:
405455
"""Return container list/status WITHOUT resource stats (fast).
406456
407457
Only queries Docker for container list + labels, skips the slow
408458
per-container stats() call. Used when we need fresh container
409459
states (running/stopped) but don't need CPU/memory numbers.
460+
461+
Finds containers by cashpilot label OR by matching Docker image
462+
to known catalog services (for containers deployed externally).
410463
"""
411464
try:
412465
client = _get_client()
413466
except RuntimeError:
414467
return []
415468

416-
containers = client.containers.list(
469+
# First: labeled containers (CashPilot-managed)
470+
labeled = client.containers.list(
417471
all=True,
418472
filters={"label": f"{LABEL_MANAGED}=true"},
419473
)
474+
seen_ids: set[str] = set()
420475

421476
results: list[dict[str, Any]] = []
422-
for c in containers:
477+
for c in labeled:
478+
seen_ids.add(c.id)
423479
results.append(
424480
{
425481
"slug": c.labels.get(LABEL_SERVICE, "unknown"),
@@ -434,6 +490,37 @@ def get_status_light() -> list[dict[str, Any]]:
434490
"category": c.labels.get(LABEL_CATEGORY, ""),
435491
}
436492
)
493+
494+
# Second: scan all containers and match by image name
495+
image_map = _build_image_slug_map()
496+
if image_map:
497+
all_containers = client.containers.list(all=True)
498+
for c in all_containers:
499+
if c.id in seen_ids:
500+
continue
501+
image_name = c.image.tags[0] if c.image.tags else ""
502+
slug = image_map.get(image_name, "")
503+
if not slug and image_name:
504+
# Try without tag
505+
base = image_name.split(":")[0]
506+
slug = image_map.get(base, "")
507+
if slug:
508+
seen_ids.add(c.id)
509+
results.append(
510+
{
511+
"slug": slug,
512+
"name": c.name,
513+
"status": c.status,
514+
"image": image_name or str(c.image.short_id),
515+
"cpu_percent": 0.0,
516+
"memory_mb": 0.0,
517+
"created": c.attrs.get("Created", ""),
518+
"container_id": c.short_id,
519+
"deployed_by": "external",
520+
"category": "",
521+
}
522+
)
523+
437524
return results
438525

439526

docs/guides/adnade.md

Lines changed: 0 additions & 82 deletions
This file was deleted.

0 commit comments

Comments
 (0)