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 .github/workflows/proto-fleet-artifact-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ jobs:
run: |
go mod download
go build -v -ldflags "-X main.version=$VERSION" -o fleetd-${{ matrix.arch }} ./cmd/fleetd
go build -v -o sv2-translator-helper-${{ matrix.arch }} ./cmd/sv2-translator-helper
echo "version: $VERSION" > version.txt
echo "is_prerelease: $IS_PRERELEASE" >> version.txt
echo "build_date: $BUILD_DATE" >> version.txt
Expand Down Expand Up @@ -204,6 +205,7 @@ jobs:
run: |
tar -czf "proto-fleet-server-${VERSION}-${{ matrix.arch }}.tar.gz" \
fleetd-${{ matrix.arch }} \
sv2-translator-helper-${{ matrix.arch }} \
proto-plugin-${{ matrix.arch }} \
antminer-plugin-${{ matrix.arch }} \
virtual-plugin-${{ matrix.arch }} \
Expand Down Expand Up @@ -422,6 +424,7 @@ jobs:
tar -xzf "/tmp/server-artifacts/proto-fleet-server-${VERSION}-${{ matrix.arch }}.tar.gz" -C deployment/server
cd deployment/server
mv fleetd-${{ matrix.arch }} fleetd
mv sv2-translator-helper-${{ matrix.arch }} sv2-translator-helper
mv proto-plugin-${{ matrix.arch }} proto-plugin
mv antminer-plugin-${{ matrix.arch }} antminer-plugin
mv virtual-plugin-${{ matrix.arch }} virtual-plugin
Expand Down
44 changes: 44 additions & 0 deletions deployment-files/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,65 @@ services:
TIMESCALEDB_MAX_TIME_SERIES_ROWS: "${TIMESCALEDB_MAX_TIME_SERIES_ROWS:-100000}"
TIMESCALEDB_ASYNC_METRIC_COMMIT: "${TIMESCALEDB_ASYNC_METRIC_COMMIT:-false}"
FLEET_COMMAND_MAX_WORKERS: "${FLEET_COMMAND_MAX_WORKERS:-500}"
SV2_TRANSLATOR_ADVERTISED_HOST: "${SV2_TRANSLATOR_ADVERTISED_HOST:-}"
SV2_TRANSLATOR_CONNECT_HOST: "${SV2_TRANSLATOR_CONNECT_HOST:-}"
# Deployment-controlled half of the dual Modbus dial allowlist.
# Empty keeps all infrastructure writes disabled.
INFRASTRUCTURE_OT_CONTROL_SUBNETS: "${INFRASTRUCTURE_OT_CONTROL_SUBNETS:-}"
depends_on:
timescaledb:
condition: service_healthy
sv2-translator-helper:
condition: service_healthy
restart: always
network_mode: host
extra_hosts:
- "timescaledb:127.0.0.1"
volumes:
- sv2-translator-data:/var/lib/proto-fleet/sv2
- sv2-translator-control:/run/proto-fleet-sv2-helper:ro
logging:
driver: "json-file"
options:
max-size: "20m"
max-file: "5"
compress: "true"

sv2-translator-helper:
build:
context: ./server
command: ["/app/sv2-translator-helper"]
restart: unless-stopped
network_mode: none
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "test -S /run/proto-fleet-sv2-helper/runtime.sock"]
interval: 1s
timeout: 1s
retries: 30
start_period: 1s
volumes:
- sv2-translator-control:/run/proto-fleet-sv2-helper
- /var/run/docker.sock:/var/run/docker.sock

sv2-tproxy:
image: stratumv2/translator_sv2:main@sha256:70f7edbce640f0e70e44757d700de72ce286da95849b1b2692d250d4d549826d
container_name: proto-fleet-sv2-tproxy
profiles: ["sv2-tproxy"]
environment:
APP: "translator_sv2 -c /etc/proto-fleet/sv2/tproxy.toml"
labels:
com.protofleet.component: "sv2-tproxy"
com.protofleet.image: "stratumv2/translator_sv2:main@sha256:70f7edbce640f0e70e44757d700de72ce286da95849b1b2692d250d4d549826d"
volumes:
- sv2-translator-data:/etc/proto-fleet/sv2:ro
restart: unless-stopped
network_mode: host

fleet-client:
build:
context: ./client
Expand Down Expand Up @@ -108,6 +150,8 @@ services:

volumes:
timescaledb-data:
sv2-translator-data:
sv2-translator-control:

networks:
fleet-network:
Expand Down
14 changes: 12 additions & 2 deletions deployment-files/run-fleet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ prompt_store_reinit() {
read -p " Remove & reinitialize this volume now? ALL DATA WILL BE LOST (y/N): " answer
if [[ $answer =~ ^[Yy]$ ]]; then
echo " Shutting down containers…"
compose down --remove-orphans
compose --profile sv2-tproxy down --remove-orphans
echo " Removing volume $vol…"
docker volume rm "$vol"
echo " Volume removed; new credentials will apply next startup."
Expand Down Expand Up @@ -839,6 +839,10 @@ refresh_compose_env_args
# ----------------------------------------------------------------------------

echo "Pulling latest Docker images..."
if ! compose pull sv2-tproxy; then
echo "Error: Failed to pull the Stratum V2 translator image. Exiting."
exit 1
fi
compose pull

# Load pre-built TimescaleDB image if available (built in CI for the target architecture)
Expand All @@ -864,7 +868,13 @@ compose build --no-cache || { echo "Error: Build failed. Exiting."; exit 1; }
# ----------------------------------------------------------------------------

echo "Stopping any running services..."
compose down --remove-orphans
compose --profile sv2-tproxy down --remove-orphans

echo "Preparing the stopped Stratum V2 translator..."
if ! compose create sv2-tproxy; then
echo "Error: Failed to prepare the Stratum V2 translator. Exiting."
exit 1
fi

echo "Starting services..."
# --wait blocks until every service is running (or healthy, when a healthcheck is defined).
Expand Down
3 changes: 2 additions & 1 deletion deployment-files/server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ RUN apk add --no-cache nmap gcompat
WORKDIR /app

COPY fleetd /app/
COPY sv2-translator-helper /app/

RUN mkdir -p /app/plugins
COPY proto-plugin antminer-plugin asicrs-plugin asicrs-config.yaml /app/plugins/
COPY virtual-plugin virtual-plugin.json /app/optional-plugins/

RUN chmod 755 /app/fleetd /app/plugins/*-plugin /app/optional-plugins/virtual-plugin
RUN chmod 755 /app/fleetd /app/sv2-translator-helper /app/plugins/*-plugin /app/optional-plugins/virtual-plugin

EXPOSE 4000

Expand Down
30 changes: 30 additions & 0 deletions deployment-files/tests/test-profiles.sh
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,36 @@ assert_rendered "no-profile render keeps defaults" "$out" \
"log_parameter_max_length=0" "log_parameter_max_length_on_error=0" \
'shm_size: "268435456"'

service_block() { # rendered_output service_name
printf '%s\n' "$1" | awk -v service="$2" '
$0 == " " service ":" { active=1 }
active && /^ [^ ]/ && $0 != " " service ":" { exit }
active { print }
'
}

fleet_api_block=$(service_block "$out" fleet-api)
helper_block=$(service_block "$out" sv2-translator-helper)
if printf '%s\n' "$fleet_api_block" | grep -qF "/var/run/docker.sock"; then
fail "fleet-api must not mount the host Docker socket"
elif ! printf '%s\n' "$fleet_api_block" | grep -qF "source: sv2-translator-control" ||
! printf '%s\n' "$fleet_api_block" | grep -qF "read_only: true"; then
fail "fleet-api must mount the private translator helper socket read-only"
else
pass "fleet-api Docker access is isolated behind the private helper socket"
fi

for expected in \
"network_mode: none" \
"read_only: true" \
"no-new-privileges:true" \
"source: /var/run/docker.sock"; do
if ! printf '%s\n' "$helper_block" | grep -qF "$expected"; then
fail "sv2-translator-helper: expected hardened setting '$expected'"
fi
done
pass "sv2 translator helper hardening is rendered"

out=$(render --env-file profiles/mini.env --env-file base-secrets.env)
assert_rendered "mini render" "$out" \
"max_connections=100" "max_worker_processes=9" "random_page_cost=2.0" \
Expand Down
3 changes: 2 additions & 1 deletion server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ WORKDIR /app
# Copy the binary from builder and set permissions
COPY --from=builder /go/bin/dlv /app/dlv
COPY --from=builder /app/out/fleetd /app/fleetd
COPY --from=builder /app/out/sv2-translator-helper /app/sv2-translator-helper

RUN chmod 755 /app/fleetd
RUN chmod 755 /app/fleetd /app/sv2-translator-helper

# Create plugins directory
# Plugins are typically mounted via docker-compose volume for local dev
Expand Down
3 changes: 2 additions & 1 deletion server/Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ COPY server/ ./
# Warm /root/.cache/go-build in the image layer so sync+restart rebuilds are
# incremental without relying on a writable persistent cache volume.
RUN mkdir -p /app/plugins /app/out \
&& go build -gcflags="$GO_GCFLAGS" -o /app/out/fleetd ./cmd/fleetd
&& go build -gcflags="$GO_GCFLAGS" -o /app/out/fleetd ./cmd/fleetd \
&& go build -gcflags="$GO_GCFLAGS" -o /app/out/sv2-translator-helper ./cmd/sv2-translator-helper

EXPOSE 4000
EXPOSE 40000
Expand Down
2 changes: 2 additions & 0 deletions server/cmd/fleetd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/block/proto-fleet/server/internal/domain/plugins"
"github.com/block/proto-fleet/server/internal/domain/pools"
"github.com/block/proto-fleet/server/internal/domain/session"
"github.com/block/proto-fleet/server/internal/domain/sv2/translator"
"github.com/block/proto-fleet/server/internal/domain/telemetry"
"github.com/block/proto-fleet/server/internal/domain/telemetry/scheduler"
"github.com/block/proto-fleet/server/internal/domain/token"
Expand Down Expand Up @@ -44,6 +45,7 @@ type Config struct {
Auth token.Config `embed:"" prefix:"auth-" envprefix:"AUTH_"`
Session session.Config `embed:"" prefix:"session-" envprefix:"SESSION_"`
Pools pools.Config `embed:"" prefix:"pools-" envprefix:"POOLS_"`
SV2Translator translator.Config `embed:"" prefix:"sv2-translator-" envprefix:"SV2_TRANSLATOR_"`
Encrypt encrypt.Config `embed:"" prefix:"encrypt-" envprefix:"ENCRYPT_"`
Command command.Config `embed:"" prefix:"fleet-command-" envprefix:"FLEET_COMMAND_"`
Curtailment curtailmentReconciler.Config `embed:"" prefix:"curtailment-" envprefix:"CURTAILMENT_"`
Expand Down
15 changes: 12 additions & 3 deletions server/cmd/fleetd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import (
sitemapDomain "github.com/block/proto-fleet/server/internal/domain/sitemap"
sitesDomain "github.com/block/proto-fleet/server/internal/domain/sites"
"github.com/block/proto-fleet/server/internal/domain/stores/sqlstores"
"github.com/block/proto-fleet/server/internal/domain/sv2/translator"
"github.com/block/proto-fleet/server/internal/domain/telemetry"
"github.com/block/proto-fleet/server/internal/domain/telemetry/scheduler"
tokenDomain "github.com/block/proto-fleet/server/internal/domain/token"
Expand Down Expand Up @@ -483,6 +484,17 @@ func start(config *Config) error {

executionService := commandDomain.NewExecutionService(&config.Command, conn, dbMessageQueue, encryptSvc, tokenSvc, minerService, deviceStore, telemetryService, filesService)
executionService.WithMetricsEmitter(metricsProvider)
statusService := commandDomain.NewStatusService(conn, dbMessageQueue)
translatorManager, err := translator.NewManager(config.SV2Translator)
if err != nil {
return fmt.Errorf("initialize SV2 translator: %w", err)
}
if err := translatorManager.Resume(context.Background()); err != nil {
return fmt.Errorf("resume SV2 translator: %w", err)
}
commandSvc := commandDomain.NewService(&config.Command, conn, executionService, dbMessageQueue, statusService, encryptSvc, filesService, deviceStore, userStore, authSvc, telemetryService, pluginService, activitySvc)
commandSvc.SetPluginCapabilitiesProvider(pluginService)
commandSvc.SetSV2TranslatorManager(translatorManager)
Comment thread
pavlenex marked this conversation as resolved.
err = executionService.Start(context.Background())
if err != nil {
slog.Error("failed to start command execution service", "error", err)
Expand All @@ -492,9 +504,6 @@ func start(config *Config) error {
}()
}

statusService := commandDomain.NewStatusService(conn, dbMessageQueue)
commandSvc := commandDomain.NewService(&config.Command, conn, executionService, dbMessageQueue, statusService, encryptSvc, filesService, deviceStore, userStore, authSvc, telemetryService, pluginService, activitySvc)
commandSvc.SetPluginCapabilitiesProvider(pluginService)
// buildingStore is constructed below alongside siteStore; both are
// needed for the parseFilter cross-org check on building_ids and
// zone_keys. Hoist the construction so fleetMgmtSvc can depend on it.
Expand Down
46 changes: 46 additions & 0 deletions server/cmd/sv2-translator-helper/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
"context"
"flag"
"log/slog"
"os"
"os/signal"
"syscall"

"github.com/block/proto-fleet/server/internal/domain/sv2/translator"
)

func main() {
os.Exit(run())
}

func run() int {
listenSocket := flag.String(
"listen-socket",
translator.DefaultHelperSocket,
"private Unix socket exposed to fleet-api",
)
dockerSocket := flag.String(
"docker-socket",
translator.DefaultDockerSocket,
"Docker Engine Unix socket available only inside this helper",
)
flag.Parse()

ctx, stop := signal.NotifyContext(
context.Background(),
os.Interrupt,
syscall.SIGTERM,
)
defer stop()

if err := translator.ServeRuntimeHelper(ctx, translator.RuntimeHelperConfig{
ListenSocket: *listenSocket,
DockerSocket: *dockerSocket,
}); err != nil {
slog.Error("SV2 translator lifecycle helper stopped", "error", err)
return 1
}
return 0
}
51 changes: 51 additions & 0 deletions server/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,66 @@ services:
PLUGINS_DIR: "/app/plugins"
PLUGINS_ENABLED: "true"
PROTO_MINER_PORT: "8080"
# Containers on fleet-network can reach the translator by service name.
# Override with a LAN IP/hostname when testing physical miners.
SV2_TRANSLATOR_ADVERTISED_HOST: "${SV2_TRANSLATOR_ADVERTISED_HOST:-sv2-tproxy}"
SV2_TRANSLATOR_CONNECT_HOST: "sv2-tproxy"
Comment thread
pavlenex marked this conversation as resolved.
depends_on:
timescaledb:
condition: service_healthy
sv2-translator-helper:
condition: service_healthy
volumes:
- ./plugins:/app/plugins:ro
- sv2-translator-data:/var/lib/proto-fleet/sv2
- sv2-translator-control:/run/proto-fleet-sv2-helper:ro
restart: unless-stopped
networks:
- fleet-network
ports:
- "4000:4000"

sv2-translator-helper:
build:
context: ..
dockerfile: server/Dockerfile.dev
args:
GO_GCFLAGS: "${GO_GCFLAGS:-}"
command: ["/app/out/sv2-translator-helper"]
restart: unless-stopped
network_mode: none
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "test -S /run/proto-fleet-sv2-helper/runtime.sock"]
interval: 1s
timeout: 1s
retries: 30
start_period: 1s
volumes:
- sv2-translator-control:/run/proto-fleet-sv2-helper
- /var/run/docker.sock:/var/run/docker.sock

sv2-tproxy:
image: stratumv2/translator_sv2:main@sha256:70f7edbce640f0e70e44757d700de72ce286da95849b1b2692d250d4d549826d
container_name: proto-fleet-sv2-tproxy
profiles: ["sv2-tproxy"]
environment:
APP: "translator_sv2 -c /etc/proto-fleet/sv2/tproxy.toml"
labels:
com.protofleet.component: "sv2-tproxy"
com.protofleet.image: "stratumv2/translator_sv2:main@sha256:70f7edbce640f0e70e44757d700de72ce286da95849b1b2692d250d4d549826d"
ports:
- "34255:34255"
volumes:
- sv2-translator-data:/etc/proto-fleet/sv2:ro
restart: unless-stopped
networks:
- fleet-network

timescaledb:
extends:
file: ./docker-compose.base.yaml
Expand Down Expand Up @@ -332,6 +381,8 @@ services:
- NET_RAW
volumes:
timescaledb-data:
sv2-translator-data:
sv2-translator-control:

networks:
fleet-network:
Expand Down
Loading