blockbook/build/tools/templates.go

336 lines
9.4 KiB
Go

package build
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"text/template"
"time"
)
type Config struct {
Coin struct {
Name string `json:"name"`
Shortcut string `json:"shortcut"`
Label string `json:"label"`
Alias string `json:"alias"`
} `json:"coin"`
Ports struct {
BackendRPC int `json:"backend_rpc"`
BackendMessageQueue int `json:"backend_message_queue"`
BlockbookInternal int `json:"blockbook_internal"`
BlockbookPublic int `json:"blockbook_public"`
} `json:"ports"`
IPC struct {
RPCURLTemplate string `json:"rpc_url_template"`
RPCUser string `json:"rpc_user"`
RPCPass string `json:"rpc_pass"`
RPCTimeout int `json:"rpc_timeout"`
MessageQueueBindingTemplate string `json:"message_queue_binding_template"`
} `json:"ipc"`
Backend struct {
PackageName string `json:"package_name"`
PackageRevision string `json:"package_revision"`
SystemUser string `json:"system_user"`
Version string `json:"version"`
BinaryURL string `json:"binary_url"`
VerificationType string `json:"verification_type"`
VerificationSource string `json:"verification_source"`
ExtractCommand string `json:"extract_command"`
ExcludeFiles []string `json:"exclude_files"`
ExecCommandTemplate string `json:"exec_command_template"`
LogrotateFilesTemplate string `json:"logrotate_files_template"`
PostinstScriptTemplate string `json:"postinst_script_template"`
ServiceType string `json:"service_type"`
ServiceAdditionalParamsTemplate string `json:"service_additional_params_template"`
ProtectMemory bool `json:"protect_memory"`
Mainnet bool `json:"mainnet"`
ServerConfigFile string `json:"server_config_file"`
ClientConfigFile string `json:"client_config_file"`
AdditionalParams interface{} `json:"additional_params,omitempty"`
} `json:"backend"`
Blockbook struct {
PackageName string `json:"package_name"`
SystemUser string `json:"system_user"`
InternalBindingTemplate string `json:"internal_binding_template"`
PublicBindingTemplate string `json:"public_binding_template"`
ExplorerURL string `json:"explorer_url"`
AdditionalParams string `json:"additional_params"`
BlockChain struct {
Parse bool `json:"parse,omitempty"`
Subversion string `json:"subversion,omitempty"`
AddressFormat string `json:"address_format,omitempty"`
MempoolWorkers int `json:"mempool_workers"`
MempoolSubWorkers int `json:"mempool_sub_workers"`
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
XPubMagic uint32 `json:"xpub_magic,omitempty"`
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
Slip44 uint32 `json:"slip44,omitempty"`
AdditionalParams map[string]json.RawMessage `json:"additional_params"`
} `json:"block_chain"`
} `json:"blockbook"`
Meta struct {
BuildDatetime string `json:"-"` // generated field
PackageMaintainer string `json:"package_maintainer"`
PackageMaintainerEmail string `json:"package_maintainer_email"`
} `json:"meta"`
Env struct {
Version string `json:"version"`
BackendInstallPath string `json:"backend_install_path"`
BackendDataPath string `json:"backend_data_path"`
BlockbookInstallPath string `json:"blockbook_install_path"`
BlockbookDataPath string `json:"blockbook_data_path"`
} `json:"-"`
}
func jsonToString(msg json.RawMessage) (string, error) {
d, err := msg.MarshalJSON()
if err != nil {
return "", err
}
return string(d), nil
}
func generateRPCAuth(user, pass string) (string, error) {
cmd := exec.Command("/usr/bin/env", "bash", "-c", "build/scripts/rpcauth.py \"$0\" \"$1\" | sed -n -e 2p", user, pass)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
return out.String(), nil
}
func (c *Config) ParseTemplate() *template.Template {
templates := map[string]string{
"IPC.RPCURLTemplate": c.IPC.RPCURLTemplate,
"IPC.MessageQueueBindingTemplate": c.IPC.MessageQueueBindingTemplate,
"Backend.ExecCommandTemplate": c.Backend.ExecCommandTemplate,
"Backend.LogrotateFilesTemplate": c.Backend.LogrotateFilesTemplate,
"Backend.PostinstScriptTemplate": c.Backend.PostinstScriptTemplate,
"Backend.ServiceAdditionalParamsTemplate": c.Backend.ServiceAdditionalParamsTemplate,
"Blockbook.InternalBindingTemplate": c.Blockbook.InternalBindingTemplate,
"Blockbook.PublicBindingTemplate": c.Blockbook.PublicBindingTemplate,
}
funcMap := template.FuncMap{
"jsonToString": jsonToString,
"generateRPCAuth": generateRPCAuth,
}
t := template.New("").Funcs(funcMap)
for name, def := range templates {
t = template.Must(t.Parse(fmt.Sprintf(`{{define "%s"}}%s{{end}}`, name, def)))
}
return t
}
func LoadConfig(configsDir, coin string) (*Config, error) {
config := new(Config)
f, err := os.Open(filepath.Join(configsDir, "coins", coin+".json"))
if err != nil {
return nil, err
}
d := json.NewDecoder(f)
err = d.Decode(config)
if err != nil {
return nil, err
}
f, err = os.Open(filepath.Join(configsDir, "environ.json"))
if err != nil {
return nil, err
}
d = json.NewDecoder(f)
err = d.Decode(&config.Env)
if err != nil {
return nil, err
}
config.Meta.BuildDatetime = time.Now().Format("Mon, 02 Jan 2006 15:04:05 -0700")
if !isEmpty(config, "backend") {
switch config.Backend.ServiceType {
case "forking":
case "simple":
default:
return nil, fmt.Errorf("Invalid service type: %s", config.Backend.ServiceType)
}
switch config.Backend.VerificationType {
case "":
case "gpg":
case "sha256":
case "gpg-sha256":
default:
return nil, fmt.Errorf("Invalid verification type: %s", config.Backend.VerificationType)
}
}
return config, nil
}
func isEmpty(config *Config, target string) bool {
switch target {
case "backend":
return config.Backend.PackageName == ""
case "blockbook":
return config.Blockbook.PackageName == ""
default:
panic("Invalid target name: " + target)
}
}
func GeneratePackageDefinitions(config *Config, templateDir, outputDir string) error {
templ := config.ParseTemplate()
err := makeOutputDir(outputDir)
if err != nil {
return err
}
for _, subdir := range []string{"backend", "blockbook"} {
if isEmpty(config, subdir) {
continue
}
root := filepath.Join(templateDir, subdir)
err = os.Mkdir(filepath.Join(outputDir, subdir), 0755)
if err != nil {
return err
}
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("%s: %s", path, err)
}
if path == root {
return nil
}
if filepath.Base(path)[0] == '.' {
return nil
}
subpath := path[len(root)-len(subdir):]
if info.IsDir() {
err = os.Mkdir(filepath.Join(outputDir, subpath), info.Mode())
if err != nil {
return fmt.Errorf("%s: %s", path, err)
}
return nil
}
t := template.Must(templ.Clone())
t = template.Must(t.ParseFiles(path))
err = writeTemplate(filepath.Join(outputDir, subpath), info, t, config)
if err != nil {
return fmt.Errorf("%s: %s", path, err)
}
return nil
})
if err != nil {
return err
}
}
if !isEmpty(config, "backend") {
err = writeBackendServerConfigFile(config, outputDir)
if err == nil {
err = writeBackendClientConfigFile(config, outputDir)
}
if err != nil {
return err
}
}
return nil
}
func makeOutputDir(path string) error {
err := os.RemoveAll(path)
if err == nil {
err = os.Mkdir(path, 0755)
}
return err
}
func writeTemplate(path string, info os.FileInfo, templ *template.Template, config *Config) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode())
if err != nil {
return err
}
defer f.Close()
err = templ.ExecuteTemplate(f, "main", config)
if err != nil {
return err
}
return nil
}
func writeBackendServerConfigFile(config *Config, outputDir string) error {
out, err := os.OpenFile(filepath.Join(outputDir, "backend/server.conf"), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer out.Close()
if config.Backend.ServerConfigFile == "" {
return nil
} else {
in, err := os.Open(filepath.Join(outputDir, "backend/config", config.Backend.ServerConfigFile))
if err != nil {
return err
}
defer in.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
}
return nil
}
func writeBackendClientConfigFile(config *Config, outputDir string) error {
out, err := os.OpenFile(filepath.Join(outputDir, "backend/client.conf"), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer out.Close()
if config.Backend.ClientConfigFile == "" {
return nil
} else {
in, err := os.Open(filepath.Join(outputDir, "backend/config", config.Backend.ClientConfigFile))
if err != nil {
return err
}
defer in.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
}
return nil
}