- commit
- 7368dc3
- parent
- ec956d2
- author
- Antonio Mika
- date
- 2025-03-12 19:02:15 -0400 EDT
Single ssh server generator and prometheus support
8 files changed,
+243,
-131
+3,
-3
1@@ -54,8 +54,8 @@ ARG APP=prose
2
3 COPY --from=builder-web /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
4 COPY --from=builder-web /go/bin/${APP}-web ./web
5-COPY --from=builder-web /app/${APP}/html ./${APP}/html
6-COPY --from=builder-web /app/${APP}/public ./${APP}/public
7+COPY --from=builder-web /app/pkg/apps/${APP}/html ./${APP}/html
8+COPY --from=builder-web /app/pkg/apps/${APP}/public ./${APP}/public
9
10 ENTRYPOINT ["/app/web"]
11
12@@ -69,7 +69,7 @@ ARG APP=prose
13 COPY --from=builder-ssh /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
14 COPY --from=builder-ssh /go/bin/${APP}-ssh ./ssh
15 # some services require the html folder
16-COPY --from=builder-ssh /app/${APP}/html ./${APP}/html
17+COPY --from=builder-ssh /app/pkg/apps/${APP}/html ./${APP}/html
18
19
20 ENTRYPOINT ["/app/ssh"]
+18,
-23
1@@ -17,13 +17,12 @@ import (
2 "github.com/picosh/pico/pkg/send/protocols/sftp"
3 "github.com/picosh/pico/pkg/shared"
4 "github.com/picosh/utils"
5- "golang.org/x/crypto/ssh"
6 )
7
8 func StartSshServer() {
9- host := utils.GetEnv("LISTS_HOST", "0.0.0.0")
10- port := utils.GetEnv("LISTS_SSH_PORT", "2222")
11- // promPort := utils.GetEnv("LISTS_PROM_PORT", "9222")
12+ host := utils.GetEnv("FEEDS_HOST", "0.0.0.0")
13+ port := utils.GetEnv("FEEDS_SSH_PORT", "2222")
14+ promPort := utils.GetEnv("FEEDS_PROM_PORT", "9222")
15 cfg := NewConfigSite("feeds-ssh")
16 logger := cfg.Logger
17
18@@ -44,12 +43,16 @@ func StartSshServer() {
19 handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
20
21 sshAuth := shared.NewSshAuthHandler(dbh, logger)
22- server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
23- ListenAddr: "localhost:2222",
24- ServerConfig: &ssh.ServerConfig{
25- PublicKeyCallback: sshAuth.PubkeyAuthHandler,
26- },
27- Middleware: []pssh.SSHServerMiddleware{
28+
29+ // Create a new SSH server
30+ server, err := pssh.NewSSHServerWithConfig(
31+ ctx,
32+ logger,
33+ host,
34+ port,
35+ promPort,
36+ sshAuth.PubkeyAuthHandler,
37+ []pssh.SSHServerMiddleware{
38 pipe.Middleware(handler, ".txt"),
39 list.Middleware(handler),
40 scp.Middleware(handler),
41@@ -58,26 +61,18 @@ func StartSshServer() {
42 pssh.PtyMdw(pssh.DeprecatedNotice()),
43 pssh.LogMiddleware(handler, dbh),
44 },
45- SubsystemMiddleware: []pssh.SSHServerMiddleware{
46+ []pssh.SSHServerMiddleware{
47 sftp.Middleware(handler),
48 pssh.LogMiddleware(handler, dbh),
49 },
50- })
51-
52- pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
53- if err != nil {
54- logger.Error("failed to read private key file", "error", err)
55- return
56- }
57+ nil,
58+ )
59
60- signer, err := ssh.ParsePrivateKey(pemBytes)
61 if err != nil {
62- logger.Error("failed to parse private key", "error", err)
63- return
64+ logger.Error("failed to create ssh server", "err", err.Error())
65+ os.Exit(1)
66 }
67
68- server.Config.AddHostKey(signer)
69-
70 done := make(chan os.Signal, 1)
71
72 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+16,
-21
1@@ -17,13 +17,12 @@ import (
2 "github.com/picosh/pico/pkg/send/protocols/sftp"
3 "github.com/picosh/pico/pkg/shared"
4 "github.com/picosh/utils"
5- "golang.org/x/crypto/ssh"
6 )
7
8 func StartSshServer() {
9 host := utils.GetEnv("PASTES_HOST", "0.0.0.0")
10 port := utils.GetEnv("PASTES_SSH_PORT", "2222")
11- // promPort := utils.GetEnv("PASTES_PROM_PORT", "9222")
12+ promPort := utils.GetEnv("PASTES_PROM_PORT", "9222")
13 cfg := NewConfigSite("pastes-ssh")
14 logger := cfg.Logger
15
16@@ -42,12 +41,16 @@ func StartSshServer() {
17 }
18 handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
19 sshAuth := shared.NewSshAuthHandler(dbh, logger)
20- server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
21- ListenAddr: "localhost:2222",
22- ServerConfig: &ssh.ServerConfig{
23- PublicKeyCallback: sshAuth.PubkeyAuthHandler,
24- },
25- Middleware: []pssh.SSHServerMiddleware{
26+
27+ // Create a new SSH server
28+ server, err := pssh.NewSSHServerWithConfig(
29+ ctx,
30+ logger,
31+ host,
32+ port,
33+ promPort,
34+ sshAuth.PubkeyAuthHandler,
35+ []pssh.SSHServerMiddleware{
36 pipe.Middleware(handler, ""),
37 list.Middleware(handler),
38 scp.Middleware(handler),
39@@ -56,26 +59,18 @@ func StartSshServer() {
40 pssh.PtyMdw(pssh.DeprecatedNotice()),
41 pssh.LogMiddleware(handler, dbh),
42 },
43- SubsystemMiddleware: []pssh.SSHServerMiddleware{
44+ []pssh.SSHServerMiddleware{
45 sftp.Middleware(handler),
46 pssh.LogMiddleware(handler, dbh),
47 },
48- })
49-
50- pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
51- if err != nil {
52- logger.Error("failed to read private key file", "error", err)
53- return
54- }
55+ nil,
56+ )
57
58- signer, err := ssh.ParsePrivateKey(pemBytes)
59 if err != nil {
60- logger.Error("failed to parse private key", "error", err)
61- return
62+ logger.Error("failed to create ssh server", "err", err.Error())
63+ os.Exit(1)
64 }
65
66- server.Config.AddHostKey(signer)
67-
68 done := make(chan os.Signal, 1)
69 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
70 logger.Info("Starting SSH server", "host", host, "port", port)
+14,
-22
1@@ -16,13 +16,12 @@ import (
2 "github.com/picosh/pico/pkg/shared"
3 "github.com/picosh/pico/pkg/tunkit"
4 "github.com/picosh/utils"
5- "golang.org/x/crypto/ssh"
6 )
7
8 func StartSshServer(cfg *PgsConfig, killCh chan error) {
9 host := utils.GetEnv("PGS_HOST", "0.0.0.0")
10 port := utils.GetEnv("PGS_SSH_PORT", "2222")
11- // promPort := utils.GetEnv("PGS_PROM_PORT", "9222")
12+ promPort := utils.GetEnv("PGS_PROM_PORT", "9222")
13 logger := cfg.Logger
14
15 ctx, cancel := context.WithCancel(context.Background())
16@@ -43,12 +42,14 @@ func StartSshServer(cfg *PgsConfig, killCh chan error) {
17 }
18
19 // Create a new SSH server
20- server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
21- ListenAddr: "localhost:2222",
22- ServerConfig: &ssh.ServerConfig{
23- PublicKeyCallback: sshAuth.PubkeyAuthHandler,
24- },
25- Middleware: []pssh.SSHServerMiddleware{
26+ server, err := pssh.NewSSHServerWithConfig(
27+ ctx,
28+ logger,
29+ host,
30+ port,
31+ promPort,
32+ sshAuth.PubkeyAuthHandler,
33+ []pssh.SSHServerMiddleware{
34 pipe.Middleware(handler, ""),
35 list.Middleware(handler),
36 scp.Middleware(handler),
37@@ -58,29 +59,20 @@ func StartSshServer(cfg *PgsConfig, killCh chan error) {
38 Middleware(handler),
39 pssh.LogMiddleware(handler, handler.Cfg.DB),
40 },
41- SubsystemMiddleware: []pssh.SSHServerMiddleware{
42+ []pssh.SSHServerMiddleware{
43 sftp.Middleware(handler),
44 pssh.LogMiddleware(handler, handler.Cfg.DB),
45 },
46- ChannelMiddleware: map[string]pssh.SSHServerChannelMiddleware{
47+ map[string]pssh.SSHServerChannelMiddleware{
48 "direct-tcpip": tunkit.LocalForwardHandler(webTunnel),
49 },
50- })
51-
52- pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
53- if err != nil {
54- logger.Error("failed to read private key file", "error", err)
55- return
56- }
57+ )
58
59- signer, err := ssh.ParsePrivateKey(pemBytes)
60 if err != nil {
61- logger.Error("failed to parse private key", "error", err)
62- return
63+ logger.Error("failed to create ssh server", "err", err.Error())
64+ os.Exit(1)
65 }
66
67- server.Config.AddHostKey(signer)
68-
69 done := make(chan os.Signal, 1)
70 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
71 logger.Info("starting SSH server on", "host", host, "port", port)
+16,
-21
1@@ -18,7 +18,6 @@ import (
2 "github.com/picosh/pico/pkg/shared"
3 "github.com/picosh/pico/pkg/tui"
4 "github.com/picosh/utils"
5- "golang.org/x/crypto/ssh"
6 )
7
8 func createTui(shrd *tui.SharedModel) pssh.SSHServerMiddleware {
9@@ -40,7 +39,7 @@ func createTui(shrd *tui.SharedModel) pssh.SSHServerMiddleware {
10 func StartSshServer() {
11 host := utils.GetEnv("PICO_HOST", "0.0.0.0")
12 port := utils.GetEnv("PICO_SSH_PORT", "2222")
13- // promPort := utils.GetEnv("PICO_PROM_PORT", "9222")
14+ promPort := utils.GetEnv("PICO_PROM_PORT", "9222")
15 cfg := NewConfigSite("pico-ssh")
16 logger := cfg.Logger
17
18@@ -61,12 +60,16 @@ func StartSshServer() {
19 }
20
21 sshAuth := shared.NewSshAuthHandler(dbpool, logger)
22- server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
23- ListenAddr: "localhost:2222",
24- ServerConfig: &ssh.ServerConfig{
25- PublicKeyCallback: sshAuth.PubkeyAuthHandler,
26- },
27- Middleware: []pssh.SSHServerMiddleware{
28+
29+ // Create a new SSH server
30+ server, err := pssh.NewSSHServerWithConfig(
31+ ctx,
32+ logger,
33+ host,
34+ port,
35+ promPort,
36+ sshAuth.PubkeyAuthHandler,
37+ []pssh.SSHServerMiddleware{
38 pipe.Middleware(handler, ""),
39 list.Middleware(handler),
40 scp.Middleware(handler),
41@@ -86,26 +89,18 @@ func StartSshServer() {
42 WishMiddleware(cliHandler),
43 pssh.LogMiddleware(handler, dbpool),
44 },
45- SubsystemMiddleware: []pssh.SSHServerMiddleware{
46+ []pssh.SSHServerMiddleware{
47 sftp.Middleware(handler),
48 pssh.LogMiddleware(handler, dbpool),
49 },
50- })
51-
52- pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
53- if err != nil {
54- logger.Error("failed to read private key file", "error", err)
55- return
56- }
57+ nil,
58+ )
59
60- signer, err := ssh.ParsePrivateKey(pemBytes)
61 if err != nil {
62- logger.Error("failed to parse private key", "error", err)
63- return
64+ logger.Error("failed to create ssh server", "err", err.Error())
65+ os.Exit(1)
66 }
67
68- server.Config.AddHostKey(signer)
69-
70 done := make(chan os.Signal, 1)
71 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
72 logger.Info("starting SSH server on", "host", host, "port", port)
+16,
-20
1@@ -12,14 +12,13 @@ import (
2 "github.com/picosh/pico/pkg/shared"
3 psub "github.com/picosh/pubsub"
4 "github.com/picosh/utils"
5- "golang.org/x/crypto/ssh"
6 )
7
8 func StartSshServer() {
9 host := utils.GetEnv("PIPE_HOST", "0.0.0.0")
10 port := utils.GetEnv("PIPE_SSH_PORT", "2222")
11 portOverride := utils.GetEnv("PIPE_SSH_PORT_OVERRIDE", port)
12- // promPort := utils.GetEnv("PIPE_PROM_PORT", "9222")
13+ promPort := utils.GetEnv("PIPE_PROM_PORT", "9222")
14 cfg := NewConfigSite("pipe-ssh")
15 logger := cfg.Logger
16
17@@ -43,31 +42,28 @@ func StartSshServer() {
18 }
19
20 sshAuth := shared.NewSshAuthHandler(dbh, logger)
21- server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
22- ListenAddr: "localhost:2222",
23- ServerConfig: &ssh.ServerConfig{
24- PublicKeyCallback: sshAuth.PubkeyAuthHandler,
25- },
26- Middleware: []pssh.SSHServerMiddleware{
27+
28+ // Create a new SSH server
29+ server, err := pssh.NewSSHServerWithConfig(
30+ ctx,
31+ logger,
32+ host,
33+ port,
34+ promPort,
35+ sshAuth.PubkeyAuthHandler,
36+ []pssh.SSHServerMiddleware{
37 WishMiddleware(handler),
38 pssh.LogMiddleware(handler, dbh),
39 },
40- })
41-
42- pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
43- if err != nil {
44- logger.Error("failed to read private key file", "error", err)
45- return
46- }
47+ nil,
48+ nil,
49+ )
50
51- signer, err := ssh.ParsePrivateKey(pemBytes)
52 if err != nil {
53- logger.Error("failed to parse private key", "error", err)
54- return
55+ logger.Error("failed to create ssh server", "err", err.Error())
56+ os.Exit(1)
57 }
58
59- server.Config.AddHostKey(signer)
60-
61 done := make(chan os.Signal, 1)
62 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
63 logger.Info("Starting SSH server", "host", host, "port", port)
+16,
-21
1@@ -19,13 +19,12 @@ import (
2 "github.com/picosh/pico/pkg/shared"
3 "github.com/picosh/pico/pkg/shared/storage"
4 "github.com/picosh/utils"
5- "golang.org/x/crypto/ssh"
6 )
7
8 func StartSshServer() {
9 host := utils.GetEnv("PROSE_HOST", "0.0.0.0")
10 port := utils.GetEnv("PROSE_SSH_PORT", "2222")
11- // promPort := utils.GetEnv("PROSE_PROM_PORT", "9222")
12+ promPort := utils.GetEnv("PROSE_PROM_PORT", "9222")
13 cfg := NewConfigSite("prose-ssh")
14 logger := cfg.Logger
15
16@@ -61,12 +60,16 @@ func StartSshServer() {
17 handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
18
19 sshAuth := shared.NewSshAuthHandler(dbh, logger)
20- server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{
21- ListenAddr: "localhost:2222",
22- ServerConfig: &ssh.ServerConfig{
23- PublicKeyCallback: sshAuth.PubkeyAuthHandler,
24- },
25- Middleware: []pssh.SSHServerMiddleware{
26+
27+ // Create a new SSH server
28+ server, err := pssh.NewSSHServerWithConfig(
29+ ctx,
30+ logger,
31+ host,
32+ port,
33+ promPort,
34+ sshAuth.PubkeyAuthHandler,
35+ []pssh.SSHServerMiddleware{
36 pipe.Middleware(handler, ".md"),
37 list.Middleware(handler),
38 scp.Middleware(handler),
39@@ -75,26 +78,18 @@ func StartSshServer() {
40 pssh.PtyMdw(pssh.DeprecatedNotice()),
41 pssh.LogMiddleware(handler, dbh),
42 },
43- SubsystemMiddleware: []pssh.SSHServerMiddleware{
44+ []pssh.SSHServerMiddleware{
45 sftp.Middleware(handler),
46 pssh.LogMiddleware(handler, dbh),
47 },
48- })
49-
50- pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
51- if err != nil {
52- logger.Error("failed to read private key file", "error", err)
53- return
54- }
55+ nil,
56+ )
57
58- signer, err := ssh.ParsePrivateKey(pemBytes)
59 if err != nil {
60- logger.Error("failed to parse private key", "error", err)
61- return
62+ logger.Error("failed to create ssh server", "err", err.Error())
63+ os.Exit(1)
64 }
65
66- server.Config.AddHostKey(signer)
67-
68 done := make(chan os.Signal, 1)
69 signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
70 logger.Info("Starting SSH server", "host", host, "port", port)
+144,
-0
1@@ -2,16 +2,24 @@ package pssh
2
3 import (
4 "context"
5+ "crypto/ed25519"
6+ "crypto/rand"
7 "crypto/subtle"
8+ "encoding/pem"
9 "errors"
10 "fmt"
11 "log/slog"
12 "net"
13+ "net/http"
14+ "os"
15 "strings"
16 "sync"
17 "time"
18
19 "github.com/antoniomika/syncmap"
20+ "github.com/prometheus/client_golang/prometheus"
21+ "github.com/prometheus/client_golang/prometheus/promauto"
22+ "github.com/prometheus/client_golang/prometheus/promhttp"
23 "golang.org/x/crypto/ssh"
24 )
25
26@@ -21,6 +29,7 @@ type SSHServerConn struct {
27 Logger *slog.Logger
28 Conn *ssh.ServerConn
29 SSHServer *SSHServer
30+ Start time.Time
31
32 mu sync.Mutex
33 }
34@@ -203,6 +212,7 @@ func NewSSHServerConn(
35 Logger: logger,
36 Conn: conn,
37 SSHServer: server,
38+ Start: time.Now(),
39 }
40 }
41
42@@ -212,7 +222,9 @@ type SSHServerChannelMiddleware func(ssh.NewChannel, *SSHServerConn) error
43
44 type SSHServerConfig struct {
45 *ssh.ServerConfig
46+ App string
47 ListenAddr string
48+ PromListenAddr string
49 Middleware []SSHServerMiddleware
50 SubsystemMiddleware []SSHServerMiddleware
51 ChannelMiddleware map[string]SSHServerChannelMiddleware
52@@ -225,9 +237,48 @@ type SSHServer struct {
53 Config *SSHServerConfig
54 Listener net.Listener
55 Conns *syncmap.Map[string, *SSHServerConn]
56+
57+ SessionsCreated *prometheus.CounterVec
58+ SessionsFinished *prometheus.CounterVec
59+ SessionsDuration *prometheus.CounterVec
60 }
61
62 func (s *SSHServer) ListenAndServe() error {
63+ if s.Config.PromListenAddr != "" {
64+ s.SessionsCreated = promauto.With(prometheus.DefaultRegisterer).NewCounterVec(prometheus.CounterOpts{
65+ Name: "wish_sessions_created_total",
66+ Help: "The total number of sessions created",
67+ ConstLabels: prometheus.Labels{
68+ "app": s.Config.App,
69+ },
70+ }, []string{"command"})
71+
72+ s.SessionsFinished = promauto.With(prometheus.DefaultRegisterer).NewCounterVec(prometheus.CounterOpts{
73+ Name: "wish_sessions_finished_total",
74+ Help: "The total number of sessions created",
75+ ConstLabels: prometheus.Labels{
76+ "app": s.Config.App,
77+ },
78+ }, []string{"command"})
79+
80+ s.SessionsDuration = promauto.With(prometheus.DefaultRegisterer).NewCounterVec(prometheus.CounterOpts{
81+ Name: "wish_sessions_duration_seconds",
82+ Help: "The total sessions duration in seconds",
83+ ConstLabels: prometheus.Labels{
84+ "app": s.Config.App,
85+ },
86+ }, []string{"command"})
87+
88+ go func() {
89+ http.Handle("/metrics", promhttp.Handler())
90+ err := http.ListenAndServe(s.Config.PromListenAddr, nil)
91+ if err != nil {
92+ s.Logger.Error("prometheus", "err", err)
93+ panic(err)
94+ }
95+ }()
96+ }
97+
98 listen, err := net.Listen("tcp", s.Config.ListenAddr)
99 if err != nil {
100 return err
101@@ -408,6 +459,14 @@ func NewSSHServer(ctx context.Context, logger *slog.Logger, config *SSHServerCon
102 return
103 }
104
105+ if sc.SSHServer.Config.PromListenAddr != "" {
106+ sc.SSHServer.SessionsCreated.WithLabelValues(payload.Value).Inc()
107+ defer func() {
108+ sc.SSHServer.SessionsFinished.WithLabelValues(payload.Value).Inc()
109+ sc.SSHServer.SessionsDuration.WithLabelValues(payload.Value).Add(time.Since(sc.Start).Seconds())
110+ }()
111+ }
112+
113 sesh.SetValue("command", strings.Fields(payload.Value))
114
115 h := func(*SSHServerConnSession) error { return nil }
116@@ -511,6 +570,91 @@ func NewSSHServer(ctx context.Context, logger *slog.Logger, config *SSHServerCon
117 return server
118 }
119
120+type PubKeyAuthHandler func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error)
121+
122+func NewSSHServerWithConfig(
123+ ctx context.Context,
124+ logger *slog.Logger,
125+ host, port, promPort string,
126+ pubKeyAuthHandler PubKeyAuthHandler,
127+ middleware, subsystemMiddleware []SSHServerMiddleware,
128+ channelMiddleware map[string]SSHServerChannelMiddleware) (*SSHServer, error) {
129+ server := NewSSHServer(ctx, logger, &SSHServerConfig{
130+ ListenAddr: fmt.Sprintf("%s:%s", host, port),
131+ ServerConfig: &ssh.ServerConfig{
132+ PublicKeyCallback: pubKeyAuthHandler,
133+ },
134+ Middleware: middleware,
135+ SubsystemMiddleware: subsystemMiddleware,
136+ ChannelMiddleware: channelMiddleware,
137+ })
138+
139+ if promPort != "" {
140+ server.Config.PromListenAddr = fmt.Sprintf("%s:%s", host, promPort)
141+ }
142+
143+ pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
144+ if err != nil {
145+ logger.Error("failed to read private key file", "error", err)
146+ if !os.IsNotExist(err) {
147+ return nil, err
148+ }
149+
150+ logger.Info("generating new private key")
151+
152+ pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
153+ if err != nil {
154+ logger.Error("failed to generate private key", "error", err)
155+ return nil, err
156+ }
157+
158+ privb, err := ssh.MarshalPrivateKey(privKey, "")
159+ if err != nil {
160+ logger.Error("failed to marshal private key", "error", err)
161+ return nil, err
162+ }
163+
164+ block := &pem.Block{
165+ Type: "OPENSSH PRIVATE KEY",
166+ Bytes: privb.Bytes,
167+ }
168+
169+ if err = os.MkdirAll("ssh_data", 0700); err != nil {
170+ logger.Error("failed to create ssh_data directory", "error", err)
171+ return nil, err
172+ }
173+
174+ pemBytes = pem.EncodeToMemory(block)
175+
176+ if err = os.WriteFile("ssh_data/term_info_ed25519", pemBytes, 0600); err != nil {
177+ logger.Error("failed to write private key", "error", err)
178+ return nil, err
179+ }
180+
181+ sshPubKey, err := ssh.NewPublicKey(pubKey)
182+ if err != nil {
183+ logger.Error("failed to create public key", "error", err)
184+ return nil, err
185+ }
186+
187+ pubb := ssh.MarshalAuthorizedKey(sshPubKey)
188+ if err = os.WriteFile("ssh_data/term_info_ed25519.pub", pubb, 0600); err != nil {
189+ logger.Error("failed to write public key", "error", err)
190+ return nil, err
191+ }
192+ }
193+
194+ signer, err := ssh.ParsePrivateKey(pemBytes)
195+ if err != nil {
196+ logger.Error("failed to parse private key", "error", err)
197+ return nil, err
198+ }
199+
200+ server.Config.AddHostKey(signer)
201+
202+ return server, nil
203+}
204+
205 func KeysEqual(a, b ssh.PublicKey) bool {
206 if a == nil || b == nil {
207 return false