Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/collectors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from app.collectors.packetstream import PacketStreamCollector
from app.collectors.proxyrack import ProxyRackCollector
from app.collectors.repocket import RepocketCollector
from app.collectors.salad import SaladCollector
from app.collectors.storj import StorjCollector
from app.collectors.traffmonetizer import TraffmonetizerCollector

Expand All @@ -41,6 +42,7 @@
"packetstream": PacketStreamCollector,
"grass": GrassCollector,
"bytelixir": BytelixirCollector,
"salad": SaladCollector,
}

# Map of slug -> list of config keys needed to instantiate the collector
Expand All @@ -58,6 +60,7 @@
"packetstream": ["auth_token"],
"grass": ["access_token"],
"bytelixir": ["session_cookie", "?remember_web", "?xsrf_token"],
"salad": ["auth_cookie"],
}


Expand Down
70 changes: 70 additions & 0 deletions app/collectors/salad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Salad earnings collector.

Authenticates via the ``auth`` cookie and fetches the current balance
from the Salad API at app-api.salad.com.

Salad uses ASP.NET Core anti-forgery: the ``auth`` cookie value must
also be sent as the ``X-XSRF-TOKEN`` header (double-submit pattern).

To get the token: open salad.com in your browser, log in, press F12,
go to Application > Cookies > .salad.com, and copy the ``auth`` cookie.
"""

from __future__ import annotations

import logging

import httpx

from app.collectors.base import BaseCollector, EarningsResult

logger = logging.getLogger(__name__)

API_BASE = "https://app-api.salad.com/api/v1"


class SaladCollector(BaseCollector):
"""Collect earnings from Salad's API using the auth cookie."""

platform = "salad"

def __init__(self, auth_cookie: str) -> None:
self.auth_cookie = auth_cookie

async def collect(self) -> EarningsResult:
"""Fetch current Salad balance."""
try:
cookies = {"auth": self.auth_cookie}
headers = {"X-XSRF-TOKEN": self.auth_cookie}

async with httpx.AsyncClient(timeout=30) as client:
resp = await client.get(
f"{API_BASE}/profile/balance",
cookies=cookies,
headers=headers,
)

if resp.status_code in (401, 403):
return EarningsResult(
platform=self.platform,
balance=0.0,
error="Auth cookie expired — get a new 'auth' cookie from salad.com",
)

resp.raise_for_status()
data = resp.json()

balance = float(data.get("currentBalance", 0))

return EarningsResult(
platform=self.platform,
balance=round(balance, 4),
currency="USD",
)
except Exception as exc:
logger.error("Salad collection failed: %s", exc)
return EarningsResult(
platform=self.platform,
balance=0.0,
error=str(exc),
)
25 changes: 25 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,7 @@ async def api_collectors_meta(request: Request) -> list[dict[str, Any]]:
"access_token",
"api_key",
"session_cookie",
"auth_cookie",
"oauth_token",
"brd_sess_id",
}
Expand Down Expand Up @@ -1318,6 +1319,30 @@ class ConfigUpdate(BaseModel):
async def api_set_config(request: Request, body: ConfigUpdate) -> dict[str, str]:
_require_owner(request)
await database.set_config_bulk(body.data)

# Auto-create "external" deployment records for manual-only services
# whose collector credentials were just saved. Without a deployment
# row, _run_collection() will never instantiate the collector.
from app.collectors import _COLLECTOR_ARGS

for slug, arg_keys in _COLLECTOR_ARGS.items():
required_keys = [f"{slug}_{a.lstrip('?')}" for a in arg_keys if not a.startswith("?")]
if not required_keys:
continue
if not all(body.data.get(k) for k in required_keys):
continue
svc = catalog.get_service(slug)
if not svc:
continue
docker_conf = svc.get("docker", {})
has_image = bool(docker_conf and docker_conf.get("image"))
if has_image:
continue # Docker services get deployed normally
existing = await database.get_deployment(slug)
if not existing:
await database.save_deployment(slug=slug, container_id="", status="external")
logger.info("Auto-created external deployment for %s", slug)

return {"status": "saved"}


Expand Down
25 changes: 16 additions & 9 deletions docs/guides/salad.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,27 @@ Salad.io lets you share your GPU for distributed AI workloads and earn Salad bal

### 1. Create an account

Sign up at [Salad](https://salad.io).
Sign up at [Salad](https://salad.io) and install the desktop application on your Windows machine.

### 2. Get your credentials
### 2. Get your auth cookie

After signing up, locate the credentials needed for Docker deployment. These are typically your email/password or an API token found in the dashboard.
Salad runs as a native Windows app — there is no Docker image. To let CashPilot track your earnings:

### 3. Deploy with CashPilot
1. Open [salad.com](https://salad.com) in your browser and log in.
2. Press **F12** to open DevTools.
3. Go to **Application > Cookies > salad.com**.
4. Copy the value of the `auth` cookie (starts with `CfDJ8...`).

In the CashPilot web UI, find **Salad** in the service catalog and click **Deploy**. Enter the required credentials and CashPilot will handle the rest.
### 3. Configure CashPilot

## Docker Configuration
Add the cookie to your CashPilot configuration:

```
salad_auth_cookie=<your auth cookie value>
```

- **Image:** ``
CashPilot will poll `app-api.salad.com/api/v1/profile/balance` to fetch your current and lifetime balance.

### Environment Variables
## Docker Configuration

No environment variables required.
Salad is a native Windows desktop application — no Docker image is available. CashPilot monitors earnings via the Salad API only.
2 changes: 1 addition & 1 deletion services/compute/salad.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ platforms: [windows]

collector:
type: api
notes: "API at app.salad.io. Auth via session token. Earnings and machine status endpoints available."
notes: "API at app-api.salad.com/api/v1/profile/balance. Auth via 'auth' cookie + X-XSRF-TOKEN header (double-submit). Returns currentBalance (USD) and lifetimeBalance."
Loading