github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/build/tools/templates.go (about) 1 package build 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "reflect" 12 "runtime" 13 "text/template" 14 "time" 15 ) 16 17 // Backend contains backend specific fields 18 type Backend struct { 19 PackageName string `json:"package_name"` 20 PackageRevision string `json:"package_revision"` 21 SystemUser string `json:"system_user"` 22 Version string `json:"version"` 23 BinaryURL string `json:"binary_url"` 24 VerificationType string `json:"verification_type"` 25 VerificationSource string `json:"verification_source"` 26 ExtractCommand string `json:"extract_command"` 27 ExcludeFiles []string `json:"exclude_files"` 28 ExecCommandTemplate string `json:"exec_command_template"` 29 ExecScript string `json:"exec_script"` 30 LogrotateFilesTemplate string `json:"logrotate_files_template"` 31 PostinstScriptTemplate string `json:"postinst_script_template"` 32 ServiceType string `json:"service_type"` 33 ServiceAdditionalParamsTemplate string `json:"service_additional_params_template"` 34 ProtectMemory bool `json:"protect_memory"` 35 Mainnet bool `json:"mainnet"` 36 ServerConfigFile string `json:"server_config_file"` 37 ClientConfigFile string `json:"client_config_file"` 38 AdditionalParams interface{} `json:"additional_params,omitempty"` 39 Platforms map[string]Backend `json:"platforms,omitempty"` 40 } 41 42 // Config contains the structure of the config 43 type Config struct { 44 Coin struct { 45 Name string `json:"name"` 46 Shortcut string `json:"shortcut"` 47 Label string `json:"label"` 48 Alias string `json:"alias"` 49 } `json:"coin"` 50 Ports struct { 51 BackendRPC int `json:"backend_rpc"` 52 BackendMessageQueue int `json:"backend_message_queue"` 53 BackendP2P int `json:"backend_p2p"` 54 BackendHttp int `json:"backend_http"` 55 BackendAuthRpc int `json:"backend_authrpc"` 56 BlockbookInternal int `json:"blockbook_internal"` 57 BlockbookPublic int `json:"blockbook_public"` 58 } `json:"ports"` 59 IPC struct { 60 RPCURLTemplate string `json:"rpc_url_template"` 61 RPCUser string `json:"rpc_user"` 62 RPCPass string `json:"rpc_pass"` 63 RPCTimeout int `json:"rpc_timeout"` 64 MessageQueueBindingTemplate string `json:"message_queue_binding_template"` 65 } `json:"ipc"` 66 Backend Backend `json:"backend"` 67 Blockbook struct { 68 PackageName string `json:"package_name"` 69 SystemUser string `json:"system_user"` 70 InternalBindingTemplate string `json:"internal_binding_template"` 71 PublicBindingTemplate string `json:"public_binding_template"` 72 ExplorerURL string `json:"explorer_url"` 73 AdditionalParams string `json:"additional_params"` 74 BlockChain struct { 75 Parse bool `json:"parse,omitempty"` 76 Subversion string `json:"subversion,omitempty"` 77 AddressFormat string `json:"address_format,omitempty"` 78 MempoolWorkers int `json:"mempool_workers"` 79 MempoolSubWorkers int `json:"mempool_sub_workers"` 80 BlockAddressesToKeep int `json:"block_addresses_to_keep"` 81 XPubMagic uint32 `json:"xpub_magic,omitempty"` 82 XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"` 83 XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"` 84 Slip44 uint32 `json:"slip44,omitempty"` 85 86 AdditionalParams map[string]json.RawMessage `json:"additional_params"` 87 } `json:"block_chain"` 88 } `json:"blockbook"` 89 Meta struct { 90 BuildDatetime string `json:"-"` // generated field 91 PackageMaintainer string `json:"package_maintainer"` 92 PackageMaintainerEmail string `json:"package_maintainer_email"` 93 } `json:"meta"` 94 Env struct { 95 Version string `json:"version"` 96 BackendInstallPath string `json:"backend_install_path"` 97 BackendDataPath string `json:"backend_data_path"` 98 BlockbookInstallPath string `json:"blockbook_install_path"` 99 BlockbookDataPath string `json:"blockbook_data_path"` 100 Architecture string `json:"architecture"` 101 } `json:"-"` 102 } 103 104 func jsonToString(msg json.RawMessage) (string, error) { 105 d, err := msg.MarshalJSON() 106 if err != nil { 107 return "", err 108 } 109 return string(d), nil 110 } 111 112 func generateRPCAuth(user, pass string) (string, error) { 113 cmd := exec.Command("/usr/bin/env", "bash", "-c", "build/scripts/rpcauth.py \"$0\" \"$1\" | sed -n -e 2p", user, pass) 114 var out bytes.Buffer 115 cmd.Stdout = &out 116 err := cmd.Run() 117 if err != nil { 118 return "", err 119 } 120 return out.String(), nil 121 } 122 123 // ParseTemplate parses the template 124 func (c *Config) ParseTemplate() *template.Template { 125 templates := map[string]string{ 126 "IPC.RPCURLTemplate": c.IPC.RPCURLTemplate, 127 "IPC.MessageQueueBindingTemplate": c.IPC.MessageQueueBindingTemplate, 128 "Backend.ExecCommandTemplate": c.Backend.ExecCommandTemplate, 129 "Backend.LogrotateFilesTemplate": c.Backend.LogrotateFilesTemplate, 130 "Backend.PostinstScriptTemplate": c.Backend.PostinstScriptTemplate, 131 "Backend.ServiceAdditionalParamsTemplate": c.Backend.ServiceAdditionalParamsTemplate, 132 "Blockbook.InternalBindingTemplate": c.Blockbook.InternalBindingTemplate, 133 "Blockbook.PublicBindingTemplate": c.Blockbook.PublicBindingTemplate, 134 } 135 136 funcMap := template.FuncMap{ 137 "jsonToString": jsonToString, 138 "generateRPCAuth": generateRPCAuth, 139 } 140 141 t := template.New("").Funcs(funcMap) 142 143 for name, def := range templates { 144 t = template.Must(t.Parse(fmt.Sprintf(`{{define "%s"}}%s{{end}}`, name, def))) 145 } 146 147 return t 148 } 149 150 func copyNonZeroBackendFields(toValue *Backend, fromValue *Backend) { 151 from := reflect.ValueOf(*fromValue) 152 to := reflect.ValueOf(toValue).Elem() 153 for i := 0; i < from.NumField(); i++ { 154 if from.Field(i).IsValid() && !from.Field(i).IsZero() { 155 to.Field(i).Set(from.Field(i)) 156 } 157 } 158 } 159 160 // LoadConfig loads the config files 161 func LoadConfig(configsDir, coin string) (*Config, error) { 162 config := new(Config) 163 164 f, err := os.Open(filepath.Join(configsDir, "coins", coin+".json")) 165 if err != nil { 166 return nil, err 167 } 168 d := json.NewDecoder(f) 169 err = d.Decode(config) 170 if err != nil { 171 return nil, err 172 } 173 174 f, err = os.Open(filepath.Join(configsDir, "environ.json")) 175 if err != nil { 176 return nil, err 177 } 178 d = json.NewDecoder(f) 179 err = d.Decode(&config.Env) 180 if err != nil { 181 return nil, err 182 } 183 184 config.Meta.BuildDatetime = time.Now().Format("Mon, 02 Jan 2006 15:04:05 -0700") 185 config.Env.Architecture = runtime.GOARCH 186 187 if !isEmpty(config, "backend") { 188 // set platform specific fields to config 189 platform, found := config.Backend.Platforms[runtime.GOARCH] 190 if found { 191 copyNonZeroBackendFields(&config.Backend, &platform) 192 } 193 194 switch config.Backend.ServiceType { 195 case "forking": 196 case "simple": 197 default: 198 return nil, fmt.Errorf("Invalid service type: %s", config.Backend.ServiceType) 199 } 200 201 switch config.Backend.VerificationType { 202 case "": 203 case "gpg": 204 case "sha256": 205 case "gpg-sha256": 206 default: 207 return nil, fmt.Errorf("Invalid verification type: %s", config.Backend.VerificationType) 208 } 209 } 210 211 return config, nil 212 } 213 214 func isEmpty(config *Config, target string) bool { 215 switch target { 216 case "backend": 217 return config.Backend.PackageName == "" 218 case "blockbook": 219 return config.Blockbook.PackageName == "" 220 default: 221 panic("Invalid target name: " + target) 222 } 223 } 224 225 // GeneratePackageDefinitions generate the package definitions from the config 226 func GeneratePackageDefinitions(config *Config, templateDir, outputDir string) error { 227 templ := config.ParseTemplate() 228 229 err := makeOutputDir(outputDir) 230 if err != nil { 231 return err 232 } 233 234 for _, subdir := range []string{"backend", "blockbook"} { 235 if isEmpty(config, subdir) { 236 continue 237 } 238 239 root := filepath.Join(templateDir, subdir) 240 241 err = os.Mkdir(filepath.Join(outputDir, subdir), 0755) 242 if err != nil { 243 return err 244 } 245 246 err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 247 if err != nil { 248 return fmt.Errorf("%s: %s", path, err) 249 } 250 251 if path == root { 252 return nil 253 } 254 if filepath.Base(path)[0] == '.' { 255 return nil 256 } 257 258 subpath := path[len(root)-len(subdir):] 259 260 if info.IsDir() { 261 err = os.Mkdir(filepath.Join(outputDir, subpath), info.Mode()) 262 if err != nil { 263 return fmt.Errorf("%s: %s", path, err) 264 } 265 return nil 266 } 267 268 t := template.Must(templ.Clone()) 269 t = template.Must(t.ParseFiles(path)) 270 271 err = writeTemplate(filepath.Join(outputDir, subpath), info, t, config) 272 if err != nil { 273 return fmt.Errorf("%s: %s", path, err) 274 } 275 276 return nil 277 }) 278 if err != nil { 279 return err 280 } 281 } 282 283 if !isEmpty(config, "backend") { 284 if err := writeBackendServerConfigFile(config, outputDir); err != nil { 285 return err 286 } 287 288 if err := writeBackendClientConfigFile(config, outputDir); err != nil { 289 return err 290 } 291 292 if err := writeBackendExecScript(config, outputDir); err != nil { 293 return err 294 } 295 } 296 297 return nil 298 } 299 300 func makeOutputDir(path string) error { 301 err := os.RemoveAll(path) 302 if err == nil { 303 err = os.Mkdir(path, 0755) 304 } 305 return err 306 } 307 308 func writeTemplate(path string, info os.FileInfo, templ *template.Template, config *Config) error { 309 f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) 310 if err != nil { 311 return err 312 } 313 defer f.Close() 314 315 return templ.ExecuteTemplate(f, "main", config) 316 } 317 318 func writeBackendServerConfigFile(config *Config, outputDir string) error { 319 out, err := os.OpenFile(filepath.Join(outputDir, "backend/server.conf"), os.O_CREATE|os.O_WRONLY, 0644) 320 if err != nil { 321 return err 322 } 323 defer out.Close() 324 325 if config.Backend.ServerConfigFile == "" { 326 return nil 327 } else { 328 in, err := os.Open(filepath.Join(outputDir, "backend/config", config.Backend.ServerConfigFile)) 329 if err != nil { 330 return err 331 } 332 defer in.Close() 333 334 _, err = io.Copy(out, in) 335 if err != nil { 336 return err 337 } 338 } 339 340 return nil 341 } 342 343 func writeBackendClientConfigFile(config *Config, outputDir string) error { 344 out, err := os.OpenFile(filepath.Join(outputDir, "backend/client.conf"), os.O_CREATE|os.O_WRONLY, 0644) 345 if err != nil { 346 return err 347 } 348 defer out.Close() 349 350 if config.Backend.ClientConfigFile == "" { 351 return nil 352 } 353 in, err := os.Open(filepath.Join(outputDir, "backend/config", config.Backend.ClientConfigFile)) 354 if err != nil { 355 return err 356 } 357 defer in.Close() 358 359 _, err = io.Copy(out, in) 360 return err 361 } 362 363 func writeBackendExecScript(config *Config, outputDir string) error { 364 if config.Backend.ExecScript == "" { 365 return nil 366 } 367 368 out, err := os.OpenFile(filepath.Join(outputDir, "backend/exec.sh"), os.O_CREATE|os.O_WRONLY, 0777) 369 if err != nil { 370 return err 371 } 372 defer out.Close() 373 374 in, err := os.Open(filepath.Join(outputDir, "backend/scripts", config.Backend.ExecScript)) 375 if err != nil { 376 return err 377 } 378 defer in.Close() 379 380 _, err = io.Copy(out, in) 381 return err 382 }