repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
e05304b
parent
5359de0
author
Antonio Mika
date
2024-10-03 20:37:21 +0000 UTC
Setup a send log sink
4 files changed,  +202, -1
M go.mod
M go.sum
M go.mod
+2, -0
 1@@ -40,6 +40,7 @@ require (
 2 	github.com/picosh/send v0.0.0-20240820031602-5d3b1a4494cc
 3 	github.com/picosh/tunkit v0.0.0-20240709033345-8315d4f3cd0e
 4 	github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
 5+	github.com/samber/slog-multi v1.2.3
 6 	github.com/sendgrid/sendgrid-go v3.14.0+incompatible
 7 	github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d
 8 	github.com/x-way/crawlerdetect v0.2.21
 9@@ -133,6 +134,7 @@ require (
10 	github.com/rogpeppe/go-internal v1.11.0 // indirect
11 	github.com/rs/xid v1.5.0 // indirect
12 	github.com/safchain/ethtool v0.3.0 // indirect
13+	github.com/samber/lo v1.47.0 // indirect
14 	github.com/secure-io/sio-go v0.3.1 // indirect
15 	github.com/sendgrid/rest v2.6.9+incompatible // indirect
16 	github.com/shirou/gopsutil/v3 v3.24.5 // indirect
M go.sum
+4, -0
 1@@ -261,6 +261,10 @@ github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDj
 2 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
 3 github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
 4 github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
 5+github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
 6+github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
 7+github.com/samber/slog-multi v1.2.3 h1:np8YoAZbGP699xA92SYZxs7zzKpL1/yBYk6q8/caXpc=
 8+github.com/samber/slog-multi v1.2.3/go.mod h1:ACuZ5B6heK57TfMVkVknN2UZHoFfjCwRxR0Q2OXKHlo=
 9 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
10 github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
11 github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
M shared/config.go
+7, -1
 1@@ -271,7 +271,13 @@ func CreateLogger(space string) *slog.Logger {
 2 	log := slog.New(
 3 		slog.NewTextHandler(os.Stdout, opts),
 4 	)
 5-	return log.With("service", space)
 6+
 7+	newLog, err := SendLogRegister(log, 100)
 8+	if err != nil {
 9+		slog.Error("unable to start send logger", "error", err)
10+	}
11+
12+	return newLog.With("service", space)
13 }
14 
15 func LoggerWithUser(logger *slog.Logger, user *db.User) *slog.Logger {
A shared/sendlog.go
+189, -0
  1@@ -0,0 +1,189 @@
  2+package shared
  3+
  4+import (
  5+	"fmt"
  6+	"io"
  7+	"log/slog"
  8+	"net"
  9+	"os"
 10+	"path/filepath"
 11+	"strings"
 12+	"sync"
 13+	"time"
 14+
 15+	slogmulti "github.com/samber/slog-multi"
 16+	"golang.org/x/crypto/ssh"
 17+)
 18+
 19+type SendLogWriter struct {
 20+	SSHClient *ssh.Client
 21+	Session   *ssh.Session
 22+	StdinPipe io.Writer
 23+	Done      chan struct{}
 24+	Messages  chan []byte
 25+	Timeout   time.Duration
 26+	closeOnce sync.Once
 27+	startOnce sync.Once
 28+}
 29+
 30+func (c *SendLogWriter) Write(data []byte) (int, error) {
 31+	var (
 32+		n   int
 33+		err error
 34+	)
 35+
 36+	select {
 37+	case c.Messages <- data:
 38+		n = len(data)
 39+	case <-time.After(c.Timeout):
 40+		err = fmt.Errorf("unable to send data within timeout")
 41+	}
 42+
 43+	return n, err
 44+}
 45+
 46+func (c *SendLogWriter) Open() {
 47+	go func() {
 48+		for {
 49+			select {
 50+			case data := <-c.Messages:
 51+				_, err := c.StdinPipe.Write(data)
 52+				if err != nil {
 53+					slog.Info("received error on write", "error", err)
 54+				}
 55+			case <-c.Done:
 56+				return
 57+			}
 58+		}
 59+	}()
 60+}
 61+
 62+func createSSHClient(remoteHost string, keyLocation string, keyPassphrase string, remoteHostname string, remoteUser string) *ssh.Client {
 63+	if !strings.Contains(remoteHost, ":") {
 64+		remoteHost += ":22"
 65+	}
 66+
 67+	rawConn, err := net.Dial("tcp", remoteHost)
 68+	if err != nil {
 69+		slog.Error(
 70+			"Unable to create ssh client, tcp connection not established",
 71+			slog.Any("error", err),
 72+		)
 73+		panic(err)
 74+	}
 75+
 76+	keyPath, err := filepath.Abs(keyLocation)
 77+	if err != nil {
 78+		slog.Error(
 79+			"Unable to create ssh client, cannot find key file",
 80+			slog.Any("error", err),
 81+		)
 82+		panic(err)
 83+	}
 84+
 85+	f, err := os.Open(keyPath)
 86+	if err != nil {
 87+		slog.Error(
 88+			"Unable to create ssh client, unable to open key",
 89+			slog.Any("error", err),
 90+		)
 91+		panic(err)
 92+	}
 93+	defer f.Close()
 94+
 95+	data, err := io.ReadAll(f)
 96+	if err != nil {
 97+		slog.Error(
 98+			"Unable to create ssh client, unable to read key",
 99+			slog.Any("error", err),
100+		)
101+		panic(err)
102+	}
103+
104+	var signer ssh.Signer
105+
106+	if keyPassphrase != "" {
107+		signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(keyPassphrase))
108+	} else {
109+		signer, err = ssh.ParsePrivateKey(data)
110+	}
111+
112+	if err != nil {
113+		slog.Error(
114+			"Unable to create ssh client, unable to parse key",
115+			slog.Any("error", err),
116+		)
117+		panic(err)
118+	}
119+
120+	sshConn, chans, reqs, err := ssh.NewClientConn(rawConn, remoteHostname, &ssh.ClientConfig{
121+		Auth:            []ssh.AuthMethod{ssh.PublicKeys(signer)},
122+		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
123+		User:            remoteUser,
124+	})
125+	if err != nil {
126+		slog.Error(
127+			"Unable to create ssh client, unable to create client conn",
128+			slog.Any("error", err),
129+		)
130+		panic(err)
131+	}
132+
133+	sshClient := ssh.NewClient(sshConn, chans, reqs)
134+
135+	return sshClient
136+}
137+
138+func SendLogRegister(logger *slog.Logger, buffer int) (*slog.Logger, error) {
139+	if buffer < 0 {
140+		buffer = 0
141+	}
142+
143+	currentHandler := logger.Handler()
144+
145+	sshClient := createSSHClient(
146+		"send.pico.sh:22",
147+		os.Getenv("SSH_KEY"),
148+		os.Getenv("SSH_PASSPHRASE"),
149+		"send.pico.sh",
150+		os.Getenv("SSH_USER"),
151+	)
152+
153+	sesh, err := sshClient.NewSession()
154+	if err != nil {
155+		return logger, nil
156+	}
157+
158+	stdinPipe, err := sesh.StdinPipe()
159+	if err != nil {
160+		return logger, nil
161+	}
162+
163+	err = sesh.Start("pub log-sink -b=false")
164+	if err != nil {
165+		return logger, nil
166+	}
167+
168+	logWriter := &SendLogWriter{
169+		SSHClient: sshClient,
170+		Session:   sesh,
171+		StdinPipe: stdinPipe,
172+		Done:      make(chan struct{}),
173+		Messages:  make(chan []byte, buffer),
174+		Timeout:   10 * time.Millisecond,
175+	}
176+
177+	logWriter.Open()
178+
179+	return slog.New(
180+		slogmulti.Fanout(
181+			currentHandler,
182+			slog.NewJSONHandler(logWriter, &slog.HandlerOptions{
183+				AddSource: true,
184+				Level:     slog.LevelDebug,
185+			}),
186+		),
187+	), nil
188+}
189+
190+var _ io.Writer = (*SendLogWriter)(nil)