2026-02-17 12:22:36 +00:00

259 lines
4.9 KiB
Go

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
package util
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path/filepath"
"strconv"
"time"
"github.com/fluxerapp/fluxer/fluxer_devops/livekitctl/internal/errors"
)
func Log(msg string) {
fmt.Println(msg)
}
func Logf(format string, args ...interface{}) {
fmt.Printf(format+"\n", args...)
}
func Which(binName string) string {
path, err := exec.LookPath(binName)
if err != nil {
return ""
}
return path
}
type RunOptions struct {
Check bool
Capture bool
Env []string
Cwd string
}
type RunResult struct {
ExitCode int
Output string
}
func Run(cmd []string, opts RunOptions) (*RunResult, error) {
if len(cmd) == 0 {
return nil, errors.NewCmdError("empty command", nil)
}
c := exec.Command(cmd[0], cmd[1:]...)
if opts.Cwd != "" {
c.Dir = opts.Cwd
}
if len(opts.Env) > 0 {
c.Env = append(os.Environ(), opts.Env...)
}
var output bytes.Buffer
if opts.Capture {
c.Stdout = &output
c.Stderr = &output
} else {
c.Stdout = os.Stdout
c.Stderr = os.Stderr
}
err := c.Run()
exitCode := 0
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
} else {
return nil, errors.NewCmdError(fmt.Sprintf("command not found: %s", cmd[0]), err)
}
}
result := &RunResult{
ExitCode: exitCode,
Output: output.String(),
}
if opts.Check && exitCode != 0 {
return result, errors.NewCmdError(
fmt.Sprintf("command failed (%d): %v\n%s", exitCode, cmd, result.Output),
nil,
)
}
return result, nil
}
func RunSimple(cmd []string) error {
_, err := Run(cmd, RunOptions{Check: true, Capture: false})
return err
}
func RunCapture(cmd []string) (string, error) {
result, err := Run(cmd, RunOptions{Check: true, Capture: true})
if err != nil {
return "", err
}
return result.Output, nil
}
func RunCaptureNoCheck(cmd []string) (string, int) {
result, _ := Run(cmd, RunOptions{Check: false, Capture: true})
if result == nil {
return "", -1
}
return result.Output, result.ExitCode
}
func AtomicWriteText(path string, content string, mode os.FileMode, uid, gid int) error {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
tmpFile, err := os.CreateTemp(dir, ".tmp-")
if err != nil {
return err
}
tmpName := tmpFile.Name()
_, err = tmpFile.WriteString(content)
if err != nil {
tmpFile.Close()
os.Remove(tmpName)
return err
}
if err := tmpFile.Sync(); err != nil {
tmpFile.Close()
os.Remove(tmpName)
return err
}
tmpFile.Close()
if err := os.Chmod(tmpName, mode); err != nil {
os.Remove(tmpName)
return err
}
if uid >= 0 || gid >= 0 {
if err := os.Chown(tmpName, uid, gid); err != nil {
os.Remove(tmpName)
return err
}
}
return os.Rename(tmpName, path)
}
func ReadJSON(path string, v interface{}) error {
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
return json.Unmarshal(data, v)
}
func WriteJSON(path string, v interface{}, mode os.FileMode, uid, gid int) error {
data, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
content := string(data) + "\n"
return AtomicWriteText(path, content, mode, uid, gid)
}
func NowRFC3339() string {
return time.Now().UTC().Format(time.RFC3339)
}
func EnsureDir(path string, mode os.FileMode, uid, gid int) error {
if err := os.MkdirAll(path, mode); err != nil {
return err
}
if err := os.Chmod(path, mode); err != nil {
return err
}
if uid >= 0 || gid >= 0 {
if err := os.Chown(path, uid, gid); err != nil {
return err
}
}
return nil
}
type UserGroup struct {
UID int
GID int
}
func LookupUserGroup(username string) *UserGroup {
u, err := user.Lookup(username)
if err != nil {
return nil
}
uid, err := strconv.Atoi(u.Uid)
if err != nil {
return nil
}
gid, err := strconv.Atoi(u.Gid)
if err != nil {
return nil
}
return &UserGroup{UID: uid, GID: gid}
}
func FileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func CopyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
return err
}