This repository has been archived on 2026-03-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
oubliette/internal/shell/bash/bash.go
Torjus Håkestad 1b28f10ca8 refactor: migrate module path from git.t-juice.club to code.t-juice.club
Update Go module path and all import references to reflect the migration
from Gitea (git.t-juice.club) to Forgejo (code.t-juice.club).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 18:51:23 +01:00

109 lines
2.3 KiB
Go

package bash
import (
"context"
"errors"
"fmt"
"io"
"strings"
"time"
"code.t-juice.club/torjus/oubliette/internal/shell"
)
const sessionTimeout = 5 * time.Minute
// BashShell emulates a basic bash-like shell.
type BashShell struct{}
// NewBashShell returns a new BashShell instance.
func NewBashShell() *BashShell {
return &BashShell{}
}
func (b *BashShell) Name() string { return "bash" }
func (b *BashShell) Description() string { return "Basic bash-like shell emulator" }
func (b *BashShell) Handle(ctx context.Context, sess *shell.SessionContext, rw io.ReadWriteCloser) error {
ctx, cancel := context.WithTimeout(ctx, sessionTimeout)
defer cancel()
username := sess.Username
if sess.CommonConfig.FakeUser != "" {
username = sess.CommonConfig.FakeUser
}
hostname := sess.CommonConfig.Hostname
fs := newFilesystem(hostname)
state := &shellState{
cwd: "/root",
username: username,
hostname: hostname,
fs: fs,
}
// Send banner.
if sess.CommonConfig.Banner != "" {
fmt.Fprint(rw, sess.CommonConfig.Banner)
}
fmt.Fprintf(rw, "Last login: %s from 10.0.0.1\r\n",
time.Now().Add(-2*time.Hour).Format("Mon Jan 2 15:04:05 2006"))
for {
prompt := formatPrompt(state)
if _, err := fmt.Fprint(rw, prompt); err != nil {
return nil
}
line, err := shell.ReadLine(ctx, rw)
if errors.Is(err, io.EOF) {
fmt.Fprint(rw, "logout\r\n")
return nil
}
if err != nil {
return nil
}
trimmed := strings.TrimSpace(line)
if trimmed == "" {
continue
}
result := dispatch(state, trimmed)
var output string
if result.output != "" {
output = result.output
// Convert newlines to \r\n for terminal display.
output = strings.ReplaceAll(output, "\r\n", "\n")
output = strings.ReplaceAll(output, "\n", "\r\n")
fmt.Fprintf(rw, "%s\r\n", output)
}
// Log command and output to store.
if sess.Store != nil {
if err := sess.Store.AppendSessionLog(ctx, sess.SessionID, trimmed, output); err != nil {
return fmt.Errorf("append session log: %w", err)
}
}
if sess.OnCommand != nil {
sess.OnCommand("bash")
}
if result.exit {
return nil
}
}
}
func formatPrompt(state *shellState) string {
cwd := state.cwd
if cwd == "/root" {
cwd = "~"
} else if strings.HasPrefix(cwd, "/root/") {
cwd = "~" + cwd[5:]
}
return fmt.Sprintf("%s@%s:%s# ", state.username, state.hostname, cwd)
}