Skip to content
Merged
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
7 changes: 6 additions & 1 deletion app/collectors/storj.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ async def collect(self) -> EarningsResult:
return EarningsResult(
platform=self.platform,
balance=0.0,
error="Storagenode API not reachable — is port 14002 accessible?",
error=(
"Storagenode API not reachable at "
f"{self.api_url} — if running in Docker, use "
"the node's LAN IP instead of localhost "
"(e.g. http://192.168.1.x:14002)"
),
)
except Exception as exc:
logger.error("Storj collection failed: %s", exc)
Expand Down
56 changes: 39 additions & 17 deletions app/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ def deploy_service(
network_mode = docker_conf.get("network_mode") or None
cap_add = docker_conf.get("cap_add") or None
privileged = docker_conf.get("privileged", False)
stop_timeout = docker_conf.get("stop_timeout")

# Command: resolve ${VAR} placeholders from env dict
raw_command = docker_conf.get("command") or None
Expand Down Expand Up @@ -208,21 +209,24 @@ def deploy_service(
logger.warning("Failed to pull image %s: %s (trying local)", image, exc)

logger.info("Creating container %s from %s", name, image)
container = client.containers.run(
image=image,
name=name,
environment=env,
ports=ports if ports and network_mode != "host" else None,
volumes=volumes if volumes else None,
network_mode=network_mode,
cap_add=cap_add,
privileged=privileged,
command=command if command else None,
labels=labels,
hostname=hostname or f"cashpilot-{slug}",
detach=True,
restart_policy={"Name": "unless-stopped"},
)
run_kwargs: dict[str, Any] = {
"image": image,
"name": name,
"environment": env,
"ports": ports if ports and network_mode != "host" else None,
"volumes": volumes if volumes else None,
"network_mode": network_mode,
"cap_add": cap_add,
"privileged": privileged,
"command": command if command else None,
"labels": labels,
"hostname": hostname or f"cashpilot-{slug}",
"detach": True,
"restart_policy": {"Name": "unless-stopped"},
}
if stop_timeout:
run_kwargs["stop_timeout"] = _parse_stop_timeout(stop_timeout)
container = client.containers.run(**run_kwargs)

logger.info("Container %s started: %s", name, container.short_id)
return container.id
Expand Down Expand Up @@ -295,17 +299,35 @@ def deploy_raw(
return container.id


def _parse_stop_timeout(value: Any) -> int:
"""Parse a stop_timeout value, returning 30 on invalid input."""
try:
timeout = int(value)
except (TypeError, ValueError):
return 30
return timeout if timeout > 0 else 30


def _get_stop_timeout(slug: str) -> int:
"""Return the stop timeout from the catalog, or 30s default."""
if get_service:
svc = get_service(slug)
if svc:
return _parse_stop_timeout(svc.get("docker", {}).get("stop_timeout"))
return 30


def stop_service(slug: str) -> None:
"""Stop the container for a service."""
container = _find_container(slug)
container.stop(timeout=30)
container.stop(timeout=_get_stop_timeout(slug))
logger.info("Stopped container %s", container.name)


def restart_service(slug: str) -> None:
"""Restart the container for a service."""
container = _find_container(slug)
container.restart(timeout=30)
container.restart(timeout=_get_stop_timeout(slug))
logger.info("Restarted container %s", container.name)


Expand Down
2 changes: 2 additions & 0 deletions services/_schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
# command: "" (optional override)
# network_mode: "" (optional, e.g. "host")
# cap_add: [] (optional)
# stop_timeout: 30 (optional, seconds to wait before SIGKILL on stop — default 30)
# setup: "" (optional, one-time setup instructions/commands to run before first deploy)

# requirements:
# residential_ip: true/false
Expand Down
15 changes: 12 additions & 3 deletions services/storage/storj.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ referral:
signup_url: "https://storj.dev/node/get-started/setup"

docker:
image: storj/storagenode
image: storjlabs/storagenode
platforms: [linux/amd64, linux/arm64]
env:
- key: WALLET
Expand Down Expand Up @@ -56,11 +56,20 @@ docker:
- "${STORAGE_DIR}:/app/config"
command: "--operator.wallet-features=zksync-era"
network_mode: ""
stop_timeout: 300
setup: >
Before first run, initialize the storage node config:
docker run --rm -e SETUP="true"
--user $(id -u):$(id -g)
--mount type=bind,source="<IDENTITY_DIR>",destination=/app/identity
--mount type=bind,source="<STORAGE_DIR>",destination=/app/config
--name storagenode storjlabs/storagenode:latest
Comment thread
coderabbitai[bot] marked this conversation as resolved.
notes: >
No signup or auth token required (permissionless since mid-2025).
Image is storjlabs/storagenode (not storj/storagenode).
A one-time setup step (SETUP=true) must be run before deploying the main container.
Identity must be pre-generated using the identity binary (proof-of-work to difficulty 36, takes hours).
Use spinning disks for storage. Port 28967 TCP+UDP must be forwarded.
Local dashboard available at port 14002.
Dashboard at port 14002 — use your server's LAN IP (not localhost) for the collector API URL.

requirements:
residential_ip: false
Expand Down
Loading