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
7 changes: 3 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ vuln: $(GO_BOOTSTRAP)
$(GOPATH)/bin/govulncheck ./...

## sync-templates: Refresh mainnet + nile config templates from upstream
## into the embedded template dir (internal/render/templates).
## Source-of-truth URLs:
## mainnet: tronprotocol/java-tron develop branch
## nile: tron-nile-testnet/nile-testnet master branch
Expand All @@ -129,11 +130,9 @@ NILE_URL := https://raw.githubusercontent.com/tron-nile-testnet/nile-testnet/

sync-templates:
@echo "fetching mainnet template..."
curl -fsSL $(MAINNET_URL) -o main_net_config.conf
cp main_net_config.conf internal/render/templates/main_net_config.conf
curl -fsSL $(MAINNET_URL) -o internal/render/templates/main_net_config.conf
@echo "fetching nile template..."
curl -fsSL $(NILE_URL) -o test_net_config.conf
cp test_net_config.conf internal/render/templates/test_net_config.conf
curl -fsSL $(NILE_URL) -o internal/render/templates/test_net_config.conf
@echo "templates refreshed. Re-run 'make build test' to confirm."

## sync-schemas: Mirror schemas/output/ into internal/schema/files/ so
Expand Down
55 changes: 29 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
A command-line tool for deploying, managing, and diagnosing [java-tron](https://github.com/tronprotocol/java-tron) nodes using declarative intent files.

> **Heads-up for prior users of this repository.** Until recently this repo
> shipped only the three java-tron HOCON templates (`main_net_config.conf`,
> `test_net_config.conf`, `private_net_config.conf`) for users to copy and
> edit by hand. Those files are **still here, still authoritative, still
> synchronised with upstream** (see [Configuration Templates](#configuration-templates)
> below). What's new is `trond`, a CLI that consumes those same templates
> and a small declarative `intent.yaml` to render, deploy, and manage nodes
> end-to-end. If you only need the raw `.conf` files, nothing has changed —
> they live where they always did. If you want to skip hand-editing them,
> read on.
> shipped three java-tron HOCON templates (`main_net_config.conf`,
> `test_net_config.conf`, `private_net_config.conf`) at its root for users to
> copy and edit by hand. The mainnet and Nile templates are **no longer
> mirrored here** — take them from the upstream repositories that own them
> (see [Configuration Templates](#configuration-templates) below), while
> `private_net_config.conf` is still maintained in this repo. What's new is
> `trond`, a CLI that consumes those same templates and a small declarative
> `intent.yaml` to render, deploy, and manage nodes end-to-end. If you want
> to skip hand-editing configs, read on.

## Features

Expand Down Expand Up @@ -645,9 +645,10 @@ The image's entrypoint (`./bin/docker-entrypoint.sh`) execs

This repository started life as a curated set of HOCON config files for
java-tron. Operators would `wget` or `git clone` the file matching their
network and then hand-edit it. That workflow is still supported (see
[Configuration Templates](#configuration-templates) — the same files
sit at the repo root and get refreshed from upstream on every release).
network and then hand-edit it. That workflow is still supported — only the
mainnet and Nile files now come from the upstream repositories that own them
rather than from this repo's root, while `private_net_config.conf` is still
maintained here (see [Configuration Templates](#configuration-templates)).

What this repo *also* provides now is a small, opinionated CLI that
removes the hand-editing step. The same templates are embedded in the
Expand All @@ -657,24 +658,23 @@ deterministically; `trond apply` deploys it.

| Workflow | Before | Now (optional) |
|---|---|---|
| Get a template | `git clone` + open `main_net_config.conf` | `trond config render <intent.yaml>` |
| Get a template | `wget` the [upstream `config.conf`](https://github.com/tronprotocol/java-tron/blob/develop/framework/src/main/resources/config.conf) + hand-edit | `trond config render <intent.yaml>` |
| Tweak ports / features | Edit the `.conf` directly | Set `ports:` / `features:` in intent |
| Apply changes to a node | scp + restart by hand | `trond apply --intent <file>` (idempotent) |
| Multi-node private network | Repeat the above N times | `trond network create --intent <file>` |
| Look up "what does this HOCON key mean?" | grep / docs / java-tron source | `trond config docs <key>` |

Both flows coexist:

- **Pure template users** can still `cat main_net_config.conf` or `git pull`
this repo for the latest mainnet config, ignore `bin/`, and never touch
the CLI.
- **Pure template users** can still fetch the mainnet config straight from
Comment thread
SeriousCoding789 marked this conversation as resolved.
[java-tron](https://github.com/tronprotocol/java-tron/blob/develop/framework/src/main/resources/config.conf),
ignore `bin/`, and never touch the CLI.
- **CLI users** never need to edit the `.conf` files directly — `trond`
handles rendering, ports, validation, and lifecycle.

The CLI lives under `cmd/` and `internal/`. The original config files
remain at the repo root. `make sync-templates` re-fetches them from
upstream into both the root and the CLI's embedded copy so the two stay
in lockstep.
The CLI lives under `cmd/` and `internal/`. The templates the CLI renders
from are embedded in the binary under `internal/render/templates/`;
`make sync-templates` refreshes the mainnet and Nile copies from upstream.

## Companion Tools

Expand All @@ -698,11 +698,13 @@ in releases, alongside the main `trond` tarball.

## Configuration Templates

Base java-tron configuration templates rendered into per-node HOCON. The
mainnet and Nile templates track upstream — periodically refresh from the
Base java-tron configuration templates rendered into per-node HOCON. They
live in `internal/render/templates/` and are embedded into the `trond`
binary. The mainnet and Nile templates track upstream and are **not**
mirrored at the repo root — periodically refresh them from the
authoritative sources before tagging a release:

| File | Network | Upstream source of truth |
| Template | Network | Upstream source of truth |
|---|---|---|
| `main_net_config.conf` | Mainnet | https://github.com/tronprotocol/java-tron/blob/develop/framework/src/main/resources/config.conf |
| `test_net_config.conf` | Nile testnet | https://github.com/tron-nile-testnet/nile-testnet/blob/master/framework/src/main/resources/config-nile.conf |
Expand All @@ -715,9 +717,10 @@ make sync-templates # fetches mainnet + nile, leaves private alone
```

After a sync, run `make test` and `./bin/trond config validate examples/*.yaml`
to confirm nothing broke. Keep both copies in sync — `templates/<file>` is a
symlink pointing at the root `<file>`, and `internal/render/templates/<file>`
is the embedded copy used at runtime.
to confirm nothing broke. The mainnet and Nile templates live only in
`internal/render/templates/`. `private_net_config.conf` additionally keeps a
repo-root copy that `templates/private_net_config.conf` symlinks to — keep it
in sync with the embedded copy used at runtime.

## Examples

Expand Down
20 changes: 2 additions & 18 deletions cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/tronprotocol/tron-deployment/internal/guard"
"github.com/tronprotocol/tron-deployment/internal/intent"
"github.com/tronprotocol/tron-deployment/internal/output"
"github.com/tronprotocol/tron-deployment/internal/render"
"github.com/tronprotocol/tron-deployment/internal/state"
"github.com/tronprotocol/tron-deployment/internal/target"
)
Expand Down Expand Up @@ -150,7 +151,7 @@ func runApply(cmd *cobra.Command, args []string) error {
State: deployState,
IntentHash: intentHash,
Existing: existing,
TemplateDir: findTemplatesDir(),
TemplateDir: render.FindTemplatesDir(),
DeploymentsDir: deploymentsDir(),
EnvVars: resolveEnvVars(&parsed.Nodes[0]),
IntentPath: applyIntentPath, // FR-021: relative build.source resolves vs this
Expand Down Expand Up @@ -225,23 +226,6 @@ func resolveEnvVars(node *intent.NodeSpec) map[string]string {
return env
}

// findTemplatesDir returns an optional on-disk templates directory.
// Empty return signals render to use the embedded copy.
func findTemplatesDir() string {
if d := os.Getenv("TROND_TEMPLATES_DIR"); d != "" {
return d
}
candidates := []string{"templates", "./templates"}
for _, c := range candidates {
if info, err := os.Stat(c); err == nil && info.IsDir() {
if _, err := os.Stat(c + "/main_net_config.conf"); err == nil {
return c
}
}
}
return ""
}

// exitWithError returns a StructuredError for propagation through cobra RunE.
func exitWithError(code string, exitCode int, msg string, suggestions ...string) error {
return output.NewError(code, exitCode, msg).WithSuggestions(suggestions...)
Expand Down
2 changes: 1 addition & 1 deletion cmd/config/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func runDiff(cmd *cobra.Command, args []string) error {
}

// Render new config
templateDir := findTemplateDir()
templateDir := render.FindTemplatesDir()
node := &parsed.Nodes[0]

rendered, err := render.RenderHOCONWithSecrets(templateDir, parsed, node)
Expand Down
18 changes: 1 addition & 17 deletions cmd/config/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func runRender(cmd *cobra.Command, args []string) error {
}

// Find templates directory (relative to binary or working directory)
templateDir := findTemplateDir()
templateDir := render.FindTemplatesDir()

rendered := make([]renderedNode, 0, len(parsed.Nodes))
anyRedacted := false
Expand Down Expand Up @@ -262,19 +262,3 @@ func writeSecretFile(path string, data []byte) (err error) {
}
return nil
}

// findTemplateDir prefers the TROND_TEMPLATES_DIR env var, then falls back to
// ./templates. An empty return value tells render.RenderHOCON to use the
// embedded copy — release binaries work without any co-located files.
func findTemplateDir() string {
if d := os.Getenv("TROND_TEMPLATES_DIR"); d != "" {
return d
}
candidates := []string{"templates", "./templates"}
for _, c := range candidates {
if info, err := os.Stat(c); err == nil && info.IsDir() {
return c
}
}
return ""
}
2 changes: 1 addition & 1 deletion cmd/network/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func runAdd(cmd *cobra.Command, args []string) error {
intent.ApplyMonitoringDefaults(parsed.Monitoring)
}

templateDir := findTemplatesDir()
templateDir := render.FindTemplatesDir()
rendered, err := render.RenderHOCONWithSecrets(templateDir, parsed, node)
if err != nil {
return fmt.Errorf("render config: %w", err)
Expand Down
18 changes: 1 addition & 17 deletions cmd/network/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"slices"
"time"

Expand Down Expand Up @@ -82,7 +81,7 @@ func runCreate(cmd *cobra.Command, args []string) error {
tgt = target.NewLocalTarget()
}

templateDir := findTemplatesDir()
templateDir := render.FindTemplatesDir()
workDir := paths.Deployments()

// Hold the state lock across the whole load-modify-save cycle: this
Expand Down Expand Up @@ -271,21 +270,6 @@ func autoWireActivePeers(parsed *intent.Intent) {
}
}

func findTemplatesDir() string {
if d := os.Getenv("TROND_TEMPLATES_DIR"); d != "" {
return d
}
candidates := []string{"templates", "./templates"}
for _, c := range candidates {
if info, err := os.Stat(c); err == nil && info.IsDir() {
if _, err := os.Stat(filepath.Join(c, "main_net_config.conf")); err == nil {
return c
}
}
}
return ""
}

// deployNetworkMonitoring deploys a single monitoring stack for an entire
// private network. All nodes are scraped via the shared docker network.
func deployNetworkMonitoring(ctx context.Context, tgt target.Target, workDir string, parsed *intent.Intent) monitoringResult {
Expand Down
2 changes: 1 addition & 1 deletion cmd/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func runPlan(cmd *cobra.Command, args []string) error {
existing := store.GetNode(deployState, parsed.Name)

// 4. Render config to compute config hash
templateDir := findTemplatesDir()
templateDir := render.FindTemplatesDir()
node := &parsed.Nodes[0]

rendered, err := render.RenderHOCONWithSecrets(templateDir, parsed, node)
Expand Down
2 changes: 1 addition & 1 deletion cmd/verify_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func runVerifyConfig(cmd *cobra.Command, args []string) error {
// comparison runs against the REAL bytes (redacting here would make
// every witness node report permanent false drift against its live
// conf); lineDiff redacts each line as it emits it.
renderedDesired, err := render.RenderHOCONWithSecrets(findTemplatesDir(), parsed, &parsed.Nodes[0])
renderedDesired, err := render.RenderHOCONWithSecrets(render.FindTemplatesDir(), parsed, &parsed.Nodes[0])
if err != nil {
return exitWithError("RENDER_ERROR", output.ExitGeneralError, err.Error())
}
Expand Down
30 changes: 30 additions & 0 deletions internal/render/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ var embeddedTemplates embed.FS
//go:embed dashboards/*.json
var embeddedDashboards embed.FS

// localTemplatesDir is the working-directory override FindTemplatesDir looks
// for when TROND_TEMPLATES_DIR is unset.
const localTemplatesDir = "templates"

// LoadTemplate returns the raw HOCON template for the given network. When
// templateDir is non-empty and contains the matching file, the on-disk copy
// wins (useful for local development and tests). Otherwise we fall through
Expand Down Expand Up @@ -48,3 +52,29 @@ func LoadTemplate(templateDir, network string) ([]byte, error) {
func LoadDashboard(name string) ([]byte, error) {
return embeddedDashboards.ReadFile("dashboards/" + name)
}

// FindTemplatesDir resolves the on-disk templates directory that overrides
// the embedded copies, or "" to use the embedded ones. This is the single
// definition of "what counts as a templates directory" — every command
// resolves through it, so the rule can only ever change in one place.
//
// TROND_TEMPLATES_DIR wins outright. Otherwise a local ./templates directory
// counts only when it actually carries one of the known templates; any single
// one is enough, since keying off one particular network would ignore a
// directory that only carries the others. LoadTemplate still falls back to
// the embedded copy per network, so a partial directory is fine.
func FindTemplatesDir() string {
if d := os.Getenv("TROND_TEMPLATES_DIR"); d != "" {
return d
}
info, err := os.Stat(localTemplatesDir)
if err != nil || !info.IsDir() {
return ""
}
for _, name := range NetworkTemplate {
if _, err := os.Stat(filepath.Join(localTemplatesDir, name)); err == nil {
return localTemplatesDir
}
}
return ""
}
70 changes: 70 additions & 0 deletions internal/render/embed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package render

import (
"os"
"path/filepath"
"testing"
)

// FindTemplatesDir is the single rule every command resolves the on-disk
// template override through. It used to be copy-pasted into three cmd
// packages with three different notions of what a templates dir is; these
// tests pin the one rule so a future edit can't quietly re-fork it.
func TestFindTemplatesDir(t *testing.T) {
// chdir into a scratch dir so a real ./templates in the repo root
// (the private_net_config.conf symlink) can't leak into these cases.
chdirTemp := func(t *testing.T) string {
t.Helper()
dir := t.TempDir()
t.Chdir(dir)
return dir
}

t.Run("env var wins outright", func(t *testing.T) {
chdirTemp(t)
t.Setenv("TROND_TEMPLATES_DIR", "/somewhere/else")
if got := FindTemplatesDir(); got != "/somewhere/else" {
t.Fatalf("got %q, want /somewhere/else", got)
}
})

t.Run("no templates dir means embedded", func(t *testing.T) {
chdirTemp(t)
t.Setenv("TROND_TEMPLATES_DIR", "")
if got := FindTemplatesDir(); got != "" {
t.Fatalf("got %q, want empty", got)
}
})

t.Run("dir without a known template means embedded", func(t *testing.T) {
dir := chdirTemp(t)
t.Setenv("TROND_TEMPLATES_DIR", "")
if err := os.Mkdir(filepath.Join(dir, localTemplatesDir), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, localTemplatesDir, "notes.txt"), []byte("x"), 0o644); err != nil {
t.Fatalf("write: %v", err)
}
if got := FindTemplatesDir(); got != "" {
t.Fatalf("got %q, want empty", got)
}
})

// Any single known template is enough: keying off one particular
// network would ignore a directory that only carries the others.
for network, file := range NetworkTemplate {
t.Run("dir carrying only "+network, func(t *testing.T) {
dir := chdirTemp(t)
t.Setenv("TROND_TEMPLATES_DIR", "")
if err := os.Mkdir(filepath.Join(dir, localTemplatesDir), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, localTemplatesDir, file), []byte("x"), 0o644); err != nil {
t.Fatalf("write: %v", err)
}
if got := FindTemplatesDir(); got != localTemplatesDir {
t.Fatalf("got %q, want %q", got, localTemplatesDir)
}
})
}
}
3 changes: 3 additions & 0 deletions internal/render/hocon.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
)

// NetworkTemplate maps network names to their base config template file.
// The files live in internal/render/templates and are embedded into the
// binary — the repository root no longer carries mainnet or Nile copies.
// Both track upstream and are refreshed by `make sync-templates`.
var NetworkTemplate = map[string]string{
"mainnet": "main_net_config.conf",
"nile": "test_net_config.conf",
Expand Down
Loading
Loading