Compare commits
3 Commits
checkconf
...
0503370489
Author | SHA1 | Date | |
---|---|---|---|
|
0503370489 | ||
|
6b11481739 | ||
|
d027ec9108 |
30
README.md
30
README.md
@@ -36,23 +36,23 @@ logs:
|
|||||||
3. **Generate private keys:**
|
3. **Generate private keys:**
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /etc/tesseract/keys
|
mkdir -p /etc/tesseract/keys
|
||||||
./tesseract-genconf -c config.yaml gen-key
|
./tesseract-genconf -c config.yaml --write gen-key
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Create directories and generate environment files:**
|
4. **Create directories and generate environment files:**
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /var/lib/tesseract/example2025h1/data
|
mkdir -p /var/lib/tesseract/example2025h1/data
|
||||||
./tesseract-genconf -c config.yaml gen-env
|
./tesseract-genconf -c config.yaml --write gen-env
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Generate HTML and JSON files:**
|
5. **Generate HTML and JSON files:**
|
||||||
```bash
|
```bash
|
||||||
./tesseract-genconf -c config.yaml gen-html
|
./tesseract-genconf -c config.yaml --write gen-html
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **Generate nginx configuration files:**
|
6. **Generate nginx configuration files:**
|
||||||
```bash
|
```bash
|
||||||
./tesseract-genconf -c config.yaml gen-nginx
|
./tesseract-genconf -c config.yaml --write gen-nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
The port from the main `listen:` field will be used in the NGINX server blocks (in our case
|
The port from the main `listen:` field will be used in the NGINX server blocks (in our case
|
||||||
@@ -66,3 +66,25 @@ The port from the main `listen:` field will be used in the NGINX server blocks (
|
|||||||
# For production environment, take the ccadb 'production' roots
|
# For production environment, take the ccadb 'production' roots
|
||||||
./tesseract-genconf gen-roots --source https://gouda2027h2.log.ct.ipng.ch/ --output roots-production.pem
|
./tesseract-genconf gen-roots --source https://gouda2027h2.log.ct.ipng.ch/ --output roots-production.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Safe File Operations with `--diff` and `--write`
|
||||||
|
|
||||||
|
The `tesseract-genconf` tool includes safety features to prevent accidental file modifications:
|
||||||
|
|
||||||
|
- **`--diff`**: Shows colored unified diffs of what would change without writing files
|
||||||
|
- **`--write`**: Required flag to actually write files to disk
|
||||||
|
- **`--no-color`**: Disables colored diff output (useful for redirecting to files)
|
||||||
|
|
||||||
|
**Recommended workflow:**
|
||||||
|
```bash
|
||||||
|
# 1. First, preview changes with --diff
|
||||||
|
./tesseract-genconf -c config.yaml --diff gen-html
|
||||||
|
|
||||||
|
# 2. Review the colored diff output, then apply changes
|
||||||
|
./tesseract-genconf -c config.yaml --write gen-html
|
||||||
|
|
||||||
|
# 3. Or combine both to see diffs and write files
|
||||||
|
./tesseract-genconf -c config.yaml --diff --write gen-html
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Flags must come before the command name (e.g., `--diff gen-html`, not `gen-html --diff`).
|
||||||
|
5
go.mod
5
go.mod
@@ -3,3 +3,8 @@ module cheese
|
|||||||
go 1.24.4
|
go 1.24.4
|
||||||
|
|
||||||
require gopkg.in/yaml.v3 v3.0.1
|
require gopkg.in/yaml.v3 v3.0.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
)
|
||||||
|
4
go.sum
4
go.sum
@@ -1,3 +1,7 @@
|
|||||||
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateEnv(yamlFile string) {
|
func generateEnv(yamlFile string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||||
config := loadConfig(yamlFile)
|
config := loadConfig(yamlFile)
|
||||||
|
|
||||||
// Check that all local directories exist
|
// Check that all local directories exist
|
||||||
@@ -24,7 +24,7 @@ func generateEnv(yamlFile string) {
|
|||||||
|
|
||||||
// Create combined roots.pem file
|
// Create combined roots.pem file
|
||||||
rootsPemPath := filepath.Join(logEntry.LocalDirectory, "roots.pem")
|
rootsPemPath := filepath.Join(logEntry.LocalDirectory, "roots.pem")
|
||||||
err := createCombinedRootsPemWithStatus(config.Roots, logEntry.ExtraRoots, rootsPemPath)
|
err := createCombinedRootsPemWithStatus(config.Roots, logEntry.ExtraRoots, rootsPemPath, wantDiff, allowWrite, useColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create %s: %v", rootsPemPath, err)
|
log.Fatalf("Failed to create %s: %v", rootsPemPath, err)
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ func generateEnv(yamlFile string) {
|
|||||||
// Build TESSERACT_ARGS string
|
// Build TESSERACT_ARGS string
|
||||||
args := []string{
|
args := []string{
|
||||||
fmt.Sprintf("--private_key=%s", logEntry.Secret),
|
fmt.Sprintf("--private_key=%s", logEntry.Secret),
|
||||||
fmt.Sprintf("--origin=%s.%s", logEntry.ShortName, logEntry.Domain),
|
fmt.Sprintf("--origin=%s", logEntry.Origin),
|
||||||
fmt.Sprintf("--storage_dir=%s", logEntry.LocalDirectory),
|
fmt.Sprintf("--storage_dir=%s", logEntry.LocalDirectory),
|
||||||
fmt.Sprintf("--roots_pem_file=%s", rootsPemPath),
|
fmt.Sprintf("--roots_pem_file=%s", rootsPemPath),
|
||||||
}
|
}
|
||||||
@@ -53,14 +53,14 @@ func generateEnv(yamlFile string) {
|
|||||||
tesseractArgs := strings.Join(args, " ")
|
tesseractArgs := strings.Join(args, " ")
|
||||||
envContent := fmt.Sprintf("TESSERACT_ARGS=\"%s\"\nOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318\n", tesseractArgs)
|
envContent := fmt.Sprintf("TESSERACT_ARGS=\"%s\"\nOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318\n", tesseractArgs)
|
||||||
|
|
||||||
err = writeFileWithStatus(envPath, []byte(envContent))
|
err = writeFileWithStatus(envPath, []byte(envContent), wantDiff, allowWrite, useColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to write %s: %v", envPath, err)
|
log.Fatalf("Failed to write %s: %v", envPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createCombinedRootsPemWithStatus(rootsFile, extraRootsFile, outputPath string) error {
|
func createCombinedRootsPemWithStatus(rootsFile, extraRootsFile, outputPath string, wantDiff bool, allowWrite bool, useColor bool) error {
|
||||||
// Read main roots file
|
// Read main roots file
|
||||||
var combinedContent []byte
|
var combinedContent []byte
|
||||||
if rootsFile != "" {
|
if rootsFile != "" {
|
||||||
@@ -80,5 +80,5 @@ func createCombinedRootsPemWithStatus(rootsFile, extraRootsFile, outputPath stri
|
|||||||
combinedContent = append(combinedContent, extraRootsData...)
|
combinedContent = append(combinedContent, extraRootsData...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeFileWithStatus(outputPath, combinedContent)
|
return writeFileWithStatus(outputPath, combinedContent, wantDiff, allowWrite, useColor)
|
||||||
}
|
}
|
||||||
|
@@ -74,7 +74,7 @@ const htmlTemplate = `<!DOCTYPE html>
|
|||||||
|
|
||||||
{{range .Logs}}
|
{{range .Logs}}
|
||||||
|
|
||||||
<h2>{{.ShortName}}.{{.Domain}}</h2>
|
<h2>{{.Origin}}</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Log ID: <code>{{.LogID}}</code><br>
|
Log ID: <code>{{.LogID}}</code><br>
|
||||||
@@ -110,7 +110,7 @@ type TemporalInterval struct {
|
|||||||
EndExclusive string `json:"end_exclusive"`
|
EndExclusive string `json:"end_exclusive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateHTML(yamlFile string) {
|
func generateHTML(yamlFile string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||||
config := loadConfig(yamlFile)
|
config := loadConfig(yamlFile)
|
||||||
|
|
||||||
// Check that all local directories exist
|
// Check that all local directories exist
|
||||||
@@ -145,14 +145,14 @@ func generateHTML(yamlFile string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write file with status
|
// Write file with status
|
||||||
err = writeFileWithStatus(indexPath, buf.Bytes())
|
err = writeFileWithStatus(indexPath, buf.Bytes(), wantDiff, allowWrite, useColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to write HTML to %s: %v", indexPath, err)
|
log.Fatalf("Failed to write HTML to %s: %v", indexPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate log.v3.json for this log
|
// Generate log.v3.json for this log
|
||||||
jsonPath := filepath.Join(logEntry.LocalDirectory, "log.v3.json")
|
jsonPath := filepath.Join(logEntry.LocalDirectory, "log.v3.json")
|
||||||
err = generateLogJSONWithStatus(logEntry, jsonPath)
|
err = generateLogJSONWithStatus(logEntry, jsonPath, wantDiff, allowWrite, useColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate %s: %v", jsonPath, err)
|
log.Fatalf("Failed to generate %s: %v", jsonPath, err)
|
||||||
}
|
}
|
||||||
@@ -209,9 +209,9 @@ func computeKeyInfo(logEntry *Log) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateLogJSONWithStatus(logEntry Log, outputPath string) error {
|
func generateLogJSONWithStatus(logEntry Log, outputPath string, wantDiff bool, allowWrite bool, useColor bool) error {
|
||||||
logJSON := LogV3JSON{
|
logJSON := LogV3JSON{
|
||||||
Description: fmt.Sprintf("%s.%s", logEntry.ShortName, logEntry.Domain),
|
Description: logEntry.Origin,
|
||||||
SubmissionURL: fmt.Sprintf("%s/", logEntry.SubmissionPrefix),
|
SubmissionURL: fmt.Sprintf("%s/", logEntry.SubmissionPrefix),
|
||||||
MonitoringURL: fmt.Sprintf("%s/", logEntry.MonitoringPrefix),
|
MonitoringURL: fmt.Sprintf("%s/", logEntry.MonitoringPrefix),
|
||||||
TemporalInterval: TemporalInterval{
|
TemporalInterval: TemporalInterval{
|
||||||
@@ -228,5 +228,5 @@ func generateLogJSONWithStatus(logEntry Log, outputPath string) error {
|
|||||||
return fmt.Errorf("failed to marshal JSON: %v", err)
|
return fmt.Errorf("failed to marshal JSON: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeFileWithStatus(outputPath, jsonData)
|
return writeFileWithStatus(outputPath, jsonData, wantDiff, allowWrite, useColor)
|
||||||
}
|
}
|
||||||
|
@@ -12,27 +12,32 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateKeys(yamlFile string) {
|
func generateKeys(yamlFile string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||||
|
if !allowWrite {
|
||||||
|
fmt.Printf("Key generation requires --write flag\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
config := loadConfig(yamlFile)
|
config := loadConfig(yamlFile)
|
||||||
|
|
||||||
// Generate keys for each log
|
// Generate keys for each log
|
||||||
for _, logEntry := range config.Logs {
|
for _, logEntry := range config.Logs {
|
||||||
// Check if key already exists
|
// Check if key already exists
|
||||||
if _, err := os.Stat(logEntry.Secret); err == nil {
|
if _, err := os.Stat(logEntry.Secret); err == nil {
|
||||||
fmt.Printf("Key already exists for log %s: %s (skipped)\n", logEntry.ShortName, logEntry.Secret)
|
fmt.Printf("Key already exists: %s (skipped)\n", logEntry.Secret)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate new prime256v1 key
|
// Generate new prime256v1 key
|
||||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate key for log %s: %v", logEntry.ShortName, err)
|
log.Fatalf("Failed to generate key for %s: %v", logEntry.ShortName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal private key to DER format
|
// Marshal private key to DER format
|
||||||
privKeyDER, err := x509.MarshalECPrivateKey(privKey)
|
privKeyDER, err := x509.MarshalECPrivateKey(privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to marshal private key for log %s: %v", logEntry.ShortName, err)
|
log.Fatalf("Failed to marshal private key for %s: %v", logEntry.ShortName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create PEM block
|
// Create PEM block
|
||||||
@@ -43,13 +48,13 @@ func generateKeys(yamlFile string) {
|
|||||||
|
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
if err := os.MkdirAll(filepath.Dir(logEntry.Secret), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(logEntry.Secret), 0755); err != nil {
|
||||||
log.Fatalf("Failed to create directory for %s for log %s: %v", logEntry.Secret, logEntry.ShortName, err)
|
log.Fatalf("Failed to create directory for %s: %v", logEntry.Secret, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write key to file
|
// Write key to file
|
||||||
err = os.WriteFile(logEntry.Secret, privKeyPEM, 0600)
|
err = os.WriteFile(logEntry.Secret, privKeyPEM, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to write key file %s for log %s: %v", logEntry.Secret, logEntry.ShortName, err)
|
log.Fatalf("Failed to write key file %s: %v", logEntry.Secret, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Generated %s\n", logEntry.Secret)
|
fmt.Printf("Generated %s\n", logEntry.Secret)
|
||||||
|
@@ -5,8 +5,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hexops/gotextdiff"
|
||||||
|
"github.com/hexops/gotextdiff/myers"
|
||||||
|
"github.com/hexops/gotextdiff/span"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +23,6 @@ type Config struct {
|
|||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
ShortName string `yaml:"shortname"`
|
ShortName string `yaml:"shortname"`
|
||||||
Domain string `yaml:"domain"`
|
|
||||||
Inception string `yaml:"inception"`
|
Inception string `yaml:"inception"`
|
||||||
Period int `yaml:"period"`
|
Period int `yaml:"period"`
|
||||||
PoolSize int `yaml:"poolsize"`
|
PoolSize int `yaml:"poolsize"`
|
||||||
@@ -38,10 +41,14 @@ type Log struct {
|
|||||||
PublicKeyPEM string
|
PublicKeyPEM string
|
||||||
PublicKeyDERB64 string
|
PublicKeyDERB64 string
|
||||||
PublicKeyBase64 string
|
PublicKeyBase64 string
|
||||||
|
Origin string
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configFile := flag.String("c", "./tesseract-staging.yaml", "Path to the YAML configuration file")
|
configFile := flag.String("c", "./tesseract-staging.yaml", "Path to the YAML configuration file")
|
||||||
|
wantDiff := flag.Bool("diff", false, "Show unified diff of changes")
|
||||||
|
allowWrite := flag.Bool("write", false, "Allow writing files (required for actual file modifications)")
|
||||||
|
noColor := flag.Bool("no-color", false, "Disable colored diff output")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
@@ -52,15 +59,15 @@ func main() {
|
|||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "gen-html":
|
case "gen-html":
|
||||||
generateHTML(*configFile)
|
generateHTML(*configFile, *wantDiff, *allowWrite, !*noColor)
|
||||||
case "gen-env":
|
case "gen-env":
|
||||||
generateEnv(*configFile)
|
generateEnv(*configFile, *wantDiff, *allowWrite, !*noColor)
|
||||||
case "gen-key":
|
case "gen-key":
|
||||||
generateKeys(*configFile)
|
generateKeys(*configFile, *wantDiff, *allowWrite, !*noColor)
|
||||||
case "gen-nginx":
|
case "gen-nginx":
|
||||||
generateNginx(*configFile)
|
generateNginx(*configFile, *wantDiff, *allowWrite, !*noColor)
|
||||||
case "gen-roots":
|
case "gen-roots":
|
||||||
generateRoots(args[1:])
|
generateRoots(args[1:], *wantDiff, *allowWrite, !*noColor)
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", args[0])
|
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", args[0])
|
||||||
showHelp()
|
showHelp()
|
||||||
@@ -85,91 +92,116 @@ func loadConfig(yamlFile string) Config {
|
|||||||
config.Listen = []string{":8080"}
|
config.Listen = []string{":8080"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkpoints & Roots are not used in-code, not checking for being set/valid
|
// Set defaults for log entries
|
||||||
|
|
||||||
// Ensure there are logs configured
|
|
||||||
if len(config.Logs) == 0 {
|
|
||||||
log.Fatalf("Parsed YAML did not include any 'logs'")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set defaults for log entries and check for empty/missing values
|
|
||||||
for i := range config.Logs {
|
for i := range config.Logs {
|
||||||
// Checks are in order of fields of the Log struct
|
if config.Logs[i].PoolSize == 0 {
|
||||||
|
config.Logs[i].PoolSize = 750
|
||||||
if config.Logs[i].ShortName == "" {
|
|
||||||
log.Fatalf("Log %d is missing a ShortName", i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Logs[i].Domain == "" {
|
|
||||||
log.Fatalf("Log %d (%s) is missing a value for Domain", i, config.Logs[i].ShortName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inception is not used in-code
|
|
||||||
|
|
||||||
if config.Logs[i].Period == 0 {
|
if config.Logs[i].Period == 0 {
|
||||||
config.Logs[i].Period = 200
|
config.Logs[i].Period = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Logs[i].PoolSize == 0 {
|
// Extract hostname from SubmissionPrefix to set Origin
|
||||||
config.Logs[i].PoolSize = 750
|
if config.Logs[i].SubmissionPrefix != "" {
|
||||||
}
|
hostname, err := extractHostname(config.Logs[i].SubmissionPrefix)
|
||||||
|
if err != nil {
|
||||||
if config.Logs[i].SubmissionPrefix == "" {
|
log.Fatalf("Failed to parse SubmissionPrefix URL for %s: %v", config.Logs[i].ShortName, err)
|
||||||
log.Fatalf("Log %d (%s) is missing a value for SubmissionPrefix", i, config.Logs[i].ShortName)
|
}
|
||||||
}
|
config.Logs[i].Origin = hostname
|
||||||
|
|
||||||
if config.Logs[i].MonitoringPrefix == "" {
|
|
||||||
log.Fatalf("Log %d (%s) is missing a value for MonitoringPrefix", i, config.Logs[i].ShortName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CCadbRoots is not used in-code
|
|
||||||
// ExtraRoots is optional
|
|
||||||
|
|
||||||
if config.Logs[i].Secret == "" {
|
|
||||||
log.Fatalf("Log %d (%s) is missing a value for Secret", i, config.Logs[i].ShortName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache is not used in-code
|
|
||||||
|
|
||||||
if config.Logs[i].LocalDirectory == "" {
|
|
||||||
log.Fatalf("Log %d (%s) is missing a value for LocalDirectory", i, config.Logs[i].ShortName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen, NotAfterStart and NotAfterLimit are optional
|
|
||||||
|
|
||||||
// These fields are exported due to HTML templates
|
|
||||||
// but should not be provided/filled by the user
|
|
||||||
if config.Logs[i].LogID != "" {
|
|
||||||
log.Fatalf("Log %d (%s) has field LogID should not be configured (%s)", i, config.Logs[i].ShortName, config.Logs[i].LogID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Logs[i].PublicKeyPEM != "" {
|
|
||||||
log.Fatalf("Log %d (%s) has field PublicKeyPEM should not be configured (%s)", i, config.Logs[i].ShortName, config.Logs[i].PublicKeyPEM)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Logs[i].PublicKeyDERB64 != "" {
|
|
||||||
log.Fatalf("Log %d (%s) has field PublicKeyDERB64 should not be configured (%s)", i, config.Logs[i].ShortName, config.Logs[i].PublicKeyDERB64)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Logs[i].PublicKeyBase64 != "" {
|
|
||||||
log.Fatalf("Log %d (%s) has field PublicKeyBase64 should not be configured (%s)", i, config.Logs[i].ShortName, config.Logs[i].PublicKeyBase64)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFileWithStatus(filename string, content []byte) error {
|
// ANSI color codes
|
||||||
|
const (
|
||||||
|
colorReset = "\033[0m"
|
||||||
|
colorRed = "\033[31m"
|
||||||
|
colorGreen = "\033[32m"
|
||||||
|
colorYellow = "\033[33m"
|
||||||
|
colorCyan = "\033[36m"
|
||||||
|
)
|
||||||
|
|
||||||
|
// colorizeUnifiedDiff adds ANSI color codes to unified diff output
|
||||||
|
func colorizeUnifiedDiff(diff string) string {
|
||||||
|
lines := strings.Split(diff, "\n")
|
||||||
|
var colorizedLines []string
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, "---"):
|
||||||
|
// File deletion header in cyan
|
||||||
|
colorizedLines = append(colorizedLines, colorCyan+line+colorReset)
|
||||||
|
case strings.HasPrefix(line, "+++"):
|
||||||
|
// File addition header in cyan
|
||||||
|
colorizedLines = append(colorizedLines, colorCyan+line+colorReset)
|
||||||
|
case strings.HasPrefix(line, "@@"):
|
||||||
|
// Hunk header in yellow
|
||||||
|
colorizedLines = append(colorizedLines, colorYellow+line+colorReset)
|
||||||
|
case strings.HasPrefix(line, "-"):
|
||||||
|
// Deleted lines in red
|
||||||
|
colorizedLines = append(colorizedLines, colorRed+line+colorReset)
|
||||||
|
case strings.HasPrefix(line, "+"):
|
||||||
|
// Added lines in green
|
||||||
|
colorizedLines = append(colorizedLines, colorGreen+line+colorReset)
|
||||||
|
default:
|
||||||
|
// Context lines unchanged
|
||||||
|
colorizedLines = append(colorizedLines, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(colorizedLines, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFileWithStatus(filename string, content []byte, wantDiff bool, allowWrite bool, useColor bool) error {
|
||||||
existingContent, err := os.ReadFile(filename)
|
existingContent, err := os.ReadFile(filename)
|
||||||
if os.IsNotExist(err) {
|
isNew := os.IsNotExist(err)
|
||||||
fmt.Printf("Creating %s\n", filename)
|
isUnchanged := false
|
||||||
|
|
||||||
|
if isNew {
|
||||||
|
if allowWrite {
|
||||||
|
fmt.Printf("Creating %s\n", filename)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Would create %s\n", filename)
|
||||||
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("failed to read existing file %s: %v", filename, err)
|
return fmt.Errorf("failed to read existing file %s: %v", filename, err)
|
||||||
} else if string(existingContent) == string(content) {
|
} else if string(existingContent) == string(content) {
|
||||||
fmt.Printf("Unchanged %s\n", filename)
|
fmt.Printf("Unchanged %s\n", filename)
|
||||||
return nil
|
isUnchanged = true
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Updating %s\n", filename)
|
if allowWrite {
|
||||||
|
fmt.Printf("Updating %s\n", filename)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Would update %s\n", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if wantDiff && !isUnchanged {
|
||||||
|
if isNew {
|
||||||
|
// For new files, show the entire content as added
|
||||||
|
edits := myers.ComputeEdits(span.URIFromPath(filename), "", string(content))
|
||||||
|
diff := fmt.Sprint(gotextdiff.ToUnified("/dev/null", filename, "", edits))
|
||||||
|
if useColor {
|
||||||
|
fmt.Print(colorizeUnifiedDiff(diff))
|
||||||
|
} else {
|
||||||
|
fmt.Print(diff)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For existing files, show the diff
|
||||||
|
edits := myers.ComputeEdits(span.URIFromPath(filename), string(existingContent), string(content))
|
||||||
|
diff := fmt.Sprint(gotextdiff.ToUnified(filename, filename+".new", string(existingContent), edits))
|
||||||
|
if useColor {
|
||||||
|
fmt.Print(colorizeUnifiedDiff(diff))
|
||||||
|
} else {
|
||||||
|
fmt.Print(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isUnchanged || !allowWrite {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(filename, content, 0644)
|
err = os.WriteFile(filename, content, 0644)
|
||||||
@@ -181,8 +213,17 @@ func writeFileWithStatus(filename string, content []byte) error {
|
|||||||
|
|
||||||
func showHelp() {
|
func showHelp() {
|
||||||
fmt.Printf("Usage: %s [options] <command>\n\n", os.Args[0])
|
fmt.Printf("Usage: %s [options] <command>\n\n", os.Args[0])
|
||||||
|
fmt.Printf("Note: Flags must come before the command name.\n\n")
|
||||||
fmt.Printf("Options:\n")
|
fmt.Printf("Options:\n")
|
||||||
fmt.Printf(" -c <file> Path to YAML configuration file (default: ./tesseract-staging.yaml)\n\n")
|
fmt.Printf(" -c <file> Path to YAML configuration file (default: ./tesseract-staging.yaml)\n")
|
||||||
|
fmt.Printf(" --diff Show unified diff of changes without writing files\n")
|
||||||
|
fmt.Printf(" --write Allow writing files (required for actual file modifications)\n")
|
||||||
|
fmt.Printf(" --no-color Disable colored diff output\n\n")
|
||||||
|
fmt.Printf("Examples:\n")
|
||||||
|
fmt.Printf(" %s --diff gen-html # Show colored diffs without writing\n", os.Args[0])
|
||||||
|
fmt.Printf(" %s --diff --no-color gen-html # Show plain diffs without writing\n", os.Args[0])
|
||||||
|
fmt.Printf(" %s --write gen-html # Write files\n", os.Args[0])
|
||||||
|
fmt.Printf(" %s --diff --write gen-html # Show colored diffs and write files\n\n", os.Args[0])
|
||||||
fmt.Printf("Commands:\n")
|
fmt.Printf("Commands:\n")
|
||||||
fmt.Printf(" gen-html Generate index.html and log.v3.json files in each log's localdirectory.\n")
|
fmt.Printf(" gen-html Generate index.html and log.v3.json files in each log's localdirectory.\n")
|
||||||
fmt.Printf(" Creates HTML pages with log information and CT log metadata JSON.\n")
|
fmt.Printf(" Creates HTML pages with log information and CT log metadata JSON.\n")
|
||||||
|
@@ -76,7 +76,7 @@ type NginxTemplateData struct {
|
|||||||
ListenPort string
|
ListenPort string
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateNginx(yamlFile string) {
|
func generateNginx(yamlFile string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||||
config := loadConfig(yamlFile)
|
config := loadConfig(yamlFile)
|
||||||
|
|
||||||
// Extract port from first listen address
|
// Extract port from first listen address
|
||||||
@@ -123,7 +123,7 @@ func generateNginx(yamlFile string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write file with status
|
// Write file with status
|
||||||
err = writeFileWithStatus(outputPath, buf.Bytes())
|
err = writeFileWithStatus(outputPath, buf.Bytes(), wantDiff, allowWrite, useColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write nginx config file %s: %v\n", outputPath, err)
|
fmt.Fprintf(os.Stderr, "Failed to write nginx config file %s: %v\n", outputPath, err)
|
||||||
continue
|
continue
|
||||||
@@ -151,14 +151,10 @@ func extractPort(listenAddr string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractHostname(urlStr string) (string, error) {
|
func extractHostname(urlStr string) (string, error) {
|
||||||
if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") {
|
|
||||||
urlStr = "https://" + urlStr
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedURL, err := url.Parse(urlStr)
|
parsedURL, err := url.Parse(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedURL.Hostname(), nil
|
return parsedURL.Host, nil
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@ type CTLogRootsResponse struct {
|
|||||||
Certificates []string `json:"certificates"`
|
Certificates []string `json:"certificates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRoots(args []string) {
|
func generateRoots(args []string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||||
sourceURL := "https://rennet2027h2.log.ct.ipng.ch/"
|
sourceURL := "https://rennet2027h2.log.ct.ipng.ch/"
|
||||||
outputFile := "roots.pem"
|
outputFile := "roots.pem"
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ func generateRoots(args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write all certificates to file with status
|
// Write all certificates to file with status
|
||||||
err = writeFileWithStatus(outputFile, pemBuffer.Bytes())
|
err = writeFileWithStatus(outputFile, pemBuffer.Bytes(), wantDiff, allowWrite, useColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to write output file %s: %v", outputFile, err)
|
log.Fatalf("Failed to write output file %s: %v", outputFile, err)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user