Skip to content

Conversation

@EmilyxFox
Copy link

@EmilyxFox EmilyxFox commented Sep 1, 2025

What

I'm trying to add a websocket console similar to the ssh console that's already there.

This was also requested in itzg/docker-minecraft-server#3152.

Things that currently work:

  • connect to ws
  • stream stdout & err over the websocket
  • send stdin over websocket

Things that don't work

  • sending the last x amount of lines to newly connected clients
    • Works!
  • requesting older lines (for scrolling up in a console, for example)
  • websocket isn't gracefully closed by the server before the program exits
    • closes with 1006
    • closes with 1001! 🎉

Potential features

itzg/docker-minecraft-server#3152 mentions being able to request things like available commands, auto completions, and various server stats (cpu, ram). I don't know the feasibility of this at all but I thought I'd include it here anyways just to track.

I think being able to query server status (starting, running, stopping) would be cool, or having it sent in a heartbeat type thing.

Why

Feature could be useful for making web panel consoles which I think would be cool as a sidecar.

I think it would also be a good idea to have two formats (json & text) to either use json formatted messages with extra stuff in, or just send plain stdout/err and stdin through the websocket.

I haven't tested a lot of edge cases at all since this code is an absolute mess. I did test the really long line bug though, and it seems to handle that fine. I make no promises that I'll finish this, but I've put it here to gather some feedback :)

Copy link
Owner

@itzg itzg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay reviewing this. Overall, I really like this, especially the new "multi" abstractions.

@EmilyxFox
Copy link
Author

Actually sending stderr works now (tested). I think I should remove the prefix option since its useless with the json output.

@EmilyxFox
Copy link
Author

@itzg I'd be interested to hear your take on the potential features bit. Especially which ones are kinda doable and which ones are less so.

@itzg
Copy link
Owner

itzg commented Sep 6, 2025

@itzg I'd be interested to hear your take on the potential features bit. Especially which ones are kinda doable and which ones are less so.

It seems like the ones you noted, available commands, auto completions, and various server stats (cpu, ram), are not particularly doable without deeper hooks into with a plugin/mod at which point that plugin/mod should be the one exposing a websocket interface.

My expectations for this websocket support are covered since it can do the equivalent of rcon's sending commands and returning the response. The prior and current logging retrieval was even a bonus/optional capability in my mind.

Reporting server up/down status does seem doable and helpful via a call to mc-monitor.

@EmilyxFox
Copy link
Author

I think authentication might still be a good idea before its merged. It could just be a header or query param or something like that, I think, and then just taken from the rcon password.

@itzg
Copy link
Owner

itzg commented Sep 6, 2025

Ah, great point about authentication, but yes keeping it simple like leveraging the rcon password as a bearer token, etc seems good.

Comment on lines 106 to 117
password := r.Header.Get("X-WS-Auth")
if password != getWebsocketPassword() {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)

errMsg := AuthErrorMessage{
Type: MessageTypeAuthError,
Reason: "invalid password",
}
json.NewEncoder(w).Encode(errMsg)
return
}
Copy link
Author

@EmilyxFox EmilyxFox Sep 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to be able to skip this with WEBSOCKET_DISABLE_AUTHENTICATION=true but I would have to coerce "true" "TRUE" and maybe "1" to bools and idk you might have a fancy way of doing that.
Otherwise this works. lmk if you don't like the header name or whatnot

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't have to use WithEnv(). That created env vars for every setting and I'm not sure that's what you wanted. lmk!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EmilyxFox
Copy link
Author

EmilyxFox commented Sep 6, 2025

(I can only reference changes in comments)
I'm wondering if finally closing the websocket just before this done (or maybe after) would be the best place to do it? You'd want to get the whole shutdown sequence sent over the websocket.

@itzg
Copy link
Owner

itzg commented Sep 6, 2025

(I can only reference changes in comments) I'm wondering if finally closing the websocket just before this done (or maybe after) would be the best place to do it? You'd want to get the whole shutdown sequence sent over the websocket.

Yes but even better is to hook into the context that gets cancelled there. Only quirk is that would mean moving the context creation here

https://github.com/itzg/mc-server-runner/pull/124/files#diff-2873f79a86c0d8b3335cd7731b0ecf7dd4301eb19a82ef7a1cba7589b5252261R153

further up and passing that into the websocket server object setup.

I'm likely over simplifying the suggestion so I don't mind pushing a change to show what I mean.

@EmilyxFox
Copy link
Author

Feel free to push your suggestion. Its quite late here, so no rush, I probably wont write any more code today 🫡

@Mqxx
Copy link

Mqxx commented Oct 20, 2025

But how would you link an input to a specific output?

You don't. And you can't. Thats my point. But you can give a specific "feedback" for each command send to the server stdin. You can not do that over WebSocket, at least not without an proper protocol like JsonRPC 2.0

@EmilyxFox
Copy link
Author

But how would you link an input to a specific output?

You don't. And you can't. thats my point. But you can give a specific "feedback" for each command send to the server stdin. You can not do that over WebSocket, at least not without an proper protocol like JsonRPC 2.0

I guess "feedback" is confusing me. What do you imagine this feedback to be? Just a 2xx response code, or?

@Mqxx
Copy link

Mqxx commented Oct 20, 2025

Just a 2xx response code, or?

For example, yes. And with that you can build on top of that. For example posting to localhost:8080/health would give you stats about the current health of the container. And you could build much more with that.

But if you cram all of that in just this one WebSocket connection, you quickly would hit limitations.

Just to make it clear: You can absolutely build it that way and say: "Yes I only want to be able to stream the stdout, and send commands to the server". And that's completely fine. But If you want to be more open (implementation wise) and expand the functionality in the future, I would not build it that way...

@Mqxx
Copy link

Mqxx commented Oct 20, 2025

For example Discord API works the same. To send messages to a channel you use the normal HTTP POST API and to receive live message feeds from the API you can subscribe to the WebSocket API for a channel.

@EmilyxFox
Copy link
Author

I think your ideas are good, but I still think having a fully websocket console is the best solution for that part of it. Maybe the websocket console should be put behind a path (eg. /console), to leave space for other endpoints. Then there could be another endpoint (/console/send) that you could use to send stdin lines directly.

@Mqxx
Copy link

Mqxx commented Oct 20, 2025

Maybe the websocket console should be put behind a path

That's at least what I would do 😅 but yea

@Mqxx
Copy link

Mqxx commented Oct 20, 2025

All of my opinions are just suggestions, because I have build systems and concepts like this in the past 😅

@Mqxx
Copy link

Mqxx commented Oct 20, 2025

But I am not familiar with go-lang that's why I am not telling you how to do it and instead offering solutions how you could build it and make it (in my opinion) better 😊

@EmilyxFox
Copy link
Author

But I am not familiar with go-lang that's why I am not telling you how to do it and instead offering solutions how you could build it and make it (in my opinion) better 😊

These are all functional considerations so the language doesn't matter :) I do like the idea of making room for more features in the future, but I am worried about scope creep, and whether those features are even possible. I'm not even sure how many other endpoints are possible. Different functions/endpoints might also need to be handled by different parts of the whole docker-minecraft-server stack, and that introduces quite a bit of complexity.

@itzg
Copy link
Owner

itzg commented Oct 20, 2025

I agree with @EmilyxFox 's concerns. Your feature scope @Mqxx seems to be expanding and changing quite a bit. As with all open source, you're welcome to keep using your mod implementation since that seems to do what you want more precisely.

I do like the "/console" subpath just in case there are other things and self-documents the intent.

I also caution against reinventing a few APIs that are already present in the ecosytem:

  • The new Minecraft Server Management Protocol effectively eliminates the need for a generalized, text-based command/response approach; however, @EmilyxFox 's solution is low level enough and value add (with buffered logs) to prove a solution for all types of Minecraft servers, vanilla included, and all versions (especially ones prior to MSMP)
  • Docker and Kubernetes both provide very rich APIs that do a lot of what you're now describing @Mqxx . Things like the health endpoint are first class APIs across those and are supported by the mc-monitor built into the image. For example, the "State->Health" part of response from https://docs.docker.com/reference/api/engine/version/v1.43/#tag/Container/operation/ContainerInspect
  • For richer log parsing I recommend using an observability based solution like fluent-bit or logstash. Both of which are designed for extracting log levels and others semantics from the logs

@itzg itzg closed this Oct 20, 2025
@itzg
Copy link
Owner

itzg commented Oct 20, 2025

(ugh, the UI switched focus on me just before pressing enter)

@itzg itzg reopened this Oct 20, 2025
@EmilyxFox
Copy link
Author

EmilyxFox commented Oct 20, 2025

I thought more about a /console/send endpoint, and think the best way to implement that would be to use RCON locally, and then send the RCON response as the response to the endpoint. I think doing it any other way would be unnecessarily worse than RCON, but then the solution itself seems like a needless abstraction on top of RCON. I think /console for the ws endpoint is still good to allow room for further expansion. If it ends up being the case that a whole internal reverse proxy is needed to route different api requests to different part of the stack, at least this one was separated from the start, and that wont be a breaking change. lmk what you think 😀

edit: i guess needlessness isn't really an argument for why not to do it 🙃
edit2: decided to look up how rcon works and given that it isn't just POSTing to an endpoint, I do think there's utility in abstracting it.

@itzg
Copy link
Owner

itzg commented Oct 20, 2025

Now you've thrown a fun and interesting twist into the discussion: rcon over websocket. Maybe that turns out to be the distinct, portable, and just general enough aspect to focus.

The downside is, that means the docker attach/logs endpoint is probably the better one to shift over for general log consumption.

@itzg
Copy link
Owner

itzg commented Oct 20, 2025

...with that said, that Rcon approach can be iterated later in a separate PR. Don't feel compelled to have this one PR be the end all, last chance 😄

@Mqxx
Copy link

Mqxx commented Oct 21, 2025

rcon over websocket

Maybe not RCON over WebSocket but rather RCON over HTTP. Like HTTP POST localhost:8080/console/send and than the command you want to execute on the server. Maybe we could also implement the adapter so that we use the MSMP in the first place for sending commands and fallback to basic RCON for older versions. But yea thats probably out of scope for this PR.

@Mqxx
Copy link

Mqxx commented Oct 21, 2025

I think doing it any other way would be unnecessarily worse than RCON, but then the solution itself seems like a needless abstraction on top of RCON.

No abstraction can be worst than RCON itself 😅, also I don't think it would be a "needless abstraction"

@Mqxx
Copy link

Mqxx commented Oct 21, 2025

I would suggest, to not get to much out of scope to implement the sending and receiving using only the WebSocket for now with a subpath (/console) and expand that in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants