summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorraven <citrons@mondecitronne.com>2026-03-21 03:37:02 -0500
committerraven <citrons@mondecitronne.com>2026-03-21 03:37:02 -0500
commit6b20fd455a337d28f9847f87fc91177474356986 (patch)
tree615589d010bdfa5e56a15f719ee3d11adfad0f6f
parent3f6a61d993d6c50135c3cefe3cf362390c4027d5 (diff)
console commands
-rw-r--r--cmd/metronode/main.go112
-rw-r--r--go.mod4
-rw-r--r--server/commands.go2
-rw-r--r--server/player.go1
-rw-r--r--server/server.go4
5 files changed, 119 insertions, 4 deletions
diff --git a/cmd/metronode/main.go b/cmd/metronode/main.go
index b7edd63..31cd8e5 100644
--- a/cmd/metronode/main.go
+++ b/cmd/metronode/main.go
@@ -1,12 +1,18 @@
package main
import (
+ "os"
"net"
"log"
+ "bufio"
+ "strings"
+ "sync/atomic"
+ "golang.org/x/term"
"git.citrons.xyz/metronode/server"
)
func main() {
+ defer os.Exit(0)
s := server.NewServer(server.ServerInfo {
Name: "Metronode",
Motd: "hello, world",
@@ -15,5 +21,111 @@ func main() {
if err != nil {
log.Fatal(err)
}
+
+ cons := initConsole()
+ defer cons.Close()
+ log.SetOutput(&cons)
+ go readConsole(s, &cons)
+
s.Serve(ln)
}
+
+func readConsole(s *server.Server, cons *console) {
+ rd := bufio.NewReader(cons)
+ for {
+ line, err := rd.ReadString('\r')
+ if line == "" {
+ continue
+ }
+ if len(line) > 0 {
+ line = line[:len(line) - 1]
+ }
+ cons.Write([]byte("> " + line + "\n"))
+ if err != nil {
+ return
+ }
+ s.ExecuteCommand(cons, server.ConsoleAuth, line)
+ }
+}
+
+type console struct {
+ input atomic.Pointer[string]
+ termState *term.State
+}
+
+func initConsole() console {
+ var (c console; input string)
+ c.input.Store(&input)
+ c.termState, _ = term.MakeRaw(int(os.Stdin.Fd()))
+ c.showInput()
+ return c
+}
+
+func (c *console) Close() error {
+ term.Restore(int(os.Stdin.Fd()), c.termState)
+ os.Stdout.Write([]byte("\n"))
+ return nil
+}
+
+func (c *console) Read(p []byte) (n int, err error) {
+ n, err = os.Stdin.Read(p)
+ if err != nil {
+ return
+ }
+ var sb strings.Builder
+ for _, c := range p[:n] {
+ if c == '\r' {
+ c = '\n'
+ }
+ if c >= 32 || c == '\n' {
+ sb.WriteByte(c)
+ }
+ }
+ s := sb.String()
+ newline := strings.LastIndex(s, "\n")
+ if newline != -1 {
+ s = s[newline + 1:]
+ c.input.Store(&s)
+ } else {
+ s = *c.input.Load() + s
+ c.input.Store(&s)
+ }
+ c.Write([]byte{})
+ return
+}
+
+func (c *console) showInput() {
+ s := "\0337" // save cursor position
+ w, _, _ := term.GetSize(int(os.Stdout.Fd()))
+ s = s + strings.Repeat(" ", w)
+ s = s + "\0338" // restore
+ s = s + "\0337" // save
+ s = s + "> " + *c.input.Load()
+ os.Stdout.Write([]byte(s))
+}
+
+func (c *console) Write(p []byte) (n int, err error) {
+ s := "\0338" // restore cursor position
+ s = s + "\033[0J" // erase until end of screen
+ s = s + strings.Replace(string(p), "\n", "\r\n", -1)
+ n, err = os.Stdout.Write([]byte(s))
+ n = max(0, n - (len(s) - len(p)))
+ c.showInput()
+ return
+}
+
+func (c *console) OnCommandOutput(from *server.Server, output string) {
+ s := "\033[33m" // yellow
+ s = s + output
+ s = s + "\033[0m" // reset
+ s = s + "\r\n"
+ c.Write([]byte(s))
+}
+
+func (c *console) OnCommandError(from *server.Server, err string) {
+ s := "\033[31m" // red
+ s = s + err
+ s = s + "\033[0m" // reset
+ s = s + "\r\n"
+ c.Write([]byte(s))
+}
diff --git a/go.mod b/go.mod
index 948af49..97d3bc1 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,7 @@
module git.citrons.xyz/metronode
go 1.25.7
+
+require golang.org/x/term v0.41.0
+
+require golang.org/x/sys v0.42.0 // indirect
diff --git a/server/commands.go b/server/commands.go
index 0b56aaf..8154b7b 100644
--- a/server/commands.go
+++ b/server/commands.go
@@ -110,7 +110,7 @@ var commands = map[string]commandHandler {
sort.Strings(usages)
ctx.sender.OnCommandOutput(ctx.server, "Available commands:")
for _, usage := range usages {
- ctx.sender.OnCommandOutput(ctx.server, "&7* &e" + usage)
+ ctx.sender.OnCommandOutput(ctx.server, "* " + usage)
}
return ""
}
diff --git a/server/player.go b/server/player.go
index 4a562a7..ba4767c 100644
--- a/server/player.go
+++ b/server/player.go
@@ -30,6 +30,7 @@ type playerState struct {
const (
defaultAuth = iota
opAuth
+ ConsoleAuth
)
var playerNameRegex = regexp.MustCompile("^[.-_a-zA-Z0-9]*$")
diff --git a/server/server.go b/server/server.go
index 2740e09..834d5fe 100644
--- a/server/server.go
+++ b/server/server.go
@@ -95,9 +95,7 @@ func (s *Server) Serve(ln net.Listener) {
case <-ping: s.SendPings()
case <-tick: s.Tick()
case <-save: s.Save(nil)
- case <-sigterm:
- fmt.Println("")
- s.Stop(nil)
+ case <-sigterm: s.Stop(nil)
case <-s.stopped: return
}
}