package minio import ( "context" "encoding/json" "fmt" "os" "path/filepath" "strings" "github.com/example/storage-appliance/internal/infra/osexec" ) type Adapter struct { Runner osexec.Runner EnvPath string } func NewAdapter(runner osexec.Runner, envPath string) *Adapter { if envPath == "" { envPath = "/etc/minio/minio.env" } return &Adapter{Runner: runner, EnvPath: envPath} } type Settings struct { AccessKey string `json:"access_key"` SecretKey string `json:"secret_key"` DataPath string `json:"data_path"` Port int `json:"port"` TLS bool `json:"tls"` } // WriteEnv writes environment file used by MinIO service func (a *Adapter) WriteEnv(ctx context.Context, s Settings) error { dir := filepath.Dir(a.EnvPath) if err := os.MkdirAll(dir, 0755); err != nil { return err } // env lines lines := []string{ fmt.Sprintf("MINIO_ROOT_USER=%s", s.AccessKey), fmt.Sprintf("MINIO_ROOT_PASSWORD=%s", s.SecretKey), fmt.Sprintf("MINIO_VOLUMES=%s", s.DataPath), } if s.Port != 0 { lines = append(lines, fmt.Sprintf("MINIO_OPTS=--address :%d", s.Port)) } content := strings.Join(lines, "\n") + "\n" tmp := filepath.Join(dir, ".minio.env.tmp") if err := os.WriteFile(tmp, []byte(content), 0600); err != nil { return err } if err := os.Rename(tmp, a.EnvPath); err != nil { return err } return nil } // Reload reloads minio service to pick up new env; prefer systemctl reload func (a *Adapter) Reload(ctx context.Context) error { _, stderr, _, err := osexec.ExecWithRunner(a.Runner, ctx, "systemctl", "reload", "minio") if err == nil { return nil } // fallback to restart _, stderr, _, err = osexec.ExecWithRunner(a.Runner, ctx, "systemctl", "restart", "minio") if err != nil { return fmt.Errorf("minio reload/restart failed: %s", stderr) } return nil } // ConfigureMC configures mc alias to point to the MinIO service using given settings func (a *Adapter) ConfigureMC(ctx context.Context, alias string, settings Settings) error { secure := "--insecure" if settings.TLS { secure = "" } // mc alias set [--api S3v4] endpoint := fmt.Sprintf("http://127.0.0.1:%d", settings.Port) if settings.TLS { endpoint = fmt.Sprintf("https://127.0.0.1:%d", settings.Port) } _, stderr, _, err := osexec.ExecWithRunner(a.Runner, ctx, "mc", "alias", "set", alias, endpoint, settings.AccessKey, settings.SecretKey, secure) if err != nil { return fmt.Errorf("mc alias set failed: %s", stderr) } return nil } // ListBuckets uses mc to list buckets via alias func (a *Adapter) ListBuckets(ctx context.Context, alias string) ([]string, error) { out, stderr, _, err := osexec.ExecWithRunner(a.Runner, ctx, "mc", "ls", "--json", alias) if err != nil { return nil, fmt.Errorf("mc ls failed: %s", stderr) } // parse JSON lines, each contains a 'key' or 'name' - in mc, `ls --json` returns 'key' var buckets []string lines := strings.Split(strings.TrimSpace(out), "\n") for _, l := range lines { var obj map[string]any if err := json.Unmarshal([]byte(l), &obj); err != nil { continue } if otype, ok := obj["type"].(string); ok && otype == "bucket" { if name, ok := obj["key"].(string); ok { buckets = append(buckets, name) } } } return buckets, nil } // CreateBucket uses mc to create a new bucket alias/ func (a *Adapter) CreateBucket(ctx context.Context, alias, name string) error { _, stderr, _, err := osexec.ExecWithRunner(a.Runner, ctx, "mc", "mb", alias+"/"+name) if err != nil { return fmt.Errorf("mc mb failed: %s", stderr) } return nil }