diff --git a/k8s/base/dca/server.yaml b/k8s/base/dca/server.yaml index e2b0674..f5ef060 100644 --- a/k8s/base/dca/server.yaml +++ b/k8s/base/dca/server.yaml @@ -41,6 +41,8 @@ spec: value: "8088" - name: TASK_QUEUE_NAME value: "dca_plugin_queue" + - name: SERVER_TASK_QUEUE_NAME + value: "dca_plugin_queue" - name: SERVER_HOST value: "0.0.0.0" - name: SERVER_PORT @@ -157,6 +159,8 @@ spec: value: "8089" - name: TASK_QUEUE_NAME value: "dca_plugin_queue" + - name: SERVER_TASK_QUEUE_NAME + value: "dca_plugin_queue" - name: SERVER_HOST value: "0.0.0.0" - name: SERVER_PORT diff --git a/k8s/overlays/production/kustomization.yaml b/k8s/overlays/production/kustomization.yaml index 9e3a023..fc98f55 100644 --- a/k8s/overlays/production/kustomization.yaml +++ b/k8s/overlays/production/kustomization.yaml @@ -17,10 +17,10 @@ patchesStrategicMerge: # Image configuration for production # Override base images with proper registry and tags images: - # DCA Plugin (app-recurring) v1.0.82 + # DCA Plugin (app-recurring) - server v1.0.84 has TaskQueueName fix - name: docker.io/library/app-recurring-server:local-amd64 newName: ghcr.io/vultisig/app-recurring/server - newTag: v1.0.82 + newTag: v1.0.84 - name: docker.io/library/app-recurring-worker:local-amd64 newName: ghcr.io/vultisig/app-recurring/worker newTag: v1.0.82 @@ -40,10 +40,10 @@ images: - name: docker.io/library/vultisig-tx-indexer:local-amd64 newName: ghcr.io/vultisig/verifier/tx_indexer newTag: v0.1.16 - # VCLI v1.0.1 - fix: GetPluginServerURL reads env vars before defaults + # VCLI v1.0.3 - fix: billing fetch + shared libraries included in image - name: docker.io/library/vcli:local-amd64 newName: ghcr.io/vultisig/vcli - newTag: v1.0.1 + newTag: v1.0.3 # Patch deployments for production GHCR images patches: @@ -104,3 +104,25 @@ patches: kind: Deployment name: tx-indexer namespace: verifier + # Remove nodeSelector constraints from infra (allows any region) + - patch: |- + - op: remove + path: /spec/template/spec/nodeSelector + target: + kind: StatefulSet + name: postgres + namespace: infra + - patch: |- + - op: remove + path: /spec/template/spec/nodeSelector + target: + kind: StatefulSet + name: redis + namespace: infra + - patch: |- + - op: remove + path: /spec/template/spec/nodeSelector + target: + kind: StatefulSet + name: minio + namespace: infra diff --git a/local/Dockerfile b/local/Dockerfile index 6001131..738d056 100644 --- a/local/Dockerfile +++ b/local/Dockerfile @@ -1,16 +1,17 @@ -# Build stage -FROM golang:1.23-alpine AS builder +# Build stage - use Debian for glibc compatibility with go-wrappers +FROM golang:1.25-bookworm AS builder -RUN apk add --no-cache git +ARG TARGETARCH=amd64 + +RUN apt-get update && apt-get install -y git build-essential && rm -rf /var/lib/apt/lists/* WORKDIR /build # Copy go mod files COPY go.mod go.sum ./ -# Remove local replace directives for Docker build (use remote versions) -RUN sed -i '/^replace/,/^)/d' go.mod && \ - sed -i '/replace.*=>.*\.\.\//d' go.mod +# Remove only local path replace directives (keep version fixes) +RUN sed -i '/=> *\.\./d' go.mod # Download dependencies RUN go mod download @@ -18,16 +19,23 @@ RUN go mod download # Copy source code COPY cmd/ cmd/ -# Build binary -RUN CGO_ENABLED=0 GOOS=linux go build -o vcli ./cmd/vcli +# Build binary with CGO enabled for crypto libs +RUN CGO_ENABLED=1 GOOS=linux GOARCH=${TARGETARCH} go build -o vcli ./cmd/vcli + +# Find and copy shared libraries (from includes/linux/ subdirectory) +RUN mkdir -p /build/libs && \ + find /go/pkg/mod/github.com/vultisig/go-wrappers*/includes/linux -name "*.so" -exec cp {} /build/libs/ \; -# Runtime stage -FROM alpine:3.19 +# Runtime stage - use Debian slim for glibc compatibility +FROM debian:bookworm-slim -RUN apk add --no-cache ca-certificates +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* WORKDIR /app +# Copy shared libraries +COPY --from=builder /build/libs/*.so /usr/lib/ + # Copy binary from builder COPY --from=builder /build/vcli /usr/local/bin/vcli diff --git a/local/cmd/vcli/cmd/policy_generate.go b/local/cmd/vcli/cmd/policy_generate.go index 8cd9b06..298e0d0 100644 --- a/local/cmd/vcli/cmd/policy_generate.go +++ b/local/cmd/vcli/cmd/policy_generate.go @@ -123,22 +123,17 @@ func runPolicyGenerate(pluginID, from, to, amount, frequency, vaultName, toVault return fmt.Errorf("recipe validation failed: %w", err) } - // Include default free billing entries (required by DCA plugin) - // Both "once" and "per-tx" types are needed to match plugin pricing + // Fetch plugin pricing to build matching billing entries + billing, err := fetchPluginBilling(pluginID) + if err != nil { + // If we can't fetch pricing, use empty billing (plugin may not have pricing) + billing = []map[string]any{} + } + + // Build policy with recipe and billing policy := map[string]any{ - "recipe": recipe, - "billing": []map[string]any{ - { - "type": "once", - "amount": "0", - "asset": "usdc", - }, - { - "type": "per-tx", - "amount": "0", - "asset": "usdc", - }, - }, + "recipe": recipe, + "billing": billing, } jsonBytes, err := json.MarshalIndent(policy, "", " ") @@ -234,3 +229,75 @@ func extractErrorMessage(body []byte) string { } return string(body) } + +// fetchPluginBilling fetches the plugin's pricing and converts it to billing entries. +// The billing entries must match the plugin's pricing count for policy creation to succeed. +// Uses uint64 for amount to match verifier's expected type. +// Uses the public /plugins endpoint (no auth required) instead of /plugin/{id} (requires auth). +func fetchPluginBilling(pluginID string) ([]map[string]any, error) { + cfg, err := LoadConfig() + if err != nil { + return nil, err + } + + resolvedID := ResolvePluginID(pluginID) + + url := fmt.Sprintf("%s/plugins", cfg.Verifier) + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to fetch plugins: status %d", resp.StatusCode) + } + + var result struct { + Data struct { + Plugins []struct { + ID string `json:"id"` + Pricing []struct { + Type string `json:"type"` + Frequency *string `json:"frequency"` + Amount int64 `json:"amount"` + Asset string `json:"asset"` + } `json:"pricing"` + } `json:"plugins"` + } `json:"data"` + } + + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return nil, err + } + + var targetPricing []struct { + Type string `json:"type"` + Frequency *string `json:"frequency"` + Amount int64 `json:"amount"` + Asset string `json:"asset"` + } + for _, p := range result.Data.Plugins { + if p.ID == resolvedID { + targetPricing = p.Pricing + break + } + } + + var billing []map[string]any + for _, p := range targetPricing { + frequency := "" + if p.Frequency != nil { + frequency = *p.Frequency + } + entry := map[string]any{ + "type": p.Type, + "frequency": frequency, + "amount": uint64(p.Amount), + } + billing = append(billing, entry) + } + + return billing, nil +} diff --git a/local/scripts/run-services.sh b/local/scripts/run-services.sh index d268d09..9b98f86 100755 --- a/local/scripts/run-services.sh +++ b/local/scripts/run-services.sh @@ -151,7 +151,7 @@ cd "$ROOT_DIR/app-recurring" export MODE="swap" export SERVER_PORT="8082" export SERVER_HOST="0.0.0.0" -export SERVER_TASKQUEUENAME="dca_plugin_queue" +export TASK_QUEUE_NAME="dca_plugin_queue" export SERVER_ENCRYPTIONSECRET="dev-encryption-secret-32b" export POSTGRES_DSN="postgres://vultisig:vultisig@localhost:5432/vultisig-dca?sslmode=disable" export REDIS_URI="redis://:vultisig@localhost:6379"