2021-12-04 02:25:09 +00:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2021-12-04 02:56:57 +00:00
|
|
|
"errors"
|
2021-12-04 02:25:09 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2021-12-04 02:56:57 +00:00
|
|
|
"io/fs"
|
2021-12-04 02:25:09 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2021-12-04 08:58:16 +00:00
|
|
|
"strings"
|
2021-12-04 02:25:09 +00:00
|
|
|
|
2021-12-04 08:58:16 +00:00
|
|
|
"gitea.benny.dog/torjus/ezshare/store"
|
2021-12-04 02:25:09 +00:00
|
|
|
"github.com/pelletier/go-toml"
|
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
)
|
|
|
|
|
2021-12-06 05:08:17 +00:00
|
|
|
var ErrNotFound = fmt.Errorf("config not found")
|
|
|
|
|
2021-12-04 02:25:09 +00:00
|
|
|
type Config struct {
|
|
|
|
Server *ServerConfig `toml:"Server"`
|
|
|
|
Client *ClientConfig `toml:"Client"`
|
|
|
|
location string
|
|
|
|
}
|
|
|
|
type CertificatePaths struct {
|
|
|
|
CertificatePath string `toml:"CertificatePath"`
|
|
|
|
CertificateKeyPath string `toml:"CertificateKeyPath"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServerConfig struct {
|
2021-12-06 05:08:17 +00:00
|
|
|
LogLevel string `toml:"LogLevel"`
|
|
|
|
Hostname string `toml:"Hostname"`
|
|
|
|
GRPCEndpoint string `toml:"GRPCEndpoint"`
|
|
|
|
UserStoreConfig *ServerUserStoreConfig `toml:"UserStore"`
|
|
|
|
FileStoreConfig *ServerFileStoreConfig `toml:"FileStore"`
|
|
|
|
GRPC *ServerGRPCConfig `toml:"GRPC"`
|
|
|
|
HTTP *ServerHTTPConfig `toml:"HTTP"`
|
2021-12-04 02:25:09 +00:00
|
|
|
}
|
2021-12-04 08:58:16 +00:00
|
|
|
|
2021-12-06 05:08:17 +00:00
|
|
|
type ServerFileStoreConfig struct {
|
2021-12-04 08:58:16 +00:00
|
|
|
Type string `toml:"Type"`
|
|
|
|
FSStoreConfig *FSStoreConfig `toml:"Filesystem"`
|
|
|
|
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
|
|
|
|
}
|
|
|
|
|
2021-12-06 05:08:17 +00:00
|
|
|
type ServerUserStoreConfig struct {
|
|
|
|
Type string `toml:"Type"`
|
|
|
|
BoltStoreConfig *BoltStoreConfig `toml:"Bolt"`
|
|
|
|
}
|
|
|
|
|
2021-12-04 08:58:16 +00:00
|
|
|
type BoltStoreConfig struct {
|
|
|
|
Path string `toml:"Path"`
|
2021-12-04 02:25:09 +00:00
|
|
|
}
|
2021-12-04 08:58:16 +00:00
|
|
|
|
2021-12-04 02:25:09 +00:00
|
|
|
type FSStoreConfig struct {
|
|
|
|
Dir string `toml:"Dir"`
|
|
|
|
}
|
2021-12-04 08:58:16 +00:00
|
|
|
|
2021-12-04 02:25:09 +00:00
|
|
|
type ServerGRPCConfig struct {
|
|
|
|
ListenAddr string `toml:"ListenAddr"`
|
|
|
|
CACerts *CertificatePaths `toml:"CACerts"`
|
|
|
|
Certs *CertificatePaths `toml:"Certs"`
|
|
|
|
}
|
2021-12-04 08:58:16 +00:00
|
|
|
|
2021-12-04 02:25:09 +00:00
|
|
|
type ServerHTTPConfig struct {
|
|
|
|
ListenAddr string `toml:"ListenAddr"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ClientConfig struct {
|
|
|
|
DefaultServer string `toml:"DefaultServer"`
|
|
|
|
ServerCertPath string `toml:"ServerCertPath"`
|
|
|
|
Certs *CertificatePaths `toml:"Certs"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func FromDefault() *Config {
|
|
|
|
cfg := &Config{
|
|
|
|
Server: &ServerConfig{
|
2021-12-04 03:31:19 +00:00
|
|
|
LogLevel: "INFO",
|
2021-12-04 02:25:09 +00:00
|
|
|
GRPC: &ServerGRPCConfig{
|
|
|
|
ListenAddr: ":50051",
|
|
|
|
CACerts: &CertificatePaths{},
|
|
|
|
Certs: &CertificatePaths{},
|
|
|
|
},
|
|
|
|
HTTP: &ServerHTTPConfig{
|
|
|
|
ListenAddr: ":8089",
|
|
|
|
},
|
|
|
|
},
|
2021-12-06 05:08:17 +00:00
|
|
|
Client: &ClientConfig{
|
|
|
|
Certs: &CertificatePaths{},
|
|
|
|
},
|
2021-12-04 02:25:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return cfg
|
|
|
|
}
|
|
|
|
|
|
|
|
func FromReader(r io.Reader) (*Config, error) {
|
|
|
|
decoder := toml.NewDecoder(r)
|
|
|
|
c := FromDefault()
|
|
|
|
if err := decoder.Decode(c); err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read config: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func FromFile(path string) (*Config, error) {
|
|
|
|
f, err := os.Open(path)
|
|
|
|
if err != nil {
|
2021-12-06 05:08:17 +00:00
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
return nil, ErrNotFound
|
|
|
|
}
|
2021-12-04 02:25:09 +00:00
|
|
|
return nil, fmt.Errorf("unable to open config-file: %w", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
cfg, err := FromReader(f)
|
|
|
|
if err == nil {
|
|
|
|
cfg.location = path
|
|
|
|
}
|
|
|
|
|
|
|
|
return cfg, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func FromDefaultLocations() (*Config, error) {
|
|
|
|
defaultLocations := []string{
|
|
|
|
"ezshare.toml",
|
|
|
|
}
|
|
|
|
userConfigDir, err := os.UserConfigDir()
|
2021-12-04 03:13:08 +00:00
|
|
|
if err == nil {
|
2021-12-04 02:25:09 +00:00
|
|
|
defaultLocations = append(defaultLocations, filepath.Join(userConfigDir, "ezshare", "ezshare.toml"))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, location := range defaultLocations {
|
|
|
|
if _, err := os.Stat(location); err == nil {
|
|
|
|
return FromFile(location)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("config not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Config) Location() string {
|
|
|
|
return c.location
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *CertificatePaths) GetCertBytes() ([]byte, error) {
|
|
|
|
f, err := os.Open(cp.CertificatePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ioutil.ReadAll(f)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *CertificatePaths) GetKeyBytes() ([]byte, error) {
|
|
|
|
f, err := os.Open(cp.CertificateKeyPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ioutil.ReadAll(f)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cc *ClientConfig) ServerCertBytes() ([]byte, error) {
|
|
|
|
f, err := os.Open(cc.ServerCertPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to open server certificate: %w", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
data, err := ioutil.ReadAll(f)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read client server certificate: %w", err)
|
|
|
|
}
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cc *ClientConfig) Creds() (credentials.TransportCredentials, error) {
|
|
|
|
srvCertBytes, err := cc.ServerCertBytes()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
clientCertBytes, err := cc.Certs.GetCertBytes()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read client cert: %w", err)
|
|
|
|
}
|
|
|
|
clientKeyBytes, err := cc.Certs.GetKeyBytes()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read client cert: %w", err)
|
|
|
|
}
|
|
|
|
certPool := x509.NewCertPool()
|
|
|
|
if !certPool.AppendCertsFromPEM(srvCertBytes) {
|
|
|
|
return nil, fmt.Errorf("unable to load ca cert")
|
|
|
|
}
|
|
|
|
clientCert, err := tls.X509KeyPair(clientCertBytes, clientKeyBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to load client cert: %s", err)
|
|
|
|
}
|
|
|
|
config := &tls.Config{
|
|
|
|
Certificates: []tls.Certificate{clientCert},
|
|
|
|
RootCAs: certPool,
|
|
|
|
}
|
|
|
|
return credentials.NewTLS(config), nil
|
2021-12-04 02:56:57 +00:00
|
|
|
}
|
2021-12-04 02:25:09 +00:00
|
|
|
|
2021-12-06 05:08:17 +00:00
|
|
|
func CreateDefaultConfigDir() error {
|
2021-12-04 02:56:57 +00:00
|
|
|
userConfigDir, err := os.UserConfigDir()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
configDirPath := filepath.Join(userConfigDir, "ezshare")
|
|
|
|
info, err := os.Stat(configDirPath)
|
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.Mkdir(configDirPath, 0755); err != nil {
|
|
|
|
return fmt.Errorf("unable to create config-dir: %w", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !info.IsDir() {
|
|
|
|
return fmt.Errorf("config-directory is not a directory")
|
|
|
|
}
|
|
|
|
}
|
2021-12-06 05:08:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func DefaultConfigFilePath() (string, error) {
|
|
|
|
userConfigDir, err := os.UserConfigDir()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return filepath.Join(userConfigDir, "ezshare", "ezshare.toml"), nil
|
2021-12-04 02:56:57 +00:00
|
|
|
|
2021-12-06 05:08:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Config) ToDefaultFile() error {
|
|
|
|
if err := CreateDefaultConfigDir(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
configFilePath, err := DefaultConfigFilePath()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-12-04 02:56:57 +00:00
|
|
|
_, err = os.Stat(configFilePath)
|
|
|
|
if err != nil {
|
|
|
|
if !errors.Is(err, fs.ErrNotExist) {
|
|
|
|
return fmt.Errorf("error stating config-file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.Create(configFilePath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to create config-file: %w", err)
|
|
|
|
}
|
|
|
|
encoder := toml.NewEncoder(f)
|
|
|
|
fmt.Printf("Writing config to '%s'", configFilePath)
|
|
|
|
return encoder.Encode(c)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("config-file already exists")
|
2021-12-04 02:25:09 +00:00
|
|
|
}
|
2021-12-04 08:58:16 +00:00
|
|
|
|
2021-12-06 05:08:17 +00:00
|
|
|
func (sc *ServerFileStoreConfig) GetStore() (store.FileStore, func() error, error) {
|
2021-12-04 08:58:16 +00:00
|
|
|
nopCloseFunc := func() error { return nil }
|
|
|
|
if strings.EqualFold(sc.Type, "bolt") {
|
|
|
|
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return s, s.Close, err
|
|
|
|
|
|
|
|
}
|
|
|
|
if strings.EqualFold(sc.Type, "filesystem") {
|
|
|
|
s := store.NewFileSystemStore(sc.FSStoreConfig.Dir)
|
|
|
|
return s, nopCloseFunc, nil
|
|
|
|
}
|
|
|
|
if strings.EqualFold(sc.Type, "memory") {
|
2021-12-05 00:00:32 +00:00
|
|
|
return store.NewMemoryStore(), nopCloseFunc, nil
|
2021-12-04 08:58:16 +00:00
|
|
|
}
|
2021-12-06 05:08:17 +00:00
|
|
|
if strings.EqualFold(sc.Type, "bolt") {
|
|
|
|
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
closeFunc := func() error { return s.Close() }
|
|
|
|
return s, closeFunc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil, fmt.Errorf("invalid store config")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sc *ServerUserStoreConfig) GetStore() (store.UserStore, func() error, error) {
|
|
|
|
nopCloseFunc := func() error { return nil }
|
|
|
|
if strings.EqualFold(sc.Type, "bolt") {
|
|
|
|
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return s, s.Close, err
|
|
|
|
|
|
|
|
}
|
|
|
|
if strings.EqualFold(sc.Type, "memory") {
|
|
|
|
return store.NewMemoryStore(), nopCloseFunc, nil
|
|
|
|
}
|
|
|
|
if strings.EqualFold(sc.Type, "bolt") {
|
|
|
|
s, err := store.NewBoltStore(sc.BoltStoreConfig.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
closeFunc := func() error { return s.Close() }
|
|
|
|
return s, closeFunc, nil
|
|
|
|
}
|
2021-12-04 08:58:16 +00:00
|
|
|
|
|
|
|
return nil, nil, fmt.Errorf("invalid store config")
|
|
|
|
}
|