8 Commits

Author SHA1 Message Date
63727343ee [1.3] 신고
- 데모 파일 압축해서 업로드하도록 수정
2025-07-14 11:32:25 +09:00
19a4ff103f metric 서비스 복원 2025-07-01 18:51:40 +09:00
106aa68529 prometheus metric 관련 코드 제거 2025-06-26 14:48:05 +09:00
cf4a2a3ea5 포트 수정 및 로그 추가 2025-06-25 19:43:36 +09:00
965d6fa5b9 [1.2] 인프라
- houston.sh 리눅스 줄바꿈으로 변경
- make_houston_package.ps1이 houston.sh도 포함하도록 변경
2025-06-09 12:08:29 +09:00
0ed48e0de7 [1.2] 리플레이
- 리플레이 파일 다운로드 api 배열로 반환하도록 수정
2025-05-13 18:14:16 +09:00
d8ccbf209c [1.2] 리플레이
- 리플레이 파일 이름 유지하도록 수정
2025-05-13 17:37:24 +09:00
5f68795185 리플레이 저장
- stdout pipe를 통해 houston에 리플레이 upload 요청 기능 추가
2025-01-09 16:21:29 +09:00
20 changed files with 1136 additions and 718 deletions

3
.gitignore vendored
View File

@ -3,5 +3,4 @@
houston
houston.zip
config.json
.vscode/
/data
.vscode/

View File

@ -2,7 +2,6 @@ package client
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
@ -32,30 +31,15 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type runcommand struct {
Exec string `json:"exec"`
Args []string `json:"args"`
Version string `json:"version"`
AutoRestart bool `json:"auto_restart"`
OutputLogFile string `json:"logfile"`
}
type easyruncommand runcommand
func (t *runcommand) UnmarshalJSON(b []byte) error {
easy := easyruncommand{
Version: "latest",
AutoRestart: true,
}
if err := json.Unmarshal(b, &easy); err != nil {
return err
}
*t = runcommand(easy)
return nil
Exec string `json:"exec"`
Args []string `json:"args"`
Version string `json:"version"`
}
type clientConfig struct {
@ -63,6 +47,7 @@ type clientConfig struct {
HttpAddress string `json:"http_server_address"`
StorageRoot string `json:"storage_path"`
MetricNamespace string `json:"metric_namespace"`
MetricPipeName string `json:"metric_pipe"`
ConstLabels map[string]string `json:"metric_const_labels"`
Autorun map[string]runcommand `json:"autorun"`
}
@ -89,22 +74,20 @@ func loadClientConfig() (clientConfig, error) {
type HoustonClient interface {
Shutdown()
Start()
MetricHandler() http.Handler
}
var seq = int32(1)
type procmeta struct {
id int32
cmd *exec.Cmd
name string
args []string
version string
verpath string
recover bool
state int32
stdin io.WriteCloser
logfile string
keepLatest bool
id int32
cmd *exec.Cmd
name string
args []string
version string
verpath string
state int32
stdin io.WriteCloser
}
func (pm *procmeta) isState(s protos.ProcessState) bool {
@ -120,9 +103,10 @@ func (pm *procmeta) setState(s protos.ProcessState) {
}
type uploadRequest struct {
logFile string
name string
version string
filePath string
name string
version string
uploadedFileName string
}
type houstonClient struct {
@ -141,16 +125,10 @@ type houstonClient struct {
version string
standalone bool
siblingProcIndex map[string]uint64
registry *prometheus.Registry
}
func unmarshal[T any](val *T, src map[string]string) {
defer func() {
r := recover()
if r != nil {
logger.Error(r)
}
}()
argval := reflect.ValueOf(val)
for i := 0; i < argval.Elem().Type().NumField(); i++ {
if !argval.Elem().Type().Field(i).IsExported() {
@ -163,9 +141,6 @@ func unmarshal[T any](val *T, src map[string]string) {
} else if argval.Elem().Field(i).Kind() == reflect.Array || argval.Elem().Field(i).Kind() == reflect.Slice {
conv := strings.Split(arg, "\n")
argval.Elem().Field(i).Set(reflect.ValueOf(conv))
} else if argval.Elem().Field(i).Kind() == reflect.Bool {
bv, _ := strconv.ParseBool(arg)
argval.Elem().Field(i).SetBool(bv)
} else {
argval.Elem().Field(i).SetString(arg)
}
@ -329,6 +304,7 @@ func NewClient(standalone bool) (HoustonClient, error) {
standalone: standalone,
uploadChan: make(chan uploadRequest, 100),
siblingProcIndex: make(map[string]uint64),
registry: prometheus.NewRegistry(),
}
ctx, cancel := context.WithCancel(context.Background())
@ -336,7 +312,6 @@ func NewClient(standalone bool) (HoustonClient, error) {
exitChan := make(chan *exec.Cmd, 10)
operationChan := make(chan *protos.OperationQueryResponse, 10)
hc.wg.Add(1)
ignoreRecover := int32(0)
// autorun 처리
go func() {
@ -375,10 +350,6 @@ func NewClient(standalone bool) (HoustonClient, error) {
proc.cmd.Process.Release()
if proc.isState(protos.ProcessState_Restart) {
if proc.keepLatest {
proc.version = "latest"
}
if err := hc.startChildProcess(&shared.StartProcessRequest{
Version: proc.version,
Name: proc.name,
@ -399,58 +370,65 @@ func NewClient(standalone bool) (HoustonClient, error) {
op.Refresh(ctx, hc.makeOperationQueryRequest())
case resp := <-operationChan:
logger.Println("houton query operation :", resp.Operation)
switch shared.Operation(resp.Operation) {
case shared.Deploy:
var dr shared.DeployRequest
unmarshal(&dr, resp.Args)
logger.Println("args :", dr)
hn, _ := os.Hostname()
if err := hc.deploy(&dr, func(dp *protos.DeployingProgress) {
dp.Hostname = hn
dp.Name = dr.Name
dp.Version = dr.Version
op.ReportDeployingProgress(ctx, dp)
}); err == nil {
if dr.Name == "houston" {
// houston_update_dir 다운로드가 완료되었으므로 종료
// 종료되고나면 스크립트가 알아서 재 실행
hc.Shutdown()
return
if dr.Name == myname {
if srcdir, replacer, err := hc.prepareUpdateSelf(&dr); err == nil {
args := []string{
fmt.Sprintf("%d", os.Getpid()),
srcdir,
filepath.ToSlash(os.Args[0]),
}
args = append(args, os.Args[1:]...)
cmd := exec.Command(replacer, args...)
if err := cmd.Start(); err != nil {
logger.Println(err)
} else {
hc.shutdownFunc()
}
} else {
logger.Println(err)
}
prog := gatherDeployedPrograms(hc.config.StorageRoot, dr.Name)
hc.deploys[dr.Name] = prog
op.Refresh(ctx, hc.makeOperationQueryRequest())
op.ReportDeployingProgress(ctx, &protos.DeployingProgress{
Hostname: hn,
Name: dr.Name,
Version: dr.Version,
State: "success",
Progress: 0,
Total: 0,
})
} else {
logger.Println(err)
hn, _ := os.Hostname()
op.ReportDeployingProgress(ctx, &protos.DeployingProgress{
Hostname: hn,
Name: dr.Name,
Version: dr.Version,
State: "fail:" + err.Error(),
Progress: 0,
Total: 0,
})
if err := hc.deploy(&dr, func(dp *protos.DeployingProgress) {
dp.Hostname = hn
dp.Name = dr.Name
dp.Version = dr.Version
op.ReportDeployingProgress(ctx, dp)
}); err == nil {
prog := gatherDeployedPrograms(hc.config.StorageRoot, dr.Name)
hc.deploys[dr.Name] = prog
op.Refresh(ctx, hc.makeOperationQueryRequest())
op.ReportDeployingProgress(ctx, &protos.DeployingProgress{
Hostname: hn,
Name: dr.Name,
Version: dr.Version,
State: "success",
Progress: 0,
Total: 0,
})
} else {
logger.Println(err)
op.ReportDeployingProgress(ctx, &protos.DeployingProgress{
Hostname: hn,
Name: dr.Name,
Version: dr.Version,
State: "fail:" + err.Error(),
Progress: 0,
Total: 0,
})
}
}
case shared.Withdraw:
var wr shared.WithdrawRequest
unmarshal(&wr, resp.Args)
logger.Println("args :", wr)
err := hc.withdraw(&wr)
if err == nil {
prog := gatherDeployedPrograms(hc.config.StorageRoot, wr.Name)
@ -467,8 +445,6 @@ func NewClient(standalone bool) (HoustonClient, error) {
case shared.Start:
var sr shared.StartProcessRequest
unmarshal(&sr, resp.Args)
logger.Println("args :", sr)
if err := hc.startChildProcess(&sr); err != nil {
logger.ErrorWithCallStack(err)
} else {
@ -478,8 +454,6 @@ func NewClient(standalone bool) (HoustonClient, error) {
case shared.Stop:
var sr shared.StopProcessRequest
unmarshal(&sr, resp.Args)
logger.Println("args :", sr)
if err := hc.stopChildProcess(&sr, op); err != nil {
logger.Println(err)
}
@ -487,52 +461,32 @@ func NewClient(standalone bool) (HoustonClient, error) {
case shared.Restart:
var rr shared.RestartProcessRequest
unmarshal(&rr, resp.Args)
logger.Println("args :", rr)
if err := hc.restartChildProcess(&rr, op); err != nil {
logger.Println(err)
}
case shared.Upload:
var ur shared.UploadRequest
unmarshal(&ur, resp.Args)
if err := hc.uploadFiles(&ur); err != nil {
logger.Println(err)
}
case shared.Exception:
idstr := resp.Args["id"]
id64, _ := strconv.ParseInt(idstr, 10, 0)
id := int32(id64)
var found *procmeta
hc.childProcs = gocommon.ShrinkSlice(hc.childProcs, func(e *procmeta) bool {
if e.id == id {
found = e
e.cmd.Wait()
e.cmd.Process.Release()
return true
}
return false
})
if found != nil {
found.cmd.Wait()
found.cmd.Process.Release()
if found.recover && atomic.LoadInt32(&ignoreRecover) == 0 {
time.Sleep(time.Second)
sr := shared.StartProcessRequest{
Name: found.name,
Version: found.version,
Args: found.args,
AutoRestart: found.recover,
OutputLogFile: found.logfile,
}
if err := hc.startChildProcess(&sr); err != nil {
logger.Println("startChildProcess failed by autorun :", err)
logger.ErrorWithCallStack(err)
} else {
logger.Println("recover success :", sr)
}
}
}
if op != nil {
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
}
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
}
}
}
@ -540,8 +494,6 @@ func NewClient(standalone bool) (HoustonClient, error) {
hc.shutdownFunc = func() {
// child process 강제 종료
atomic.StoreInt32(&ignoreRecover, 1)
for _, procmeta := range hc.childProcs {
if procmeta.cmd != nil && procmeta.cmd.Process != nil {
procmeta.cmd.Process.Signal(os.Kill)
@ -559,7 +511,7 @@ func NewClient(standalone bool) (HoustonClient, error) {
return hc, nil
}
func uploadSafe(url, filePath, name, version string) error {
func uploadSafe(url, filePath, name, version, uploadedFileName string) error {
defer func() {
r := recover()
if r != nil {
@ -593,7 +545,10 @@ func uploadSafe(url, filePath, name, version string) error {
// createTime := file.
httpreq.Header.Set("Houston-Service-Name", name)
httpreq.Header.Set("Houston-Service-Version", version)
httpreq.Header.Set("Houston-Service-Filename", t.BirthTime().UTC().Format(time.DateOnly)+"."+hn+path.Ext(filePath))
if len(uploadedFileName) == 0 {
uploadedFileName = t.BirthTime().UTC().Format(time.DateOnly) + "." + hn + path.Ext(filePath)
}
httpreq.Header.Set("Houston-Service-Filename", uploadedFileName)
httpreq.Header.Set("Content-Type", "application/zip")
resp, err := http.DefaultClient.Do(httpreq)
if err != nil {
@ -632,12 +587,22 @@ func (hc *houstonClient) Start() {
close(hc.uploadChan)
}()
if len(hc.config.MetricPipeName) == 0 {
hc.config.MetricPipeName = "houston_metric_pipe"
}
if len(hc.config.MetricNamespace) == 0 {
hc.config.MetricNamespace = "ou"
}
run_metric_pipe_reader(hc.config, hc.registry, hc.ctx)
go func() {
// upload 고루틴
url := hc.config.HttpAddress + "/upload"
for req := range hc.uploadChan {
logger.Println("uploadSafe :", req)
err := uploadSafe(url, req.logFile, req.name, req.version)
err := uploadSafe(url, req.filePath, req.name, req.version, req.uploadedFileName)
if err != nil {
logger.Println("uploadSafe return err :", err)
}
@ -674,11 +639,9 @@ func (hc *houstonClient) Start() {
// service 서비스
for i := 0; i < count; i++ {
sr := shared.StartProcessRequest{
Name: service,
Version: cmd.Version,
Args: append([]string{cmd.Exec}, cmd.Args...),
AutoRestart: cmd.AutoRestart,
OutputLogFile: cmd.OutputLogFile,
Name: service,
Version: cmd.Version,
Args: append([]string{cmd.Exec}, cmd.Args...),
}
if err := hc.startChildProcess(&sr); err != nil {
@ -705,7 +668,7 @@ func (hc *houstonClient) Start() {
reconnCount++
var err error
dialContext, cancelDial := context.WithTimeout(context.Background(), 15*time.Second)
dialContext, cancelDial := context.WithTimeout(context.Background(), 5*time.Second)
client, err = grpc.DialContext(dialContext, hc.config.GrpcAddress, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
cancelDial()
@ -721,9 +684,7 @@ func (hc *houstonClient) Start() {
if client != nil {
err := hc.checkOperation(client)
if err != nil {
if status.Convert(err).Message() != status.Convert(context.Canceled).Message() {
logger.Println("grpc.DialContext hc.checkOperation failed :", err)
}
logger.Println("grpc.DialContext hc.checkOperation failed :", err)
client = nil
}
}
@ -735,6 +696,12 @@ func (hc *houstonClient) Shutdown() {
hc.shutdownFunc()
}
func (hc *houstonClient) MetricHandler() http.Handler {
return promhttp.InstrumentMetricHandler(
hc.registry, promhttp.HandlerFor(hc.registry, promhttp.HandlerOpts{}),
)
}
func (hc *houstonClient) checkOperation(client *grpc.ClientConn) error {
defer func() {
r := recover()

View File

@ -1,35 +0,0 @@
package client
import (
"fmt"
"sync"
"testing"
"time"
)
func Test_houstonClient_Start(t *testing.T) {
tc := make(chan int, 1000)
var wg sync.WaitGroup
wg.Add(1)
go func() {
// receive
defer wg.Done()
for v := range tc {
fmt.Println(v)
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
// send
for i := 0; i < 100; i++ {
tc <- i
}
close(tc)
fmt.Println("channel close called")
}()
wg.Wait()
}

View File

@ -25,10 +25,6 @@ import (
"golang.org/x/text/transform"
)
const (
houston_update_dir = "./houston.update"
)
func pof2(x int64, min int64) (out int64) {
out = 1
org := x
@ -221,7 +217,13 @@ func (hc *houstonClient) prepareDeploy(name string, version string) (destPath st
}()
verpath := path.Join(hc.config.StorageRoot, name, version)
if _, err := os.Stat(verpath); !os.IsNotExist(err) {
if _, err := os.Stat(verpath); os.IsNotExist(err) {
// 없네? 만들면 된다.
err = os.MkdirAll(verpath, 0775)
if err != nil {
return "", err
}
} else {
// 있네? 재배포 가능한가?
for _, child := range hc.childProcs {
if child.version == version && child.name == name {
@ -280,23 +282,50 @@ func copyfile(src, dst string) error {
return nil
}
func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.DeployingProgress)) (err error) {
logger.Println("start deploying")
var root string
if req.Name == "houston" {
// houston은 버전없이 houston_update_dir폴더로 다운로드
root = houston_update_dir
// 이미 houston_update_dir가 있을 수도 있으므로 폴더채로 삭제
os.RemoveAll(houston_update_dir)
} else {
root, err = hc.prepareDeploy(req.Name, req.Version)
if err != nil {
return err
}
func (hc *houstonClient) prepareUpdateSelf(req *shared.DeployRequest) (srcdir string, replacer string, err error) {
// 내가 스스로 업데이트
// 다운로드 받고 압축 푼 다음에 교체용 프로세스 시작
tempdir, err := os.MkdirTemp(os.TempDir(), "*")
if err != nil {
return "", "", err
}
fname, err := download(tempdir, hc.makeDownloadUrl(req.Url), req.AccessToken, nil)
if err != nil {
return "", "", err
}
err = os.MkdirAll(root, 0775)
switch path.Ext(fname) {
case ".zip":
err = unzip(fname)
case ".tar":
err = untar(fname)
}
if err != nil {
return "", "", err
}
// houston version 파일
err = os.WriteFile(path.Join(path.Dir(fname), "@version"), []byte(req.Version), 0644)
if err != nil {
return "", "", err
}
selfname, _ := os.Executable()
srcreplacer := path.Join(path.Dir(fname), "replacer") + path.Ext(selfname)
replacer = "./" + filepath.ToSlash("replacer"+path.Ext(selfname))
err = copyfile(srcreplacer, replacer)
if err == nil {
err = os.Chmod(replacer, 0775)
}
// replacer먼저 가져옴
return filepath.ToSlash(tempdir), replacer, err
}
func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.DeployingProgress)) error {
logger.Println("start deploying")
root, err := hc.prepareDeploy(req.Name, req.Version)
if err != nil {
return err
}
@ -305,8 +334,7 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.Deplo
h := md5.New()
h.Write([]byte(strings.Trim(req.Url, "/")))
at := hex.EncodeToString(h.Sum(nil))
var fname string
fname, err = download(root, hc.makeDownloadUrl(req.Url), at, func(written int64, total int64) {
fname, err := download(root, hc.makeDownloadUrl(req.Url), at, func(written int64, total int64) {
prog := protos.DeployingProgress{
State: "download",
Progress: written,
@ -332,7 +360,7 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.Deplo
err = untar(fname)
}
if err == nil && len(req.Config) > 0 && req.Name != "houston" {
if err == nil && len(req.Config) > 0 {
// config.json도 다운로드
h := md5.New()
h.Write([]byte(strings.Trim(req.Config, "/")))

185
client/houston_pipe_req.go Normal file
View File

@ -0,0 +1,185 @@
package client
import (
"archive/zip"
"bytes"
"crypto/aes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"errors"
"io"
"os"
"path/filepath"
"strings"
)
var pipeReqPrefix = []byte("houston_pipe_req")
var pipeReqHandle = map[string]func(hc *houstonClient, meta *procmeta, param string) error{
"upload": handleStdOutUploadRequest,
}
func HandleHoustonPipeReq(hc *houstonClient, meta *procmeta, buff []byte) (pipeRequest bool, retErr error) {
if !bytes.HasPrefix(buff, pipeReqPrefix) {
return false, nil // Not a pipe request
}
command, param, err := parsePipeReq(buff)
if err != nil {
return true, err
}
if handler, ok := pipeReqHandle[command]; ok {
if err := handler(hc, meta, param); err != nil {
return true, err
}
}
return true, nil
}
var pipeReqDelimeter = []byte("|")
var pipeReqKey = []byte{
0x77, 0x77, 0x71, 0x3c, 0x75, 0x64, 0x22, 0x54,
0x3e, 0x41, 0x27, 0x68, 0x39, 0x6e, 0x23, 0x49,
0x5f, 0x66, 0x71, 0x50, 0x32, 0x68, 0x53, 0x43,
0x72, 0x2f, 0x62, 0x39, 0x6e, 0x22, 0x27, 0x2d,
}
var errInvalidRequestBuff = errors.New("parsePipeReq got invalid request format")
func parsePipeReq(buff []byte) (command, param string, err error) {
//buff == "houston_pipe_req|EncryptString\r\n"
parts := bytes.Split(buff, pipeReqDelimeter)
if len(parts) != 2 {
return "", "", errInvalidRequestBuff
}
//Decrypt
decryptBuff, err := decryptPipeReq(parts[1])
if err != nil {
return "", "", err
}
//buff == houston_pipe_req|command|example_paramstring|MD5
//decryptBuff == command|example_paramstring|MD5
parts = bytes.Split(decryptBuff, pipeReqDelimeter)
if len(parts) != 3 {
return "", "", errInvalidRequestBuff
}
command = string(parts[0])
param = string(parts[1])
receivedHash := string(parts[2])
if err := validatePipeReq(command, param, receivedHash); err != nil {
return "", "", err
}
return command, param, nil
}
func decryptPipeReq(encordBuff []byte) ([]byte, error) {
decordBuff, err := base64.StdEncoding.DecodeString(string(encordBuff))
if err != nil {
return nil, err
}
if len(decordBuff)%aes.BlockSize != 0 {
return nil, errors.New("parsePipeReq got encrypted data which is not a multiple of the block size")
}
aesBlock, err := aes.NewCipher(pipeReqKey)
if err != nil {
return nil, err
}
decryptBuff := make([]byte, len(decordBuff))
for start := 0; start < len(decordBuff); start += aes.BlockSize {
aesBlock.Decrypt(decryptBuff[start:start+aes.BlockSize], decordBuff[start:start+aes.BlockSize])
}
return decryptBuff, nil
}
var errValidatePipeFail = errors.New("validatePipeReq fail to check validation of buff")
func validatePipeReq(command, param, receivedHash string) error {
//Decord receivedHash
receiveHashLen := md5.Size * 2
if len(receivedHash) < receiveHashLen {
return errValidatePipeFail
}
decordHash, err := hex.DecodeString(receivedHash[0:receiveHashLen])
if err != nil {
return err
}
//Generate md5 from command and param
var reqBuilder strings.Builder
reqBuilder.WriteString(command)
reqBuilder.Write(pipeReqDelimeter)
reqBuilder.WriteString(param)
buffHashWriter := md5.New()
buffHashWriter.Write([]byte(reqBuilder.String()))
buffHash := buffHashWriter.Sum(nil)
if !bytes.Equal(decordHash, buffHash) {
return errValidatePipeFail
}
return nil
}
func handleStdOutUploadRequest(hc *houstonClient, meta *procmeta, param string) error {
if uploadZipPath, err := compressFile(param); err != nil {
return err
} else {
hc.uploadToAppendFile(uploadZipPath, meta.name, meta.version, filepath.Base(uploadZipPath))
}
return nil
}
func compressFile(fullPath string) (string, error) {
ext := filepath.Ext(fullPath)
zipFullPath := fullPath[:len(fullPath)-len(ext)] + ".zip"
// Create
newZipFile, err := os.Create(zipFullPath)
if err != nil {
return "", err
}
defer newZipFile.Close()
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
// Open
fileToZip, err := os.Open(fullPath)
if err != nil {
return "", err
}
defer fileToZip.Close()
fileToZipInfo, err := fileToZip.Stat()
if err != nil {
return "", err
}
// Zip
fileToZipHeader, err := zip.FileInfoHeader(fileToZipInfo)
if err != nil {
return "", err
}
fileToZipHeader.Name = fileToZipInfo.Name()
fileToZipHeader.Method = zip.Deflate
fileToZipWriter, err := zipWriter.CreateHeader(fileToZipHeader)
if err != nil {
return "", err
}
_, err = io.Copy(fileToZipWriter, fileToZip)
if err != nil {
return "", err
}
// Remove
err = os.Remove(fullPath)
if err != nil {
return "", err
}
return zipFullPath, nil
}

View File

@ -1,133 +0,0 @@
package client
import (
"fmt"
stdlog "log"
"net/http"
_ "net/http/pprof"
"sort"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
promcollectors "github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/version"
"github.com/prometheus/node_exporter/collector"
)
// handler wraps an unfiltered http.Handler but uses a filtered handler,
// created on the fly, if filtering is requested. Create instances with
// newHandler.
type handler struct {
unfilteredHandler http.Handler
// exporterMetricsRegistry is a separate registry for the metrics about
// the exporter itself.
exporterMetricsRegistry *prometheus.Registry
includeExporterMetrics bool
maxRequests int
logger log.Logger
}
func NewHandlerForNodeExporter(includeExporterMetrics bool, maxRequests int, logger log.Logger) *handler {
h := &handler{
exporterMetricsRegistry: prometheus.NewRegistry(),
includeExporterMetrics: includeExporterMetrics,
maxRequests: maxRequests,
logger: logger,
}
if h.includeExporterMetrics {
h.exporterMetricsRegistry.MustRegister(
promcollectors.NewProcessCollector(promcollectors.ProcessCollectorOpts{}),
promcollectors.NewGoCollector(),
)
}
if innerHandler, err := h.innerHandler(); err != nil {
panic(fmt.Sprintf("Couldn't create metrics handler: %s", err))
} else {
h.unfilteredHandler = innerHandler
}
return h
}
// ServeHTTP implements http.Handler.
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
filters := r.URL.Query()["collect[]"]
level.Debug(h.logger).Log("msg", "collect query:", "filters", filters)
if len(filters) == 0 {
// No filters, use the prepared unfiltered handler.
h.unfilteredHandler.ServeHTTP(w, r)
return
}
// To serve filtered metrics, we create a filtering handler on the fly.
filteredHandler, err := h.innerHandler(filters...)
if err != nil {
level.Warn(h.logger).Log("msg", "Couldn't create filtered metrics handler:", "err", err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf("Couldn't create filtered metrics handler: %s", err)))
return
}
filteredHandler.ServeHTTP(w, r)
}
// innerHandler is used to create both the one unfiltered http.Handler to be
// wrapped by the outer handler and also the filtered handlers created on the
// fly. The former is accomplished by calling innerHandler without any arguments
// (in which case it will log all the collectors enabled via command-line
// flags).
func (h *handler) innerHandler(filters ...string) (http.Handler, error) {
nc, err := collector.NewNodeCollector(h.logger, filters...)
if err != nil {
return nil, fmt.Errorf("couldn't create collector: %s", err)
}
// Only log the creation of an unfiltered handler, which should happen
// only once upon startup.
if len(filters) == 0 {
level.Info(h.logger).Log("msg", "Enabled collectors")
collectors := []string{}
for n := range nc.Collectors {
collectors = append(collectors, n)
}
sort.Strings(collectors)
for _, c := range collectors {
level.Info(h.logger).Log("collector", c)
}
}
r := prometheus.NewRegistry()
r.MustRegister(version.NewCollector("node_exporter"))
if err := r.Register(nc); err != nil {
return nil, fmt.Errorf("couldn't register node collector: %s", err)
}
var handler http.Handler
if h.includeExporterMetrics {
handler = promhttp.HandlerFor(
prometheus.Gatherers{h.exporterMetricsRegistry, r},
promhttp.HandlerOpts{
ErrorLog: stdlog.New(log.NewStdlibAdapter(level.Error(h.logger)), "", 0),
ErrorHandling: promhttp.ContinueOnError,
MaxRequestsInFlight: h.maxRequests,
Registry: h.exporterMetricsRegistry,
},
)
// Note that we have to use h.exporterMetricsRegistry here to
// use the same promhttp metrics for all expositions.
handler = promhttp.InstrumentMetricHandler(
h.exporterMetricsRegistry, handler,
)
} else {
handler = promhttp.HandlerFor(
r,
promhttp.HandlerOpts{
ErrorLog: stdlog.New(log.NewStdlibAdapter(level.Error(h.logger)), "", 0),
ErrorHandling: promhttp.ContinueOnError,
MaxRequestsInFlight: h.maxRequests,
},
)
}
return handler, nil
}

View File

@ -8,12 +8,14 @@ import (
"errors"
"fmt"
"io"
"math"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"slices"
"sort"
"strconv"
"strings"
"syscall"
@ -21,7 +23,6 @@ import (
"github.com/Knetic/govaluate"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/metric"
"repositories.action2quare.com/ayo/houston/shared"
"repositories.action2quare.com/ayo/houston/shared/protos"
)
@ -42,43 +43,74 @@ func lastExecutionArgs(verpath string) []string {
return out
}
func (hc *houstonClient) uploadToAppendLog(logFile string, name string, version string) {
func (hc *houstonClient) uploadToAppendFile(filePath string, name string, version string, uploadedFileName string) {
hc.uploadChan <- uploadRequest{
logFile: logFile,
name: name,
version: version,
filePath: filePath,
name: name,
version: version,
uploadedFileName: uploadedFileName,
}
}
func findMatchFiles(storageRoot, name, version, filter string) (string, []string) {
root := path.Join(storageRoot, name, version)
matches, err := filepath.Glob(path.Join(root, filter))
var errUploadZipLogFailed = errors.New("not ok")
func (hc *houstonClient) uploadZipLogFile(zipFile string, name string, version string) error {
zf, err := os.Open(zipFile)
if err != nil {
return "", nil
return err
}
if zf == nil {
return errUploadZipLogFailed
}
defer zf.Close()
req, err := http.NewRequest("POST", hc.config.HttpAddress+"/upload", zf)
if err != nil {
logger.Println(err)
}
req.Header.Set("Houston-Service-Name", name)
req.Header.Set("Houston-Service-Version", version)
req.Header.Set("Houston-Service-Filename", path.Base(filepath.ToSlash(zipFile)))
req.Header.Set("Content-Type", "application/zip")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errUploadZipLogFailed
}
return nil
}
func zipLogFiles(storageRoot string, req *shared.UploadRequest) (string, []string, error) {
root := path.Join(storageRoot, req.Name, req.Version)
matches, err := filepath.Glob(path.Join(root, req.Filter))
if err != nil {
return "", nil, err
}
if len(matches) == 0 {
return "", nil
return "", nil, nil
}
root = path.Join(root, path.Dir(filter))
out := make([]string, 0, len(matches))
for _, file := range matches {
for i, file := range matches {
file = filepath.ToSlash(file)
if file == root {
continue
}
out = append(out, file)
matches[i] = file
}
slices.Sort(out)
return root, out
}
func zipCompressFiles(root string, matches []string) (string, error) {
f, err := os.CreateTemp(os.TempDir(), "*.zip")
root = path.Join(root, path.Dir(req.Filter))
hostname, _ := os.Hostname()
zipFileName := path.Join(os.TempDir(), hostname+"_"+path.Base(filepath.ToSlash(matches[0]))) + ".zip"
os.Remove(zipFileName)
f, err := os.OpenFile(zipFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
return "", err
return "", nil, err
}
defer f.Close()
@ -87,6 +119,10 @@ func zipCompressFiles(root string, matches []string) (string, error) {
oldestFile := ""
for i, file := range matches {
if file == root {
continue
}
if fi, err := os.Lstat(file); err == nil {
if (fi.Mode() & os.ModeSymlink) == os.ModeSymlink {
matches[i] = ""
@ -101,21 +137,21 @@ func zipCompressFiles(root string, matches []string) (string, error) {
relative := file[len(root)+1:]
fw, err := w.Create(relative)
if err != nil {
return "", err
return "", nil, err
}
src, err := os.Open(file)
if err != nil {
return "", err
return "", nil, err
}
defer src.Close()
if _, err = io.Copy(fw, src); err != nil {
return "", err
return "", nil, err
}
}
return f.Name(), nil
return f.Name(), matches, nil
}
func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (*procmeta, error) {
@ -123,7 +159,6 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
return nil, errors.New("args is empty")
}
foundVersion := req.Version
if req.Version == "latest" {
entries, err := os.ReadDir(path.Join(storageRoot, req.Name))
if err != nil {
@ -149,11 +184,11 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
}
if len(latestVersion) > 0 {
foundVersion = latestVersion
req.Version = latestVersion
}
}
verpath := path.Join(storageRoot, req.Name, foundVersion)
verpath := path.Join(storageRoot, req.Name, req.Version)
fi, err := os.Stat(verpath)
if err != nil {
return nil, err
@ -184,22 +219,45 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
seq++
return &procmeta{
id: seq,
cmd: cmd,
name: req.Name,
args: req.Args,
version: foundVersion,
recover: req.AutoRestart,
verpath: verpath,
state: int32(protos.ProcessState_Stopped),
stdin: stdin,
logfile: req.OutputLogFile,
keepLatest: req.Version == "latest",
id: seq,
cmd: cmd,
name: req.Name,
args: req.Args,
version: req.Version,
verpath: verpath,
state: int32(protos.ProcessState_Stopped),
stdin: stdin,
}, nil
}
return nil, errors.New("not found")
}
func makeLogFilePrefix(meta *procmeta, index int) string {
now := time.Now().UTC()
ext := path.Ext(meta.args[0])
nameonly := path.Base(filepath.ToSlash(meta.args[0]))
if len(ext) > 0 {
nameonly = nameonly[:len(nameonly)-len(ext)]
}
ts := now.Format("2006-01-02T15-04-05")
if index == 0 {
return path.Join(meta.verpath, "logs", fmt.Sprintf("%s_%s", nameonly, ts))
}
return path.Join(meta.verpath, "logs", fmt.Sprintf("%s_%d_%s", nameonly, index, ts))
}
func evaluateExpression(expression string, params map[string]any) (any, error) {
expression = strings.TrimSpace(expression)
expr, err := govaluate.NewEvaluableExpression(expression)
if err != nil {
return 0, err
}
return expr.Evaluate(params)
}
func evaluateArgs(args []string, params map[string]any) ([]string, error) {
re := regexp.MustCompile(`\$\(\((.*?)\)\)`)
@ -257,101 +315,73 @@ func (hc *houstonClient) launch(meta *procmeta) error {
if err != nil {
return err
}
stderr, err := meta.cmd.StderrPipe()
err = os.MkdirAll(path.Join(meta.verpath, "logs"), 0775)
if err != nil {
return err
}
logfolder := path.Join(meta.verpath, "logs")
err = os.MkdirAll(logfolder, 0775)
if err != nil {
return err
}
logUploader := func(localctx context.Context, logfilePath string, logChan chan []byte) {
var logFile *os.File
var logFilePath string
ext := path.Ext(logfilePath)
head := logfilePath[:len(logfilePath)-len(ext)]
if len(head) > 0 && !strings.HasSuffix(head, "/") {
head += "."
}
writeLog := func(log []byte) {
if logFile == nil {
logFilePath = head + time.Now().UTC().Format("2006-01-02.150405") + ext
logFile, _ = os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
}
for written := 0; written < len(log); {
n, err := logFile.Write(log[written:])
if err != nil {
logger.Println("write log file failed :", logfilePath, err)
break
} else {
written += n
}
}
}
defer func() {
if logFile != nil {
logFile.Close()
logFile = nil
hc.uploadToAppendLog(logFilePath, meta.name, meta.version)
}
}()
defer func() {
for {
select {
case log := <-logChan:
writeLog(log)
default:
// logChan에 있는 모든 로그 소비
return
}
}
}()
for {
heartbeat := time.After(time.Minute)
select {
case <-localctx.Done():
return
case <-heartbeat:
heartbeat = time.After(time.Minute)
// 지금까지의 로그를 저장해서 업로드
if logFile != nil {
logFile.Close()
logFile = nil
hc.uploadToAppendLog(logFilePath, meta.name, meta.version)
}
case log := <-logChan:
writeLog(log)
}
}
}
stdReader := func(r io.ReadCloser, logfilePath string, verify func(buff []byte) bool) {
stdReader := func(r io.ReadCloser, index int) {
defer func() {
reco := recover()
if reco != nil {
logger.Println(reco)
}
r.Close()
}()
localctx, cancel := context.WithCancel(context.Background())
defer cancel()
defer func() {
overflow := index / 64
offset := index % 64
key := fmt.Sprintf("%s-%d", meta.args[0], overflow)
runningFlags := hc.siblingProcIndex[key]
mask := uint64(1 << offset)
runningFlags = runningFlags ^ mask
hc.siblingProcIndex[key] = runningFlags
}()
logChan := make(chan []byte, 1)
go logUploader(localctx, logfilePath, logChan)
defer r.Close()
reader := bufio.NewReader(r)
thisFileSize := 0
logFileIndex := 0
logFileNamePrefix := makeLogFilePrefix(meta, index)
logFileName := fmt.Sprintf("%s_%d.log", logFileNamePrefix, logFileIndex)
targetFile, err := os.Create(logFileName)
if err != nil {
logger.Println("failed to create log file :", logFileName)
return
}
exef, _ := os.Executable()
var linkPath string
if index == 0 {
linkPath = path.Join(path.Dir(exef), path.Dir(logFileName), meta.name+".log")
} else {
linkPath = path.Join(path.Dir(exef), path.Dir(logFileName), fmt.Sprintf("%s_%d.log", meta.name, index))
}
os.Remove(linkPath)
os.Symlink(path.Base(filepath.ToSlash(targetFile.Name())), linkPath)
defer func() {
if targetFile != nil {
targetFile.Close()
}
}()
defer func() {
logger.Println("stdReader is terminated :", meta.name)
if meta.isState(protos.ProcessState_Running) {
hc.operationChan <- &protos.OperationQueryResponse{
Operation: string(shared.Exception),
Args: map[string]string{
"id": fmt.Sprintf("%d", meta.id),
},
}
}
}()
for {
buff, err := reader.ReadBytes('\n')
if err != nil {
@ -359,88 +389,68 @@ func (hc *houstonClient) launch(meta *procmeta) error {
break
}
if verify(buff) {
if len(buff) > 0 {
logChan <- buff
for written := 0; written < len(buff); {
n, err := targetFile.Write(buff)
if err != nil {
logger.Println("write log file failed :", logFileName, err)
break
} else {
written += n
thisFileSize += n
}
}
if thisFileSize > 5*1024*1024 {
logFileIndex++
logFileName = fmt.Sprintf("%s_%d.log", logFileNamePrefix, logFileIndex)
nextTargetFile, err := os.Create(logFileName)
if err != nil {
logger.Println("failed to create log file :", logFileName)
} else {
targetFile.Close()
targetFile = nextTargetFile
os.Remove(linkPath)
os.Symlink(path.Base(filepath.ToSlash(targetFile.Name())), linkPath)
thisFileSize = 0
}
}
}
}
var evalfile string
if len(meta.logfile) > 0 {
evalfile = path.Join(logfolder, meta.logfile)
} else {
evalfile = logfolder + "/"
index := 0
for overflow := 0; ; overflow++ {
key := fmt.Sprintf("%s-%d", meta.args[0], overflow)
runningFlags := hc.siblingProcIndex[key]
if runningFlags == math.MaxUint64 {
index += 64
} else {
for si := 0; si < 64; si++ {
mask := uint64(1 << si)
if runningFlags&mask == 0 {
index += si
runningFlags |= mask
break
}
}
hc.siblingProcIndex[key] = runningFlags
break
}
}
go func() {
metricExporter := metric.NewPrometheusExport(hc.config.MetricNamespace)
defer metricExporter.Shutdown()
var metricBuffer []byte
readingMetric := false
stdReader(stdout, evalfile+".log", func(buff []byte) bool {
if readingMetric {
metricBuffer = append(metricBuffer, buff...)
} else if buff[0] == metric.METRIC_HEAD_INLINE {
readingMetric = true
metricBuffer = append(metricBuffer, buff[1:]...)
}
if readingMetric {
if metricBuffer[len(metricBuffer)-2] == metric.METRIC_TAIL_INLINE {
readingMetric = false
metricBuffer = metricBuffer[:len(metricBuffer)-2]
if metricBuffer[0] == '{' {
var desc metric.MetricDescription
if err := json.Unmarshal(metricBuffer, &desc); err != nil {
logger.Println("unmarshal metric failed :", err, string(metricBuffer))
return false
}
if desc.ConstLabels == nil {
desc.ConstLabels = make(map[string]string)
}
for k, v := range hc.config.ConstLabels {
desc.ConstLabels[k] = v
}
desc.ConstLabels["job"] = meta.name
metricExporter.RegisterMetric(&desc)
} else {
key, val := metric.ReadMetricValue(metricBuffer)
metricExporter.UpdateMetric(key, val)
}
metricBuffer = metricBuffer[:0]
}
return false
}
return true
})
logger.Println("stdReader is terminated :", meta.name)
if meta.isState(protos.ProcessState_Running) {
// state는 running인데 종료됐으면 exception처리
hc.operationChan <- &protos.OperationQueryResponse{
Operation: string(shared.Exception),
Args: map[string]string{
"id": fmt.Sprintf("%d", meta.id),
},
}
}
}()
go stdReader(stderr, evalfile+".err", func([]byte) bool { return true })
go stdReader(stdout, index)
meta.cmd.Env = append(os.Environ(), fmt.Sprintf("HOUSTON_SIBLIING_INDEX=%d", index))
meta.cmd.Args, err = evaluateArgs(meta.cmd.Args, parseEnv(meta.cmd.Env))
if err != nil {
logger.Println("evaluateArgs failed :", err)
return err
}
logger.Println("startChildProcess :", meta.cmd.Args)
err = meta.cmd.Start()
if err == nil {
logger.Println("process index, pid =", index, meta.cmd.Process.Pid)
set_affinity(meta.cmd.Process.Pid, index)
meta.setState(protos.ProcessState_Running)
}
@ -545,3 +555,61 @@ func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest,
return nil
}
func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error {
logger.Println("uploadFiles req :", *req)
for _, child := range hc.childProcs {
if child.version == req.Version && child.name == req.Name {
logger.Println("uploadFiles found :", child.version, child.name)
go func() {
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req)
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
if err = hc.uploadZipLogFile(zipFile, child.name, child.version); err == nil {
// 마지막거 빼고 삭제
if req.DeleteAfterUploaded == "true" {
for i := 0; i < len(srcFiles)-1; i++ {
os.Remove(srcFiles[i])
}
} else {
sort.StringSlice(srcFiles).Sort()
for i := 0; i < len(srcFiles)-1; i++ {
if len(srcFiles[i]) > 0 {
os.Remove(srcFiles[i])
}
}
}
} else {
logger.Println("uploadZipLogFile failed :", err)
}
} else if err != nil {
logger.Println("zipLogFiles failed :", err)
}
}()
return nil
}
}
// 실행 중이 아닌 폴더에서도 대상을 찾는다
// 전체 파일을 대상으로
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req)
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
if err = hc.uploadZipLogFile(zipFile, req.Name, req.Version); err == nil {
// 마지막거 빼고 삭제
sort.StringSlice(srcFiles).Sort()
for i := 0; i < len(srcFiles)-1; i++ {
if len(srcFiles[i]) > 0 {
os.Remove(srcFiles[i])
}
}
} else {
logger.Println("uploadZipLogFile failed :", err)
}
} else if err != nil {
logger.Println("zipLogFiles failed :", err)
}
return nil
}

View File

@ -0,0 +1,95 @@
package client
import (
"bufio"
"context"
"encoding/json"
"io"
"maps"
"strconv"
"strings"
"github.com/prometheus/client_golang/prometheus"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/metric"
)
type pipeListener struct {
config clientConfig
registry *prometheus.Registry
}
func run_metric_pipe_reader(config clientConfig, registry *prometheus.Registry, ctx context.Context) {
r := &pipeListener{
config: config,
registry: registry,
}
go r.listen(ctx)
}
type pipeReader struct {
handle io.ReadCloser
constLabels map[string]string
collector *metric.PrometheusCollector
}
func (l *pipeListener) startReader(r io.ReadCloser) {
reader := pipeReader{
handle: r,
constLabels: l.config.ConstLabels,
collector: metric.NewPrometheusCollector(l.config.MetricNamespace, l.registry),
}
defer func() {
reader.close(l.registry)
}()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
reader.parseLine(scanner.Text())
}
}
func (r *pipeReader) close(registry *prometheus.Registry) {
registry.Unregister(r.collector)
r.handle.Close()
}
func (r *pipeReader) parseLine(line string) {
defer func() {
r := recover()
if r != nil {
logger.Println(r)
}
}()
switch line[0] {
case '{':
var desc metric.MetricDescription
if err := json.Unmarshal([]byte(line), &desc); err != nil {
logger.Println("unmarshal metric failed :", err, line)
return
}
if desc.ConstLabels == nil {
desc.ConstLabels = make(map[string]string)
}
maps.Copy(desc.ConstLabels, r.constLabels)
r.collector = r.collector.RegisterMetric(&desc)
default:
kv := strings.Split(line, ":")
if len(kv) != 2 {
return
}
if len(kv[1]) == 0 {
r.collector = r.collector.UnregisterMetric(kv[0])
} else {
if val, err := strconv.ParseFloat(kv[1], 64); err == nil {
r.collector.UpdateMetric(kv[0], val)
}
}
}
}

View File

@ -0,0 +1,40 @@
package client
import (
"context"
"os"
"golang.org/x/sys/unix"
"repositories.action2quare.com/ayo/gocommon/logger"
)
func (r *pipeListener) listen(ctx context.Context) {
mode := 0666 // 읽기/쓰기 권한
pipeName := "/tmp/" + r.config.MetricPipeName
os.Remove(pipeName)
if err := unix.Mkfifo(pipeName, uint32(mode)); err != nil {
logger.Println("mkfifo failed :", pipeName, err)
return
}
defer os.Remove(pipeName)
go func() {
// file에 쓰기 핸들을 하나 열고 ctx가 Done일 때 닫음. 이래야 reader가 계속 열려있게 됨
f, err := os.OpenFile(pipeName, os.O_WRONLY, 0)
if err != nil {
logger.Println(err)
return
}
<-ctx.Done()
f.Close()
}()
file, err := os.Open(pipeName)
if err != nil {
logger.Println("FIFO open error:", err)
return
}
r.startReader(file)
}

View File

@ -0,0 +1,35 @@
package client
import (
"context"
"strings"
"github.com/natefinch/npipe"
"repositories.action2quare.com/ayo/gocommon/logger"
)
func (r *pipeListener) listen(ctx context.Context) {
pipename := r.config.MetricPipeName
if !strings.HasPrefix(pipename, `\\.\pipe\`) {
pipename = `\\.\pipe\` + pipename
}
listener, err := npipe.Listen(pipename)
if err != nil {
logger.Println("metric pipe npipe.Listen failed :", err)
return
}
go func() {
<-ctx.Done()
logger.Println("listener close")
listener.Close()
}()
for {
if conn, err := listener.Accept(); err == nil {
go r.startReader(conn)
} else {
logger.Println("metric pipe listener.Accept failed :", err)
}
}
}

88
go.mod
View File

@ -1,72 +1,82 @@
module repositories.action2quare.com/ayo/houston
go 1.19
go 1.23.0
toolchain go1.24.4
require (
github.com/Knetic/govaluate v3.0.0+incompatible
github.com/djherbis/times v1.6.0
github.com/go-kit/log v0.2.1
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/common v0.44.0
github.com/prometheus/node_exporter v1.6.1
golang.org/x/sys v0.16.0
golang.org/x/text v0.14.0
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0
google.golang.org/grpc v1.60.1
google.golang.org/protobuf v1.32.0
repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024
google.golang.org/protobuf v1.36.1
repositories.action2quare.com/ayo/gocommon v0.0.0-20250701095003-d77fa2108add
)
require (
github.com/alecthomas/kingpin/v2 v2.3.2 // indirect
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/beevik/ntp v0.3.0 // indirect
github.com/beevik/ntp v1.4.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/dennwc/btrfs v0.0.0-20230312211831-a1f570bd01a1 // indirect
github.com/dennwc/btrfs v0.0.0-20240418142341-0167142bde7a // indirect
github.com/dennwc/ioctl v1.0.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/ema/qdisc v0.0.0-20230120214811-5b708f463de3 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/ema/qdisc v1.0.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/go-envparse v0.1.0 // indirect
github.com/hodgesds/perf-utils v0.7.0 // indirect
github.com/illumos/go-kstat v0.0.0-20210513183136-173c9b0a9973 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/jsimonetti/rtnetlink v1.3.2 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/jsimonetti/rtnetlink/v2 v2.0.2 // indirect
github.com/lufia/iostat v1.2.1 // indirect
github.com/mattn/go-xmlrpc v0.0.3 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mdlayher/ethtool v0.0.0-20221212131811-ba3b4bc2e02c // indirect
github.com/mdlayher/genetlink v1.3.1 // indirect
github.com/mdlayher/ethtool v0.2.0 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/wifi v0.0.0-20220330172155-a44c70b6d3c8 // indirect
github.com/mdlayher/wifi v0.3.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/selinux v1.11.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus-community/go-runit v0.1.0 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.2-0.20240603130017-1754b780536b // indirect
github.com/safchain/ethtool v0.5.10 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
howett.net/plist v1.0.1 // indirect
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus-community/go-runit v0.1.0 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/prometheus/node_exporter v1.9.1
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.mongodb.org/mongo-driver v1.11.6 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/sync v0.12.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect
howett.net/plist v1.0.0 // indirect
)

125
go.sum
View File

@ -1,32 +1,35 @@
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU=
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dennwc/btrfs v0.0.0-20230312211831-a1f570bd01a1 h1:ue4Es4Xzz255hWQ7NAWzZxuXG+YOV7URzzusLLSe0zU=
github.com/dennwc/btrfs v0.0.0-20230312211831-a1f570bd01a1/go.mod h1:MYsOV9Dgsec3FFSOjywi0QK5r6TeBbdWxdrMGtiYXHA=
github.com/dennwc/btrfs v0.0.0-20240418142341-0167142bde7a h1:KfFsGLJFVdCXlySUkV2FmxNtmiztpJb6tV+XYBmmv8E=
github.com/dennwc/btrfs v0.0.0-20240418142341-0167142bde7a/go.mod h1:MYsOV9Dgsec3FFSOjywi0QK5r6TeBbdWxdrMGtiYXHA=
github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg=
github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/ema/qdisc v0.0.0-20230120214811-5b708f463de3 h1:Jrl8sD8wO34+EE1dV2vhOXrqFAZa/FILDnZRaV28+cw=
github.com/ema/qdisc v0.0.0-20230120214811-5b708f463de3/go.mod h1:FhIc0fLYi7f+lK5maMsesDqwYojIOh3VfRs8EVd5YJQ=
github.com/ema/qdisc v1.0.0 h1:EHLG08FVRbWLg8uRICa3xzC9Zm0m7HyMHfXobWFnXYg=
github.com/ema/qdisc v1.0.0/go.mod h1:FhIc0fLYi7f+lK5maMsesDqwYojIOh3VfRs8EVd5YJQ=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
@ -36,7 +39,6 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@ -46,6 +48,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY=
github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc=
github.com/hodgesds/perf-utils v0.7.0 h1:7KlHGMuig4FRH5fNw68PV6xLmgTe7jKs9hgAcEAbioU=
@ -55,65 +59,84 @@ github.com/illumos/go-kstat v0.0.0-20210513183136-173c9b0a9973/go.mod h1:PoK3ejP
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v1.3.2 h1:dcn0uWkfxycEEyNy0IGfx3GrhQ38LH7odjxAghimsVI=
github.com/jsimonetti/rtnetlink v1.3.2/go.mod h1:BBu4jZCpTjP6Gk0/wfrO8qcqymnN3g0hoFqObRmUo6U=
github.com/jsimonetti/rtnetlink/v2 v2.0.2 h1:ZKlbCujrIpp4/u3V2Ka0oxlf4BCkt6ojkvpy3nZoCBY=
github.com/jsimonetti/rtnetlink/v2 v2.0.2/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/iostat v1.2.1 h1:tnCdZBIglgxD47RyD55kfWQcJMGzO+1QBziSQfesf2k=
github.com/lufia/iostat v1.2.1/go.mod h1:rEPNA0xXgjHQjuI5Cy05sLlS2oRcSlWHRLrvh/AQ+Pg=
github.com/mattn/go-xmlrpc v0.0.3 h1:Y6WEMLEsqs3RviBrAa1/7qmbGB7DVD3brZIbqMbQdGY=
github.com/mattn/go-xmlrpc v0.0.3/go.mod h1:mqc2dz7tP5x5BKlCahN/n+hs7OSZKJkS9JsHNBRlrxA=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mdlayher/ethtool v0.0.0-20221212131811-ba3b4bc2e02c h1:Y7LoKqIgD7vmqJ7+6ZVnADuwUO+m3tGXbf2lK0OvjIw=
github.com/mdlayher/ethtool v0.0.0-20221212131811-ba3b4bc2e02c/go.mod h1:i0nPbE+sL2G3OtdIb9SXxW/T4UiAwh6rxPW7zcuX+KQ=
github.com/mdlayher/genetlink v1.3.1 h1:roBiPnual+eqtRkKX2Jb8UQN5ZPWnhDCGj/wR6Jlz2w=
github.com/mdlayher/genetlink v1.3.1/go.mod h1:uaIPxkWmGk753VVIzDtROxQ8+T+dkHqOI0vB1NA9S/Q=
github.com/mdlayher/ethtool v0.2.0 h1:akcA4WZVWozzirPASeMq8qgLkxpF3ykftVXwnrMKrhY=
github.com/mdlayher/ethtool v0.2.0/go.mod h1:W0pIBrNPK1TslIN4Z9wt1EVbay66Kbvek2z2f29VBfw=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mdlayher/wifi v0.0.0-20220330172155-a44c70b6d3c8 h1:/HCRFfpoICSWHvNrJ356VO4opd9dg/LaU7m8Tzdf39c=
github.com/mdlayher/wifi v0.0.0-20220330172155-a44c70b6d3c8/go.mod h1:IqdtNfemiXr50M8tnxLWSFdZKZ9vcI1Mgt0oTrCIS7A=
github.com/mdlayher/wifi v0.3.1 h1:bZDuMI1f7z5BtUUO3NgHRdR/R88YtywIe6dsEFI0Txs=
github.com/mdlayher/wifi v0.3.1/go.mod h1:ODQaObvsglghTuNhezD9grkTB4shVNc28aJfTXmvSi8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce h1:TqjP/BTDrwN7zP9xyXVuLsMBXYMt6LLYi55PlrIcq8U=
github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:ifHPsLndGGzvgzcaXUvzmt6LxKT4pJ+uzEhtnMt+f7A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8=
github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus-community/go-runit v0.1.0 h1:uTWEj/Fn2RoLdfg/etSqwzgYNOYPrARx1BHUN052tGA=
github.com/prometheus-community/go-runit v0.1.0/go.mod h1:AvJ9Jo3gAFu2lbM4+qfjdpq30FfiLDJZKbQ015u08IQ=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/node_exporter v1.6.1 h1:Srqr6UAOUDnKhurvYGIYa7GZXuMCwJpzT9KP8uTJ4vw=
github.com/prometheus/node_exporter v1.6.1/go.mod h1:+zK+m9vwxu19JHl/kVVmixdCT6fWWHlmcOUHDFpkt0Y=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/node_exporter v1.9.1 h1:5PIaixeIW9WYDAymngAK2Ucg3yHbnbG2Fz5VZuoMgj4=
github.com/prometheus/node_exporter v1.9.1/go.mod h1:g6tnkDIRSFw3/UI59KRExdfmqlkLK95qzpT3+wTXarE=
github.com/prometheus/procfs v0.15.2-0.20240603130017-1754b780536b h1:4EJkx3vycI+n5JY5ht+bnSUGamkmmXkpcNeO/OBT/0A=
github.com/prometheus/procfs v0.15.2-0.20240603130017-1754b780536b/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/siebenmann/go-kstat v0.0.0-20210513183136-173c9b0a9973 h1:GfSdC6wKfTGcgCS7BtzF5694Amne1pGCSTY252WhlEY=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/safchain/ethtool v0.5.10 h1:Im294gZtuf4pSGJRAOGKaASNi3wMeFaGaWuSaomedpc=
github.com/safchain/ethtool v0.5.10/go.mod h1:w9jh2Lx7YBR4UwzLkzCmWl85UY0W2uZdd7/DckVE5+c=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
@ -135,28 +158,38 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0=
@ -167,16 +200,22 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024 h1:WdvW4BJHoBwXqNsfEgOAZai7L9iHqRCZ7PZL0cwOULE=
repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024/go.mod h1:XA8+hQtUNh956T+kAbJKkUtMl5HUWj83knvdBvvPS5s=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250625075907-b6e187a0a747 h1:SJRRWoTKSj9hjzbEyKG55wW2YAHdxiT4cqLl1fJda9Y=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250625075907-b6e187a0a747/go.mod h1:q64I6gqlD61qwi9FfuPkwqy6Z6uzSHdcEjoHAJC27gQ=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250701095003-d77fa2108add h1:V5XUI79yK4KPukSWkxJWgx4PsiejQTjoufiglZh2m7I=
repositories.action2quare.com/ayo/gocommon v0.0.0-20250701095003-d77fa2108add/go.mod h1:q64I6gqlD61qwi9FfuPkwqy6Z6uzSHdcEjoHAJC27gQ=

View File

@ -1,37 +1,4 @@
#!/bin/bash
#!/bin/sh
HOUSTON_PATH="./houston"
UPDATE_DIR="houston.update"
nohup ./houston -client -logfile > /dev/null &
run_houston() {
if [ -f "$HOUSTON_PATH" ]; then
"$HOUSTON_PATH" "$@"
return $?
else
echo "houston 실행 파일이 없습니다."
return 1
fi
}
while true; do
echo "현재 버전의 houston을 실행합니다."
run_houston "$@"
# houston.update 폴더가 존재하는지 확인
if [ -d "$UPDATE_DIR" ]; then
echo "새로운 업데이트 폴더 발견. 업데이트를 진행합니다."
# houston.update 폴더 내의 모든 파일을 현재 폴더로 복사
cp -R "$UPDATE_DIR"/* .
# 실행 권한 부여 (필요한 경우)
chmod +x "$HOUSTON_PATH"
# 업데이트 폴더 삭제
rm -rf "$UPDATE_DIR"
echo "업데이트 완료 및 업데이트 폴더 삭제. houston을 다시 시작합니다."
else
echo "업데이트 폴더가 없습니다. 스크립트 실행을 종료합니다."
break
fi
done
echo "houston 실행 및 업데이트 스크립트 종료"
exit $?

View File

@ -6,7 +6,6 @@ import (
"context"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/houston/client"
@ -22,12 +21,20 @@ func main() {
panic(err)
}
http.Handle("/metrics", promhttp.Handler())
server := &http.Server{Addr: ":9100", Handler: nil}
http.Handle("/metrics", hc.MetricHandler())
server := &http.Server{Addr: ":9200", Handler: nil}
go func() {
logger.Println("listen /metrics")
server.ListenAndServe()
defer func() {
logger.Println("metric server shutdown")
r := recover()
if r != nil {
logger.Println(r)
}
}()
logger.Println("metric server start")
if err := server.ListenAndServe(); err != nil {
logger.Println("metric server cannot listen :", err)
}
}()
hc.Start()

View File

@ -5,12 +5,27 @@ del houston.zip
$Env:GOOS="linux"
$Env:GOARCH="amd64"
go get repositories.action2quare.com/ayo/gocommon
go mod tidy
# go get repositories.action2quare.com/ayo/gocommon
# go mod tidy
go build -ldflags="-s -w" -tags=client .
cp houston .\replacer\houston
cp config.json .\replacer\config.json
cp houston.sh .\replacer\houston.sh
cd replacer
go build -ldflags="-s -w" .
Compress-Archive -Path replacer -DestinationPath houston.zip -Force
Compress-Archive -Path config.json -Update -DestinationPath houston.zip
Compress-Archive -Path houston -Update -DestinationPath houston.zip
Compress-Archive -Path houston.sh -Update -DestinationPath houston.zip
del houston
del config.json
del replacer
del houston.sh
mv houston.zip ..\houston.zip
cd ..

113
replacer/main.go Normal file
View File

@ -0,0 +1,113 @@
package main
import (
"encoding/json"
"errors"
"io"
"log"
"os"
"os/exec"
"path"
"time"
)
func copy(src, dst string, stdlog *log.Logger) error {
fi, err := os.Stat(src)
if err != nil {
return err
}
if fi.IsDir() {
entries, _ := os.ReadDir(src)
for _, ent := range entries {
if err := copy(path.Join(src, ent.Name()), path.Join(dst, ent.Name()), stdlog); err != nil {
return err
}
}
return nil
}
inmode := fi.Mode()
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
copied, err := io.Copy(out, in)
if err != nil {
return err
}
if copied < fi.Size() {
return errors.New("copy not completed")
}
if err := out.Sync(); err != nil {
return err
}
if err := out.Chmod(inmode); err != nil {
return err
}
stdlog.Println("file copied :", src, dst)
return nil
}
func main() {
logfile, _ := os.OpenFile("replacer.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
defer logfile.Close()
stdlog := log.New(logfile, "", log.LstdFlags)
args := os.Args
// args[1] : 나를 시작한 pid. pid가 종료될 때 까지 기다림
// args[2] : target 폴더
// args[3:] : 다시 시작할 때 넘겨줄 arguments(프로세스 이름 포함)
stdlog.Println(args)
for {
stdlog.Println("wait for terminating of", args[3])
cmd := exec.Command("ps", "-p", args[1])
if err := cmd.Run(); err != nil {
break
}
time.Sleep(time.Second)
}
stdlog.Println("target is terminated")
// replacer 제거. 내가 돌고 있으므로 복사는 안된다.
// 내가 실행되기 전에 이미 복사가 되서 나는 최신 버전임
os.Remove(path.Join(args[2], os.Args[0]))
if err := copy(args[2], "", stdlog); err != nil {
stdlog.Fatal(err)
}
nextArgs := args[4:]
if bt, _ := os.ReadFile("@args"); len(bt) > 0 {
var tempArgs []string
if json.Unmarshal(bt, &tempArgs) == nil {
nextArgs = tempArgs
}
}
os.Remove("@args")
err := os.RemoveAll(args[2])
if err != nil {
stdlog.Println("os.RemoveAll failed :", args[2], err)
}
err = os.Chmod(args[3], 0775)
if err != nil {
stdlog.Println("os.Chmod failed :", err)
}
stdlog.Println("exec.Command :", args)
cmd := exec.Command(args[3], nextArgs...)
cmd.Start()
}

View File

@ -29,8 +29,6 @@ const (
)
func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) {
logger.Println("GetAgents")
enc := json.NewEncoder(w)
allHosts := h.Operation().Hosts()
enc.Encode(allHosts)
@ -47,8 +45,6 @@ func readTagsFromFile(paths ...string) string {
}
func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request) {
logger.Println("GetDeploySources")
files, err := os.ReadDir(h.deployPath)
if err != nil {
logger.Println(err)
@ -104,7 +100,6 @@ func (h *houstonHandler) UploadDeploySource(w http.ResponseWriter, r *http.Reque
// <input type="submit" value="업로드">
// </form>
file, header, err := r.FormFile("file")
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
@ -123,8 +118,6 @@ func (h *houstonHandler) UploadDeploySource(w http.ResponseWriter, r *http.Reque
name := r.FormValue("name")
ext := path.Ext(header.Filename)
logger.Println("UploadDeploySource :", name, version)
var filename string
if version == "config" {
@ -161,7 +154,6 @@ func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Reque
version := r.FormValue("version")
name := r.FormValue("name")
logger.Println("DeleteDeploySource :", name, version)
if len(version) == 0 || len(name) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
@ -169,8 +161,6 @@ func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Reque
// deploys 폴더는 파일시스템 서비스이므로 다운로드 가능
targetpath := path.Join(h.deployPath, name, version)
logger.Println("DeleteDeploySource :", name, version, targetpath)
if err := os.RemoveAll(targetpath); err != nil {
logger.Println("deleteDeploySource failed :", err)
w.WriteHeader(http.StatusInternalServerError)
@ -230,8 +220,6 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
version := r.FormValue("version")
traws := r.FormValue("targets")
logger.Println("Deploy :", name, version, traws)
var targets []string
if len(traws) > 0 {
if err := json.Unmarshal([]byte(traws), &targets); err != nil {
@ -276,15 +264,11 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
return
}
var configPath string
if name != "houston" {
// houston은 config를 포함하여 배포
configPath, err = h.findLastestConfigFile(name)
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
configPath, err := h.findLastestConfigFile(name)
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
h.Operation().Deploy(MakeDeployRequest(
@ -308,8 +292,6 @@ func (h *houstonHandler) Undeploy(w http.ResponseWriter, r *http.Request) {
version := r.FormValue("version")
traws := r.FormValue("targets")
logger.Println("Undeploy :", name, version, traws)
var targets []string
if len(traws) > 0 {
if err := json.Unmarshal([]byte(traws), &targets); err != nil {
@ -346,8 +328,6 @@ func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) {
argsline := r.FormValue("args")
traws := r.FormValue("targets")
logger.Println("StartProcess :", name, version, argsline, traws)
var targets []string
if len(traws) > 0 {
if err := json.Unmarshal([]byte(traws), &targets); err != nil {
@ -400,8 +380,6 @@ func (h *houstonHandler) StopProcess(w http.ResponseWriter, r *http.Request) {
// <input type="submit" value="업로드">
// </form>
name := r.FormValue("name")
logger.Println("StopProcess :", name)
if len(name) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
@ -446,8 +424,6 @@ func (h *houstonHandler) RestartProcess(w http.ResponseWriter, r *http.Request)
pidstr := r.FormValue("pid")
target := r.FormValue("target")
name := r.FormValue("name")
logger.Println("RestartProcess :", name, pidstr, target)
if len(target) == 0 || len(pidstr) == 0 || len(name) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
@ -492,8 +468,6 @@ func (h *houstonHandler) UploadLogs(w http.ResponseWriter, r *http.Request) {
// <input type="submit" value="업로드">
// </form>
name := r.FormValue("name")
logger.Println("UploadLogs :", name)
if len(name) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
@ -527,8 +501,6 @@ func (h *houstonHandler) GetLogFileLinks(w http.ResponseWriter, r *http.Request)
// </form>
name := r.FormValue("name")
version := r.FormValue("version")
logger.Println("GetLogFileLinks :", name, version)
if len(name) == 0 || len(version) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
@ -550,6 +522,34 @@ func (h *houstonHandler) GetLogFileLinks(w http.ResponseWriter, r *http.Request)
enc.Encode(out)
}
func (h *houstonHandler) GetDemoFileLink(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name">
// <input type="text" name="version">
// <input type="text" name="filename">
// </form>
name := r.FormValue("name")
version := r.FormValue("version")
fileName := r.FormValue("filename")
logger.Println("GetDemoFileLink :", name, version, fileName)
if len(name) == 0 || len(version) == 0 || len(fileName) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
demoFilePath := path.Join(h.downloadPath, name, version, fileName)
demoFileInfo, err := os.Stat(demoFilePath)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
out := []string{path.Join(sub_folder_name_downloads, name, version, demoFileInfo.Name())}
enc := json.NewEncoder(w)
enc.Encode(out)
}
func (h *houstonHandler) GetDeployingProgress(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(h.Operation().DeplyingProgress())
}

View File

@ -14,6 +14,7 @@ import (
"strings"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
)
@ -24,13 +25,12 @@ type HoustonServerWithHandler interface {
type houstonHandler struct {
HoustonServer
methods map[string]reflect.Method
deployPath string
downloadPath string
maingateApiToken string
methods map[string]reflect.Method
deployPath string
downloadPath string
}
func NewHoustonHandler(apiToken string) HoustonServerWithHandler {
func NewHoustonHandler() HoustonServerWithHandler {
var tmp *houstonHandler
methods := make(map[string]reflect.Method)
@ -40,9 +40,8 @@ func NewHoustonHandler(apiToken string) HoustonServerWithHandler {
methods[strings.ToLower(method.Name)] = method
}
return &houstonHandler{
HoustonServer: NewServer(),
methods: methods,
maingateApiToken: apiToken,
HoustonServer: NewServer(),
methods: methods,
}
}
@ -127,7 +126,8 @@ func (h *houstonHandler) RegisterHandlers(serveMux gocommon.ServerMuxInterface,
defer func() {
s := recover()
if s != nil {
logger.Error(s)
logger.Println(s)
debug.PrintStack()
}
io.Copy(io.Discard, r.Body)
r.Body.Close()
@ -138,12 +138,10 @@ func (h *houstonHandler) RegisterHandlers(serveMux gocommon.ServerMuxInterface,
filename := r.Header.Get("Houston-Service-Filename")
dir := path.Join(h.downloadPath, name, version)
if err := os.MkdirAll(dir, 0775); err == nil {
filepath := path.Join(dir, filename)
// filepath가 이미 있으면 append
localfile, _ := os.OpenFile(filepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if localfile != nil {
defer localfile.Close()
if _, err = io.Copy(localfile, r.Body); err != nil {
file, _ := os.Create(path.Join(dir, filename))
if file != nil {
defer file.Close()
if _, err = io.Copy(file, r.Body); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
} else {
@ -157,6 +155,9 @@ func (h *houstonHandler) RegisterHandlers(serveMux gocommon.ServerMuxInterface,
return nil
}
var noauth = flagx.Bool("noauth", false, "")
var authtype = flagx.String("auth", "on", "on|off|both")
func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
@ -171,6 +172,38 @@ func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Body.Close()
}()
// var userinfo map[string]any
// if !*noauth && (*authtype == "on" || *authtype == "both") {
// authheader := r.Header.Get("Authorization")
// if len(authheader) == 0 {
// logger.Println("Authorization header is not valid :", authheader)
// w.WriteHeader(http.StatusBadRequest)
// return
// }
// req, _ := http.NewRequest("GET", "https://graph.microsoft.com/oidc/userinfo", nil)
// req.Header.Add("Authorization", authheader)
// client := &http.Client{}
// resp, err := client.Do(req)
// if err != nil {
// logger.Println("graph microsoft api call failed :", err)
// w.WriteHeader(http.StatusBadRequest)
// return
// }
// defer resp.Body.Close()
// raw, _ := io.ReadAll(resp.Body)
// if err = json.Unmarshal(raw, &userinfo); err != nil {
// return
// }
// if _, expired := userinfo["error"]; expired {
// w.WriteHeader(http.StatusUnauthorized)
// return
// }
// }
var operation string
if r.Method == "POST" {
operation = r.FormValue("operation")

View File

@ -5,7 +5,6 @@ import (
"net"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/houston/shared"
"repositories.action2quare.com/ayo/houston/shared/protos"
@ -109,14 +108,11 @@ type Operation interface {
}
type outerconfig struct {
Houston struct {
Houston *struct {
Server serverConfig `json:"server"`
} `json:"houston"`
}
var storagePath = flagx.String("hs_storage", "", "")
var grpcPort = flagx.Int("hs_grpc_port", 0, "")
func loadServerConfig() serverConfig {
var oc outerconfig
err := gocommon.LoadConfig[outerconfig](&oc)
@ -127,15 +123,6 @@ func loadServerConfig() serverConfig {
}
}
if len(*storagePath) > 0 {
// override
oc.Houston.Server.StorageRoot = *storagePath
}
if *grpcPort != 0 {
oc.Houston.Server.GrpcPort = *grpcPort
}
return oc.Houston.Server
}

View File

@ -27,11 +27,9 @@ type WithdrawRequest struct {
}
type StartProcessRequest struct {
Name string
Version string
Args []string
AutoRestart bool
OutputLogFile string
Name string
Version string
Args []string
}
type StopProcessRequest struct {