[Tutorial] Run your own satellite (part 9) - Satellite admin

The admin subcommand ./satellite-modular admin starts the Satellite Admin Server — an internal HTTP server/API for satellite operators to perform administrative tasks. - Claude


Command to run ./satellite-modular admin

This one have knots that you need to be aware of, the minimum useful (not absolute minimum) command to run satellite admin server:

./satellite-modular admin
--identity-dir ./id/satellite/ \
--database-options.url "cockroach://my_role:123456@rendezvous.example.com:26257/my_database?sslmode=verify-full&sslrootcert=ca.crt" \
--metainfo.database-url "cockroach://my_role:123456@rendezvous.example.com:26257/my_metainfo_database?sslmode=verify-full&sslrootcert=ca.crt" \
--mail.from "no-reply@localhost" \
--live-accounting.storage-backend redis://127.0.0.1:6379 \
--mail.template-path=./emails \
--admin.static-dir=./ \
--payments.stripe-coin-payments.stripe-secret-key=sk_live_zzzJpevZZCkAwMYQiKKQQ1Nu \ # not a real key
--admin.address=127.0.0.1:8080 \
--admin.bypass-auth=true

There are some new flags:

--mail.from: for now we can set this as no-reply@localhost, we will need to register with an email service provider and config it later.

--mail.template-path=./emails: copy emails directory from storj/web/satellite/static/emails at main · storj/storj · GitHub.

--admin.static-dir=./: follow build instruction here storj/satellite/admin/ui at main · storj/storj · GitHub, after build, point to the build directory (./ is expecting a build directory, not just files inside it).

--payments.stripe-coin-payments.stripe-secret-key: I think this is an oversight from StorJ? why is this config required, someone might not use stripe?. It will try to use stripe secret on start up, and if you don’t provide one:

2026-06-16T20:42:59+07:00 ERROR accountfreeze:chore Could not list invoices {"error": "billing-freeze-chore: stripecoinpayments service: {"status":401,"message":"You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https://stripe.com/docs/api#authentication for details, or we can help at https://support.stripe.com/.","type":"invalid_request_error"}", "errorVerbose": "billing-freeze-chore: stripecoinpayments service: {"status":401,"message":"You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https://stripe.com/docs/api#authentication for details, or we can help at https://support.stripe.com/.","type":"invalid_request_error"}\n\tstorj.io/storj/satellite/payments/stripe.(*invoices).list:359\n\tstorj.io/storj/satellite/payments/stripe.(*invoices).ListOpen:379\n\tstorj.io/storj/satellite/accountfreeze.(*Chore).attemptBillingFreezeWarn:89\n\tstorj.io/storj/satellite/accountfreeze.(*Chore).Run.func1:77\n\tstorj.io/common/sync2.(*Cycle).Run:102\n\tstorj.io/storj/satellite/accountfreeze.(*Chore).Run:75\n\tstorj.io/storj/shared/mud.registerFunc[...].func2:444\n\tstorj.io/storj/shared/mud.(*Component).Run.func1:84\n\tgolang.org/x/sync/errgroup.(*Group).Go.func1:93"}

--admin.address=127.0.0.1:8080: this option is actually optional, but if don’t provide one, it will not create a portal for you to interact with satellite admin server, is this intentional?

--admin.bypass-auth=true: if you are going solo, this is the fastest way, if it ever work out, later we could change this and use some open source OIDC: keycloak, authentik, authelia as front end authentication, see more flags at ./satellite-modular admin -h | grep oidc.

Miscellaneous

While browsing around storj source code, I’ve some extra infomation for you:

  • StorJ have 4 formal type of users: Free, Paid, NFR (Not For Resale) and Member. Still, it not clear what does NFR and Member mean and are for.
  • If you plan to use STORJ crypto cryptocurrency, see this flag: --payments.storjscan.endpoint and checkout GitHub - storj/storjscan · GitHub, the idea is that: you set up storjscan as a service and point --payments.storjscan.endpoint to it. There is also a consumer side of storjscan service on satellite/payments/storjscan.

Unanswer questions:

What are the procedures when update pricing? How does it even work?

For now, I give up on that question, will do a revisit after my satellite already up, there are resources but I wasn’t able to put it together:

# keyword to research
./satellite-modular admin -h | grep -E 'remainder|retention|price|charge|entitlement'
        --console.free-trial-duration string                                                 duration for which users can access the system free of charge, 0 = unlimited time trial (default "0")
        --payments.stripe-coin-payments.populate-min-retention-invoice-line-item string      whether to populate minimum retention info in invoice line items (default "false")
        --payments.stripe-coin-payments.retention-remainder-batch-size string                number of deletion remainder records to process in a single batch (default "300")
        --payments.usage-price.storage-tb string                                             price user should pay for storage per month in dollars/TB (default "4")
        --payments.usage-price.egress-tb string                                              price user should pay for egress in dollars/TB (default "7")
        --payments.usage-price.segment string                                                price user should pay for segments stored on network per month in dollars/segment (default "0.0000088")
        --payments.minimum-charge.amount string                                              minimum amount in cents to charge customers per invoice period (0 to disable) (default "0")
        --payments.minimum-charge.effective-date string                                      date after which all users will have minimum charges applied (YYYY-MM-DD), empty to apply immediately
        --payments.products string                                                           a YAML list of products with their price structures. See satellite/payments/paymentsconfig/README.md for more details.
        --payments.placement-price-overrides string                                          a YAML mapping of product ID to placements. See satellite/payments/paymentsconfig/README.md for more details.
        --payments.usage-price-overrides string                                              semicolon-separated usage price overrides in the format partner:storage,egress,segment,egress_discount_ratio. The egress discount ratio is the ratio of free egress per unit-month of storage
        --payments.package-plans string                                                      semicolon-separated partner package plans in the format partner:price,credit. Price and credit are in cents USD.
        --account-freeze.price-threshold string                                              The failed invoice amount (in cents) beyond which an account will not be frozen (default "100000")
        --entitlements.enabled string                                                        indicates whether the entitlements service is enabled (default "false")
        --metainfo.project-entitlement.cache-expiration string                               delete objects hook cache expiration (default "10m")
        --metainfo.project-entitlement.cache-capacity string                                 delete objects hook cache capacity (default "10000")
        --metainfo.create-remainder-charge-on-object-delete string                           whether to create a remainder charge when an object is deleted before minimum retention (default "false")


# paymentsconfig
https://github.com/storj/storj/tree/main/satellite/payments/paymentsconfig

Have anyone figure that out the pricing update procedure, please comment below, thanks and see you in part 10.

Turn out, this is in fact a pain point, both admin and console will interact with stripe behind the scene, for example: when user registration, it will make call to create customer in stripe dashboard.

Which is very problematic, some enthusiast community don’t use money, and stripe only support about ~50 countries.

Claude suggest me a patch to restore the old behavior of cmd/satellite, that is: if you don’t set stripe it will not use stripe.

This is the script I use in my jenkins:

cat > remove-client.yml <<EOF
id: remove-stripe-client
language: Go

rule:
  pattern: |
    mud.Provide[Client](ball, NewStripeClient)

fix: ""
EOF
cat > inject-provider.yml <<EOF 
id: inject-provider
language: Go

rule:
  pattern: |
    stripe.Module(ball)

fix: |
  mud.Provide[stripe.Client](ball, func(log *zap.Logger, pc paymentsconfig.Config, stripeDB stripe.DB, usersDB console.Users) (stripe.Client, error) {
      switch pc.Provider {
      case "":
          return stripe.NewStripeMock(stripeDB.Customers(), usersDB), nil
      case "mock":
          return pc.MockProvider, nil
      case "stripecoinpayments":
          return stripe.NewStripeClient(log, pc.StripeCoinPayments), nil
      default:
          return nil, errs.New("invalid stripe coin payments provider %q", pc.Provider)
      }
  })

  stripe.Module(ball)
EOF

Then:

ast-grep scan \
  --rule remove-client.yml \
  --update-all \
  satellite/payments/stripe/mud.go
  
ast-grep scan \
  --rule inject-provider.yml \
  --update-all \
  satellite/mud.go

I will try to send this patch upstream to StorJ later.

Notice admin and core both have accountfreeze.Chore and accountfreeze.TrialFreezeChore:

// https://github.com/storj/storj/blob/fa38194d5149cfe8f89bf15cd6d7b2bf8c091568/satellite/run/admin.go#L17-L24

func (a *Admin) GetSelector(ball *mud.Ball) mud.ComponentSelector {
	return mud.Or(
		Observability(ball),
		mud.Select[*admin.Server](ball),
		mud.Select[*accountfreeze.Chore](ball),
		mud.Select[*accountfreeze.TrialFreezeChore](ball))
}
// https://github.com/storj/storj/blob/fa38194d5149cfe8f89bf15cd6d7b2bf8c091568/satellite/run/core.go#L37-L66

Wouldn’t this will cause double the work if it run on separated process?

It’s look like there is a hidden repo storj-admin (?).

To mitigate this, on admin, you should use exec with component admin.Server instead of run satellite-modular admin, see [Tutorial] Run your own satellite (part 10) - Introducing mud and ranged-loop - #16 by kocoten1992

That included an old experimental, ruby-on-rails based on admin. It had too many limitation (for example couldn’t use existing golang based structures and configs). It was abandoned and the new version of admin UI is the open source project.

For the wiring: thanks for the report. I will check it…