Skip to content

Commit d0a7d0e

Browse files
committed
release: improve Linux defaults and harden renewal workflow
certbro-public-snapshot: true certbro-private-commit: a6db945df26c73b4157d2801a4c991b8d0183b6c certbro-private-branch: main
1 parent 3ddf450 commit d0a7d0e

16 files changed

Lines changed: 117 additions & 541 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ dist/
44
certbro
55
scripts/publish-public.sh
66
scripts/release-current.sh
7+
scripts/release-devel.sh
8+
scripts/testdeploy.sh
79
scripts/new-alias-deploy.sh
810
*.log
911
*.pem

README.de.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ curl -fsSL https://install.certbro.com/rf | CERTBRO_VERSION=v0.1.0 sh
4141

4242
## Schnellstart
4343

44-
Für Server-Deployments eine systemweite State-Datei verwenden:
44+
Standardmäßig nutzt `certbro` `/etc/certbro/state.json` als State-Datei und `/etc/certbro` als Root für verwaltete Zertifikate. Die folgenden Befehle nennen die Pfade der Klarheit halber trotzdem explizit:
4545

4646
```sh
4747
sudo mkdir -p /etc/certbro

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ curl -fsSL https://install.certbro.com/rf | CERTBRO_VERSION=v0.1.0 sh
4141

4242
## Quick Start
4343

44-
Use a system-wide state file for server deployments:
44+
By default, `certbro` uses `/etc/certbro/state.json` as the state file and `/etc/certbro` as the managed certificates root. The commands below keep the paths explicit for clarity:
4545

4646
```sh
4747
sudo mkdir -p /etc/certbro

docs/de/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ curl -fsSL https://install.certbro.com/rf | CERTBRO_VERSION=v0.1.0 sh
2929

3030
## Konfiguration
3131

32-
Für Server-Deployments ist eine eigene State-Datei unter `/etc/certbro` sinnvoll. So bleiben Konfiguration und verwalteter Zertifikatszustand an einem Ort:
32+
Standardmäßig nutzt `certbro` `/etc/certbro/state.json` und `/etc/certbro`. Für Server-Deployments bleiben damit Konfiguration und verwalteter Zertifikatszustand an einem Ort:
3333

3434
```sh
3535
sudo mkdir -p /etc/certbro

docs/en/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ curl -fsSL https://install.certbro.com/rf | CERTBRO_VERSION=v0.1.0 sh
2929

3030
## Configure
3131

32-
For server deployments, a dedicated state file under `/etc/certbro` keeps configuration and managed certificate state in one place:
32+
By default, `certbro` uses `/etc/certbro/state.json` and `/etc/certbro`. For server deployments, these defaults keep configuration and managed certificate state in one place:
3333

3434
```sh
3535
sudo mkdir -p /etc/certbro

internal/api/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ type TLSCertificate struct {
122122
OrderState string `json:"order_state,omitempty"`
123123
RevocationScope string `json:"revocation_scope,omitempty"`
124124
RevocationPendingScope string `json:"revocation_pending_scope,omitempty"`
125+
RenewalSupported bool `json:"renewal_supported,omitempty"`
125126
ReissueSupported bool `json:"reissue_supported"`
126127
ValidityDays int `json:"validity_days,omitempty"`
127128
SerialNumber string `json:"serial_number,omitempty"`

internal/api/client_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func TestGetCertificateParsesCurrentOpenAPIFields(t *testing.T) {
122122
"order_state": "ISSUED",
123123
"revocation_scope": "certificate",
124124
"revocation_pending_scope": "order",
125+
"renewal_supported": true,
125126
"reissue_supported": true,
126127
"validity_days": 199,
127128
"valid_from": "2026-03-18T10:00:00Z",
@@ -157,6 +158,9 @@ func TestGetCertificateParsesCurrentOpenAPIFields(t *testing.T) {
157158
if cert.RevocationScope != "certificate" || cert.RevocationPendingScope != "order" {
158159
t.Fatalf("revocation scopes = %q / %q", cert.RevocationScope, cert.RevocationPendingScope)
159160
}
161+
if !cert.RenewalSupported {
162+
t.Fatalf("RenewalSupported = %v, want true", cert.RenewalSupported)
163+
}
160164
if !cert.OrderCancellable || cert.OrderCancellationMode != "revoke_issued" {
161165
t.Fatalf("order cancellation fields = %#v", cert)
162166
}

internal/app/app.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (a *App) Run(ctx context.Context, args []string) error {
7373
root.StringVar(&opts.StateFile, "state-file", defaultStateFile, "path to certbro state file")
7474
root.StringVar(&opts.APIKey, "api-key", "", "regfish API key")
7575
root.StringVar(&opts.APIBaseURL, "api-base-url", "", "override API base URL")
76-
root.StringVar(&opts.CertificatesDir, "certificates-dir", strings.TrimSpace(os.Getenv("CERTBRO_CERTIFICATES_DIR")), "root directory for certbro-managed certificate directories")
76+
root.StringVar(&opts.CertificatesDir, "certificates-dir", firstNonEmpty(strings.TrimSpace(os.Getenv("CERTBRO_CERTIFICATES_DIR")), config.DefaultCertificatesDir), "root directory for certbro-managed certificate directories")
7777
root.Usage = func() {
7878
a.printRootUsage()
7979
}
@@ -867,13 +867,8 @@ func (a *App) storeForRenew(root rootOptions, store *config.Store) (*config.Stor
867867
return nil, err
868868
}
869869

870-
merged := &config.Store{
871-
Version: store.Version,
872-
APIKey: store.APIKey,
873-
APIBaseURL: store.APIBaseURL,
874-
ContactEmail: store.ContactEmail,
875-
UserAgentInstance: store.UserAgentInstance,
876-
}
870+
merged := *store
871+
merged.ManagedCertificates = nil
877872
seen := map[string]struct{}{}
878873
for _, cert := range discovered {
879874
key := cert.Name
@@ -889,7 +884,7 @@ func (a *App) storeForRenew(root rootOptions, store *config.Store) (*config.Stor
889884
seen[key] = struct{}{}
890885
merged.ManagedCertificates = append(merged.ManagedCertificates, cert)
891886
}
892-
return merged, nil
887+
return &merged, nil
893888
}
894889

895890
func firstNonEmpty(values ...string) string {

internal/app/app_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,43 @@ func TestNewClientIgnoresUserAgentInstanceEnvVar(t *testing.T) {
287287
t.Fatalf("client.UserAgent = %q, must not use env override", client.UserAgent)
288288
}
289289
}
290+
291+
func TestStoreForRenewPreservesVerifiedAPIConfiguration(t *testing.T) {
292+
root := t.TempDir()
293+
outputDir := filepath.Join(root, "example.com")
294+
validatedAt := time.Now().UTC().Round(time.Second)
295+
296+
managed := config.ManagedCertificate{
297+
Name: "example-com",
298+
CommonName: "example.com",
299+
OutputDir: outputDir,
300+
CertificateID: "CERT123",
301+
}
302+
if err := config.SaveManagedCertificate(outputDir, managed); err != nil {
303+
t.Fatalf("SaveManagedCertificate() error = %v", err)
304+
}
305+
306+
store := &config.Store{
307+
Version: config.CurrentVersion,
308+
APIKey: "valid-key",
309+
APIBaseURL: "https://api.regfish.com",
310+
APIKeyValidatedAt: &validatedAt,
311+
ContactEmail: "ops@example.com",
312+
UserAgentInstance: "host-01",
313+
}
314+
315+
app := &App{}
316+
renewStore, err := app.storeForRenew(rootOptions{CertificatesDir: root}, store)
317+
if err != nil {
318+
t.Fatalf("storeForRenew() error = %v", err)
319+
}
320+
if renewStore.APIKeyValidatedAt == nil || !renewStore.APIKeyValidatedAt.Equal(validatedAt) {
321+
t.Fatalf("renewStore.APIKeyValidatedAt = %v, want %v", renewStore.APIKeyValidatedAt, validatedAt)
322+
}
323+
if renewStore.APIKey != store.APIKey || renewStore.APIBaseURL != store.APIBaseURL {
324+
t.Fatalf("renewStore API configuration = %#v, want preserved global configuration", renewStore)
325+
}
326+
if len(renewStore.ManagedCertificates) != 1 || renewStore.ManagedCertificates[0].Name != "example-com" {
327+
t.Fatalf("renewStore.ManagedCertificates = %#v", renewStore.ManagedCertificates)
328+
}
329+
}

internal/app/manager.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,10 @@ func planRenewal(now time.Time, managed config.ManagedCertificate, remote *api.T
879879
return renewalReissue
880880
}
881881

882+
if !remote.RenewalSupported {
883+
return renewalNewOrder
884+
}
885+
882886
if remote.ID != "" {
883887
return renewalOrder
884888
}

0 commit comments

Comments
 (0)