diff --git a/.samples/original-broken-layout.png b/.samples/original-broken-layout.png deleted file mode 100644 index 05c2b4c..0000000 Binary files a/.samples/original-broken-layout.png and /dev/null differ diff --git a/.samples/same-session-openedasnew-correct-layout.png b/.samples/same-session-openedasnew-correct-layout.png deleted file mode 100644 index aced977..0000000 Binary files a/.samples/same-session-openedasnew-correct-layout.png and /dev/null differ diff --git a/README.md b/README.md index 69f823b..cdb0f0a 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Change with `-setlogin` before first use. | `-log off` | — | Disable file logging (console output always on) | | `-mfa on` | — | Enable TOTP MFA for user — prints secret + QR code | | `-mfa off` | — | Disable TOTP MFA for user | +| `-home ` | `~` | Starting directory for new shell sessions | --- @@ -101,26 +102,8 @@ Structured JSON-lines, one entry per login attempt: ## Run as service - `gotermix.service` is pretty limitted, you can change settings there to suit your needs -```bash -# 1. Create unprivileged system user (no shell, no home) -useradd --system --no-create-home --shell /sbin/nologin gotermix - -# 2. Deploy binary and set ownership -mkdir -p /opt/gotermix -cp gotermix /opt/gotermix/ -chown -R gotermix:gotermix /opt/gotermix -chmod 750 /opt/gotermix -chmod 750 /opt/gotermix/gotermix - -# 3. Install and enable service -cp gotermix.service /etc/systemd/system/ -systemctl daemon-reload -systemctl enable --now gotermix - -# 4. Check it's up -systemctl status gotermix -journalctl -u gotermix -f -``` +- there are 2 samples, `gotermix-limitted.service` has many settings what limits what this app and sessions in can do ( you may not even change user) +- `gotermix.service` is pretty much normal full shell without restriction, run as specific user. --- diff --git a/internals/config.go b/internals/config.go index ee9ee3f..7511177 100644 --- a/internals/config.go +++ b/internals/config.go @@ -60,6 +60,7 @@ type Session struct { var ( initialCwd string + shellHome string // starting directory for new shell sessions nopwMode bool appCreds storedCreds authSecret []byte diff --git a/internals/handlers.go b/internals/handlers.go index 17ae93b..e8790c6 100644 --- a/internals/handlers.go +++ b/internals/handlers.go @@ -374,7 +374,7 @@ func handleUpload(w http.ResponseWriter, r *http.Request) { destDir := strings.TrimSpace(r.FormValue("dest")) if destDir == "" { - destDir = initialCwd + destDir = shellHome } destPath := filepath.Join(filepath.Clean(destDir), filepath.Base(header.Filename)) @@ -400,7 +400,7 @@ func handleDownload(w http.ResponseWriter, r *http.Request) { } full := filepath.Clean(path) if !filepath.IsAbs(full) { - full = filepath.Join(initialCwd, full) + full = filepath.Join(shellHome, full) } if _, err := os.Stat(full); err != nil { http.Error(w, "file not found", http.StatusNotFound) diff --git a/internals/server.go b/internals/server.go index a194a45..2745e93 100644 --- a/internals/server.go +++ b/internals/server.go @@ -15,6 +15,24 @@ import ( qrcode "github.com/skip2/go-qrcode" ) +// expandHome resolves a leading ~ to the current user's home directory. +func expandHome(p string) (string, error) { + if p == "" { + return os.UserHomeDir() + } + if p == "~" { + return os.UserHomeDir() + } + if strings.HasPrefix(p, "~/") { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, p[2:]), nil + } + return p, nil +} + // Run is the application entry point, called from main(). func Run() { addr := flag.String("addr", "127.0.0.1:5000", "listen address") @@ -25,10 +43,24 @@ func Run() { certreset := flag.Bool("certreset", false, "remove stored custom certificate, revert to self-signed") logFlag := flag.String("log", "", "auth log file path; 'off' disables file logging (default: gotermix.log next to binary)") mfaFlag := flag.String("mfa", "", "manage MFA for a user: -mfa on|off") + homeFlag := flag.String("home", "", "starting directory for new shell sessions (default: user home ~)") flag.Parse() initialCwd, _ = os.Getwd() + // Resolve shell starting directory. + home, err := expandHome(*homeFlag) + if err != nil { + fmt.Fprintf(os.Stderr, "error resolving home directory: %v\n", err) + os.Exit(1) + } + info, err := os.Stat(home) + if err != nil || !info.IsDir() { + fmt.Fprintf(os.Stderr, "error: -home %q is not a valid directory\n", home) + os.Exit(1) + } + shellHome = home + // Credentials file lives next to the executable. exe, err := os.Executable() if err != nil { diff --git a/internals/session.go b/internals/session.go index 5ddd8b8..43e19ec 100644 --- a/internals/session.go +++ b/internals/session.go @@ -67,7 +67,7 @@ func getOrCreate(id string) *Session { } else { cmd = exec.Command("/bin/bash", "-i") } - cmd.Dir = initialCwd + cmd.Dir = shellHome // Build environment: inherit parent env but force TERM so that bash readline // correctly decodes modifier+cursor sequences (Shift+Arrow etc.).