feat: add Smart Fridge shell and per-credential shell routing
Implement Samsung FridgeOS-themed shell (PLAN.md §3.3) with inventory management, temperature controls, diagnostics, alerts, and other appliance commands. Add per-credential shell routing so static credentials can specify which shell to use via the `shell` config field, passed through ssh.Permissions.Extensions. Also extract shared ReadLine helper from bash to the shell package so both shells can reuse terminal input handling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"git.t-juice.club/torjus/oubliette/internal/notify"
|
||||
"git.t-juice.club/torjus/oubliette/internal/shell"
|
||||
"git.t-juice.club/torjus/oubliette/internal/shell/bash"
|
||||
"git.t-juice.club/torjus/oubliette/internal/shell/fridge"
|
||||
"git.t-juice.club/torjus/oubliette/internal/storage"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
@@ -38,6 +39,9 @@ func New(cfg config.Config, store storage.Store, logger *slog.Logger) (*Server,
|
||||
if err := registry.Register(bash.NewBashShell(), 1); err != nil {
|
||||
return nil, fmt.Errorf("registering bash shell: %w", err)
|
||||
}
|
||||
if err := registry.Register(fridge.NewFridgeShell(), 1); err != nil {
|
||||
return nil, fmt.Errorf("registering fridge shell: %w", err)
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
cfg: cfg,
|
||||
@@ -138,10 +142,24 @@ func (s *Server) handleSession(channel ssh.Channel, requests <-chan *ssh.Request
|
||||
defer channel.Close()
|
||||
|
||||
// Select a shell from the registry.
|
||||
selectedShell, err := s.shellRegistry.Select()
|
||||
if err != nil {
|
||||
s.logger.Error("failed to select shell", "err", err)
|
||||
return
|
||||
// If the auth layer specified a shell preference, use it; otherwise random.
|
||||
var selectedShell shell.Shell
|
||||
if conn.Permissions != nil && conn.Permissions.Extensions["shell"] != "" {
|
||||
shellName := conn.Permissions.Extensions["shell"]
|
||||
sh, ok := s.shellRegistry.Get(shellName)
|
||||
if ok {
|
||||
selectedShell = sh
|
||||
} else {
|
||||
s.logger.Warn("configured shell not found, falling back to random", "shell", shellName)
|
||||
}
|
||||
}
|
||||
if selectedShell == nil {
|
||||
var err error
|
||||
selectedShell, err = s.shellRegistry.Select()
|
||||
if err != nil {
|
||||
s.logger.Error("failed to select shell", "err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ip := extractIP(conn.RemoteAddr())
|
||||
@@ -304,7 +322,13 @@ func (s *Server) passwordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.
|
||||
}
|
||||
|
||||
if d.Accepted {
|
||||
return nil, nil
|
||||
var perms *ssh.Permissions
|
||||
if d.Shell != "" {
|
||||
perms = &ssh.Permissions{
|
||||
Extensions: map[string]string{"shell": d.Shell},
|
||||
}
|
||||
}
|
||||
return perms, nil
|
||||
}
|
||||
return nil, fmt.Errorf("rejected")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user