repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

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
M Dockerfile
+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"]
M pkg/apps/feeds/ssh.go
+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)
M pkg/apps/pastes/ssh.go
+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)
M pkg/apps/pgs/ssh.go
+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)
M pkg/apps/pico/ssh.go
+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)
M pkg/apps/pipe/ssh.go
+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)
M pkg/apps/prose/ssh.go
+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)
M pkg/pssh/server.go
+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