Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
fc9e1a4
feat: add staging deployment bundles and tooling
bajtos Jun 26, 2026
9cda711
chore: generate staging identities & wallets
bajtos Jun 26, 2026
2bde8b5
fixup! remove Claude discussion summary
bajtos Jun 26, 2026
1767ae1
fix: make staging-bootstrap work over fish login shell
bajtos Jun 26, 2026
82ff2d0
fix: make staging-provision-core work against the box
bajtos Jun 26, 2026
362b751
fix: store staging key material in 1Password, not its file path
bajtos Jun 26, 2026
0bccf59
fix: catch crash-looping services in staging-deploy health gate
bajtos Jun 26, 2026
82127ce
fix: edit 1Password item in place and set field id
bajtos Jun 26, 2026
ebaa940
chore: regenerate private keys
bajtos Jun 26, 2026
0841ce2
feat: check out FORGE_REF on the box in staging-deploy
bajtos Jun 26, 2026
ee38cd3
feat: wipe bundle containers + data dirs on staging-provision
bajtos Jun 26, 2026
8343fb5
fix: drive staging-deploy health gate off docker inspect
bajtos Jun 26, 2026
fc30f56
fix: feed sprue S3 credentials via config file, not AWS env
bajtos Jun 29, 2026
a202a1c
docs: drop signing-service from staging did:web verify loop
bajtos Jun 29, 2026
db6edfa
fix: run staging piri container as root
bajtos Jun 29, 2026
fcd3e83
fix: surface real error when piri DID extraction fails
bajtos Jun 29, 2026
653c514
fix: route staging container calls via host-gateway, not public IP
bajtos Jun 29, 2026
5b07903
Revert "fix: route staging container calls via host-gateway, not publ…
bajtos Jun 29, 2026
439762c
staging: open UFW :443 so containers reach host Caddy
bajtos Jun 29, 2026
ff5e727
staging: add allow-list-piri step before piri deploy
bajtos Jun 29, 2026
e6b9266
staging: pin piri to pre-merge PR image bajtos-pr-24
bajtos Jul 1, 2026
7e22577
staging: add fund-payer step for FilecoinPay deposit
bajtos Jul 1, 2026
576179d
staging: connect piri to Lotus RPC over WebSocket
bajtos Jul 1, 2026
eeb6424
staging: bypass Storacha payment plans in sprue
bajtos Jul 1, 2026
4d2619f
docs: update info about email setup
bajtos Jul 1, 2026
342e7c1
fixup! cleanup
bajtos Jul 1, 2026
2a30f42
go mod tidy
bajtos Jul 1, 2026
b4765e8
docs: improve next steps
bajtos Jul 1, 2026
b853e22
test: fix e2e tests - update upload config
bajtos Jul 1, 2026
16a390c
Apply suggestions from code review
bajtos Jul 1, 2026
1518798
fix: address staging deployment PR review comments
bajtos Jul 1, 2026
418fb9e
fix: Piri docker image version back to `:main`
bajtos Jul 2, 2026
1a5ddef
feat: script piri provider registration for staging
bajtos Jul 2, 2026
3cc3bce
feat: disable piri indexer integration in staging
bajtos Jul 2, 2026
301b6e9
docs: recommend re-provisioning over in-place config edits
bajtos Jul 2, 2026
72550f3
Merge branch 'main' into staging-deployment
bajtos Jul 23, 2026
277bb64
feat: add hilt and ingot to the staging deployment
bajtos Jul 23, 2026
cb9a8cd
staging: add hilt and ingot delegation proofs
bajtos Jul 23, 2026
9af0a06
fix(staging): don't leak init argv into piri serve
bajtos Jul 23, 2026
66e2632
fix: drop duplicate use_path_style key left by merge
bajtos Jul 23, 2026
81d391e
docs(staging): record hilt/ingot deploy findings from the live run
bajtos Jul 23, 2026
6b971a2
docs(staging): correct the S3 region guidance in the smoke test
bajtos Jul 23, 2026
fbc3195
feat(staging): rename the S3 region to dev-ams
bajtos Jul 23, 2026
52b8e40
fix(staging): verify the provider region after register-ingot
bajtos Jul 23, 2026
ee626b5
fix(staging): no backticks inside register-ingot's ssh heredoc
bajtos Jul 23, 2026
6d2b060
Apply suggestion from @bajtos
bajtos Jul 24, 2026
13bbc25
revert(staging): don't modify systems/upload/config/config.yaml
bajtos Jul 24, 2026
f037e49
chore: go mod tidy
bajtos Jul 24, 2026
903e12d
clean up STAGING_DEPLOY.md and remove Guppy
bajtos Jul 24, 2026
fdf25bb
Address PR review comments on staging deploy
bajtos Jul 24, 2026
5ff0382
document how to deploy incremental updates
bajtos Jul 24, 2026
0bec0f4
feat(staging): run hilt-vault in persistent sealed mode
bajtos Jul 24, 2026
b2b3829
fix(staging): run hilt-vault as root so entrypoint chowns /vault/file
bajtos Jul 24, 2026
04ff934
fix(staging): skip Vault setcap step (binary absent in image)
bajtos Jul 24, 2026
b2ab246
fix(staging): unseal Vault via arg; make KV v2 enable idempotent
bajtos Jul 24, 2026
e10b342
fix(staging): auth KV setup via VAULT_TOKEN, not vault login
bajtos Jul 24, 2026
96b289a
fix(staging): normalize 1Password fields in vault-init upsert
bajtos Jul 24, 2026
59a92f3
feat(staging): add staging-reset one-shot destructive redeploy
bajtos Jul 24, 2026
b33c00f
docs cleanup
bajtos Jul 24, 2026
f405432
more doc improvements
bajtos Jul 24, 2026
0aa36a2
staging: rename ingot region dev-ams -> eu-central-3
bajtos Jul 24, 2026
1f59131
fix(staging): TCP healthcheck for bundle postgres to close initdb race
bajtos Jul 24, 2026
a8a2215
docs(staging): document postgres TCP healthcheck fix
bajtos Jul 24, 2026
cca7b84
docs(staging): emit .env.staging from access-key mint
bajtos Jul 24, 2026
a17073c
docs(staging): guard access-key mint against error responses
bajtos Jul 24, 2026
a1cfdaa
docs(staging): add s3:DeleteObjectVersion to smoke access key
bajtos Jul 24, 2026
dd3b3f4
docs(staging): report saved access key id on mint success
bajtos Jul 24, 2026
e5e43bd
staging: add CORS allowed origins to ingot config
bajtos Jul 27, 2026
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
105 changes: 104 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ workspace-build:
rm -f $(WORKSPACE_OVERRIDE); \
fi

.PHONY: help generate init up down restart clean nuke fresh logs pull build cli status guppy regen debug-upload ensure-state check-docker workspace-build shell-guppy shell-piri shell-upload shell-hilt
.PHONY: help generate init up down restart clean nuke fresh logs pull build cli status guppy regen debug-upload ensure-state check-docker workspace-build shell-guppy shell-piri shell-upload shell-hilt staging-keygen staging-bootstrap staging-provision-core staging-provision-piri staging-vault-init staging-deploy-core staging-allowlist-piri staging-deploy-piri staging-register-piri staging-register-ingot staging-fund-payer staging-reset

# Default target - show help
help:
Expand Down Expand Up @@ -82,6 +82,20 @@ help:
@echo "Debugging:"
@echo " make debug-upload Run upload (sprue) under Delve on localhost:2345"
@echo ""
@echo "Staging deployment (see docs/STAGING_DEPLOY.md):"
@echo " make staging-keygen Ensure keys+wallets+proofs exist in 1Password (idempotent)"
@echo " make staging-bootstrap One-time: prepare the box (repo, dirs, Caddy)"
@echo " make staging-provision-core Render core secrets from 1Password and ship to the box (dev machine only)"
@echo " make staging-provision-piri Render piri secrets from 1Password and ship to the box (dev machine only)"
@echo " make staging-vault-init Init + unseal hilt-vault; store keys in 1Password (run between provision-core and deploy-core)"
@echo " make staging-deploy-core Deploy the core bundle (sprue + signing-service + delegator + hilt + plc)"
@echo " make staging-allowlist-piri Allow-list the piri DID with the delegator (run before deploy-piri)"
@echo " make staging-deploy-piri Deploy the piri bundle (piri-0 + ingot)"
@echo " make staging-register-piri Register piri as a storage provider with sprue (run after both deploys)"
@echo " make staging-register-ingot Register ingot as hilt's regional provider (run after both deploys)"
@echo " make staging-fund-payer Deposit USDFC into FilecoinPay so piri can create a proof set"
@echo " make staging-reset Wipe ALL staging data and redeploy both bundles from scratch (destructive; keeps keys+wallets)"
@echo ""
@echo "Options:"
@echo " YES=1 Skip confirmation prompts (e.g., make nuke YES=1)"
@echo " SMELT_WORKSPACE=1 Run containers against binaries built from your local"
Expand Down Expand Up @@ -260,6 +274,95 @@ regen:
@echo "Keys and proofs regenerated."
@echo "Run 'make clean && make up' to restart services with new keys."

# --- Staging deployment ---------------------------------------------------
# Thin wrappers over the staging tooling; full runbook in docs/STAGING_DEPLOY.md.

# Ensure staging keys, EVM wallets, and UCAN proofs exist (idempotent: existing
# 1Password fields are reused, only missing ones are generated); write proofs to
# environments/staging/proofs/ (commit them); write wallet addresses (incl.
# PAYER_ADDRESS) into environments/staging/wallets.env (commit it).
staging-keygen:
@go run ./cmd/smelt staging keygen

# One-time: prepare the box — clone/update the repo, create secrets + data dirs,
# wire the Caddy snippet, verify. Pass REPO_URL on first run.
staging-bootstrap:
@./scripts/staging-bootstrap.sh

# Render configs/keys from 1Password and stream them to the box. Developer
# machine only (needs your op session + SSH); never run from CI. Per-bundle, since
# we typically deploy one bundle at a time.
staging-provision-core:
@./scripts/staging-provision.sh core

staging-provision-piri:
@./scripts/staging-provision.sh piri

# Initialize + unseal the persistent (Raft) hilt-vault and store its unseal key +
# root token in 1Password. Run after provision-core and before deploy-core: the
# unseal key/root token are minted at runtime by `vault operator init` (not by
# keygen) and rendered into vault-secrets.env, which deploy-core consumes.
# Developer machine only (needs your op session + jq). Idempotent.
staging-vault-init:
@./scripts/staging-vault-init.sh

# Deploy a bundle: pull pinned images, recreate, verify health.
staging-deploy-core:
@./scripts/staging-deploy.sh core

# Allow-list the piri DID with the core delegator. Run after deploy-core and
# before deploy-piri, else `piri init` step [4/7] gets a 403 from the delegator.
staging-allowlist-piri:
@./scripts/staging-allowlist-piri.sh

staging-deploy-piri:
@./scripts/staging-deploy.sh piri

# Register the piri node as a storage provider with sprue. Run after BOTH
# bundles are healthy, else uploads fail with "no storage providers available"
# (locally sprue's post_start hook does this; across bundles it can't).
staging-register-piri:
@./scripts/staging-register-piri.sh

# Register ingot as hilt's regional provider. Run after BOTH bundles are healthy
# and BEFORE creating any tenants, else the Tenant API rejects the region
# (locally hilt's post_start hook does this; across bundles it can't).
staging-register-ingot:
@./scripts/staging-register-ingot.sh

# Deposit USDFC into FilecoinPay for the payer + grant the warm-storage service
# operator approval, so `piri init`'s proof-set creation clears
# InsufficientLockupFunds. Developer machine only (needs your op session + cast).
# Amounts are baked in but env-overridable; see scripts/staging-fund-payer.sh.
staging-fund-payer:
@./scripts/staging-fund-payer.sh

# Full destructive reset of BOTH staging bundles in one shot: wipe all data
# (Hilt tenants + access keys, piri objects, ingot buckets + blob_locations,
# Postgres, DynamoDB, MinIO, the hilt-vault Raft store) and redeploy from
# scratch. Runs the whole provision -> vault-init -> deploy -> register sequence
# in the correct order, aborting on the first failure.
#
# What SURVIVES: all Ed25519 service identities and EVM wallet addresses (only
# re-shipped from 1Password, never regenerated) and all on-chain state (wallet
# balances, the payer's FilecoinPay deposit). What ROTATES: the hilt-vault unseal
# key + root token, since the Vault store is wiped and `vault operator init`
# mints them at runtime (staging-vault-init overwrites the two 1Password fields).
#
# Set FORGE_REF to deploy a branch other than main:
# FORGE_REF=staging-deployment make staging-reset
# Developer machine only (needs your op session + SSH); never run from CI.
staging-reset:
@$(MAKE) --no-print-directory staging-provision-core
@$(MAKE) --no-print-directory staging-provision-piri
@$(MAKE) --no-print-directory staging-vault-init
@$(MAKE) --no-print-directory staging-deploy-core
@$(MAKE) --no-print-directory staging-allowlist-piri
@$(MAKE) --no-print-directory staging-deploy-piri
@$(MAKE) --no-print-directory staging-register-piri
@$(MAKE) --no-print-directory staging-register-ingot
@echo "staging-reset complete."

# Pull latest pre-built images (ignores failures for local-only images)
pull: generated/compose/piri.yml ensure-state
$(COMPOSE) pull --ignore-pull-failures
Expand Down
110 changes: 110 additions & 0 deletions cmd/smelt/cmd/staging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package cmd

import (
"fmt"

"github.com/fil-forge/smelt/pkg/staging"
"github.com/spf13/cobra"
)

var stagingCmd = &cobra.Command{
Use: "staging",
Short: "Manage the Forge staging deployment",
}

var stagingKeygenCmd = &cobra.Command{
Use: "keygen",
Short: "Ensure staging keys, wallets, and proofs exist (idempotent)",
Long: `Ensures the staging stack's long-lived secrets exist in 1Password and writes
the (non-secret) UCAN delegation proofs into environments/staging/proofs/ to be
committed.

The ceremony is idempotent: every field the 1Password item already holds is
reused byte-for-byte — funded wallets, registered DIDs, and shipped keys survive
a re-run — and only missing fields are generated and added. Proofs are re-issued
only when missing or when a key they depend on was freshly generated.

It covers:
- Ed25519 service identity keys (PEM), incl. hilt and ingot
- real random secp256k1 EVM wallets for the payer, delegator transactor, and
piri owner — their addresses are printed so you can fund them via a Calibnet
faucet (private keys are stored in 1Password, never printed)
- random connection secrets (Postgres admin + per-service passwords, MinIO
keys, hilt partner key, ingot root S3 credentials)
- the indexing/egress/piri/hilt/ingot UCAN delegation proofs

The hilt Vault unseal key + root token are NOT minted here — they are produced
by "vault operator init" at runtime and stored in 1Password by
"make staging-vault-init".

To rotate a specific secret, delete its field from the 1Password item (and any
proof files signed with it) and re-run.`,
RunE: runStagingKeygen,
}

func init() {
rootCmd.AddCommand(stagingCmd)
stagingCmd.AddCommand(stagingKeygenCmd)
stagingKeygenCmd.Flags().StringP("project-dir", "d", ".", "project root directory")
stagingKeygenCmd.Flags().String("op-vault", "Fil One", "1Password vault")
stagingKeygenCmd.Flags().String("op-item", "FilOne Forge Staging", "1Password item title")
stagingKeygenCmd.Flags().Bool("store", true, "store generated secrets into 1Password via the op CLI")
stagingKeygenCmd.Flags().Bool("proofs", true, "generate UCAN delegation proofs (requires ucantool)")
stagingKeygenCmd.Flags().String("ucantool", "ucantool", "ucantool binary name or path")
}

func runStagingKeygen(cmd *cobra.Command, args []string) error {
projectDir, _ := cmd.Flags().GetString("project-dir")
opVault, _ := cmd.Flags().GetString("op-vault")
opItem, _ := cmd.Flags().GetString("op-item")
store, _ := cmd.Flags().GetBool("store")
proofs, _ := cmd.Flags().GetBool("proofs")
ucantool, _ := cmd.Flags().GetString("ucantool")

result, err := staging.Keygen(staging.Options{
ProjectDir: projectDir,
OPVault: opVault,
OPItem: opItem,
Store: store,
Proofs: proofs,
Ucantool: ucantool,
})
if err != nil {
return err
}

fmt.Println("Staging keygen complete.")
if len(result.GeneratedFields) > 0 {
fmt.Printf("\nNewly generated field(s):\n")
for _, f := range result.GeneratedFields {
fmt.Printf(" %s\n", f)
}
}
if len(result.ReusedFields) > 0 {
fmt.Printf("\nReused %d existing field(s) from 1Password (not rotated).\n", len(result.ReusedFields))
}
if len(result.GeneratedFields) == 0 && len(result.ReusedFields) > 0 {
fmt.Println("All secrets already existed — nothing was rotated.")
}
if len(result.ProofsWritten) > 0 {
fmt.Printf("\nProofs written (commit these):\n")
for _, p := range result.ProofsWritten {
fmt.Printf(" %s\n", p)
}
}
if len(result.OPFields) > 0 {
fmt.Printf("\nStored %d secret field(s) in 1Password item %q (vault %q):\n",
len(result.OPFields), opItem, opVault)
for _, f := range result.OPFields {
fmt.Printf(" %s\n", f)
}
}
if result.WalletsEnvPath != "" {
fmt.Printf("\nWrote wallet addresses to %s (commit it).\n", result.WalletsEnvPath)
}
fmt.Printf("\nFund these wallets on the Calibnet faucet (https://faucet.calibnet.chainsafe-fil.io):\n")
for role, addr := range result.FundAddresses {
fmt.Printf(" %-24s %s\n", role+":", addr)
}
return nil
}
Loading
Loading