2021-07-06 11:27:54 +00:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"text/template"
|
|
|
|
|
2023-03-07 13:38:26 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
|
2021-07-09 09:53:10 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/cli/input"
|
2021-07-06 11:27:54 +00:00
|
|
|
"github.com/spf13/cobra"
|
2021-07-09 09:53:10 +00:00
|
|
|
"github.com/spf13/viper"
|
2021-07-06 11:27:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type configTemplate struct {
|
2022-05-05 18:28:11 +00:00
|
|
|
Endpoint string
|
|
|
|
AlphabetDir string
|
|
|
|
MaxObjectSize int
|
|
|
|
EpochDuration int
|
|
|
|
CandidateFee int
|
|
|
|
ContainerFee int
|
|
|
|
ContainerAliasFee int
|
2024-04-08 09:27:30 +00:00
|
|
|
MaxECDataCount int
|
|
|
|
MaxECParityCount int
|
2022-05-05 18:28:11 +00:00
|
|
|
WithdrawFee int
|
|
|
|
Glagolitics []string
|
|
|
|
HomomorphicHashDisabled bool
|
2021-07-06 11:27:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const configTxtTemplate = `rpc-endpoint: {{ .Endpoint}}
|
|
|
|
alphabet-wallets: {{ .AlphabetDir}}
|
|
|
|
network:
|
|
|
|
max_object_size: {{ .MaxObjectSize}}
|
|
|
|
epoch_duration: {{ .EpochDuration}}
|
2024-04-08 09:27:30 +00:00
|
|
|
max_ec_data_count: {{ .MaxECDataCount}}
|
|
|
|
max_ec_parity_count: {{ .MaxECParityCount}}
|
2022-05-05 18:28:11 +00:00
|
|
|
homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}}
|
2021-08-03 12:04:38 +00:00
|
|
|
fee:
|
|
|
|
candidate: {{ .CandidateFee}}
|
|
|
|
container: {{ .ContainerFee}}
|
2021-10-18 12:01:38 +00:00
|
|
|
container_alias: {{ .ContainerAliasFee }}
|
2021-08-03 12:04:38 +00:00
|
|
|
withdraw: {{ .WithdrawFee}}
|
2022-12-23 17:35:35 +00:00
|
|
|
# if credentials section is omitted, then frostfs-adm will require manual password input
|
2022-04-06 09:36:35 +00:00
|
|
|
credentials:
|
|
|
|
contract: password # wallet for contract group signature{{ range.Glagolitics}}
|
2021-07-06 11:27:54 +00:00
|
|
|
{{.}}: password{{end}}
|
|
|
|
`
|
|
|
|
|
2023-04-26 08:24:40 +00:00
|
|
|
func initConfig(cmd *cobra.Command, _ []string) error {
|
2021-07-06 11:27:54 +00:00
|
|
|
configPath, err := readConfigPathFromArgs(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-02-02 13:28:08 +00:00
|
|
|
pathDir := filepath.Dir(configPath)
|
2023-10-31 11:56:55 +00:00
|
|
|
err = os.MkdirAll(pathDir, 0o700)
|
2021-07-06 11:27:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("create dir %s: %w", pathDir, err)
|
|
|
|
}
|
|
|
|
|
2023-10-31 11:56:55 +00:00
|
|
|
f, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, 0o600)
|
2021-07-06 11:27:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("open %s: %w", configPath, err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
configText, err := generateConfigExample(pathDir, 7)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = f.WriteString(configText)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("writing to %s: %w", configPath, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd.Printf("Initial config file saved to %s\n", configPath)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func readConfigPathFromArgs(cmd *cobra.Command) (string, error) {
|
|
|
|
configPath, err := cmd.Flags().GetString(configPathFlag)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if configPath != "" {
|
|
|
|
return configPath, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return defaultConfigPath()
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultConfigPath() (string, error) {
|
|
|
|
home, err := os.UserHomeDir()
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("getting home dir path: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-12-23 17:35:35 +00:00
|
|
|
return filepath.Join(home, ".frostfs", "adm", "config.yml"), nil
|
2021-07-06 11:27:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// generateConfigExample builds .yml representation of the config file. It is
|
|
|
|
// easier to build it manually with template instead of using viper, because we
|
|
|
|
// want to order records in specific order in file and, probably, provide
|
|
|
|
// some comments as well.
|
|
|
|
func generateConfigExample(appDir string, credSize int) (string, error) {
|
2021-08-03 12:04:38 +00:00
|
|
|
tmpl := configTemplate{
|
2022-05-05 18:28:11 +00:00
|
|
|
Endpoint: "https://neo.rpc.node:30333",
|
|
|
|
MaxObjectSize: 67108864, // 64 MiB
|
2024-04-08 09:27:30 +00:00
|
|
|
MaxECDataCount: 12, // Tested with 16-node networks, assuming 12 data + 4 parity nodes.
|
|
|
|
MaxECParityCount: 4, // Maximum 4 parity chunks, typically <= 3 for most policies.
|
2022-05-05 18:28:11 +00:00
|
|
|
EpochDuration: 240, // 1 hour with 15s per block
|
|
|
|
HomomorphicHashDisabled: false, // object homomorphic hash is enabled
|
|
|
|
CandidateFee: 100_0000_0000, // 100.0 GAS (Fixed8)
|
|
|
|
ContainerFee: 1000, // 0.000000001 * 7 GAS per container (Fixed12)
|
|
|
|
ContainerAliasFee: 500, // ContainerFee / 2
|
|
|
|
WithdrawFee: 1_0000_0000, // 1.0 GAS (Fixed8)
|
|
|
|
Glagolitics: make([]string, 0, credSize),
|
2021-07-06 11:27:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
appDir, err := filepath.Abs(appDir)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("making absolute path for %s: %w", appDir, err)
|
|
|
|
}
|
2022-02-02 13:28:08 +00:00
|
|
|
tmpl.AlphabetDir = filepath.Join(appDir, "alphabet-wallets")
|
2021-07-06 11:27:54 +00:00
|
|
|
|
|
|
|
var i innerring.GlagoliticLetter
|
2024-10-30 11:42:09 +00:00
|
|
|
for i = range innerring.GlagoliticLetter(credSize) {
|
2021-08-03 12:04:38 +00:00
|
|
|
tmpl.Glagolitics = append(tmpl.Glagolitics, i.String())
|
2021-07-06 11:27:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
t, err := template.New("config.yml").Parse(configTxtTemplate)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("parsing config template: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
|
2021-08-03 12:04:38 +00:00
|
|
|
err = t.Execute(buf, tmpl)
|
2021-07-06 11:27:54 +00:00
|
|
|
if err != nil {
|
2021-08-24 10:52:22 +00:00
|
|
|
return "", fmt.Errorf("generating config from template: %w", err)
|
2021-07-06 11:27:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return buf.String(), nil
|
|
|
|
}
|
2021-07-09 09:53:10 +00:00
|
|
|
|
2022-04-08 14:58:21 +00:00
|
|
|
func GetPassword(v *viper.Viper, name string) (string, error) {
|
|
|
|
key := "credentials." + name
|
2021-07-09 09:53:10 +00:00
|
|
|
if v.IsSet(key) {
|
|
|
|
return v.GetString(key), nil
|
|
|
|
}
|
|
|
|
|
2022-04-08 14:58:21 +00:00
|
|
|
prompt := "Password for " + name + " wallet > "
|
2021-07-09 09:53:10 +00:00
|
|
|
return input.ReadPassword(prompt)
|
|
|
|
}
|
2022-06-23 07:45:56 +00:00
|
|
|
|
|
|
|
func GetStoragePassword(v *viper.Viper, name string) (string, error) {
|
|
|
|
key := "storage." + name
|
|
|
|
if name != "" && v.IsSet(key) {
|
|
|
|
return v.GetString(key), nil
|
|
|
|
}
|
|
|
|
return input.ReadPassword("New password > ")
|
|
|
|
}
|