diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..d62af21
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,5 @@
+server/
+fabric/
+.vscode/
+mcgrizz.json
+players.cache.json
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..bf0d16c
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,22 @@
+FROM denoland/deno:debian
+
+WORKDIR /mcgrizz
+# USER deno
+
+COPY . .
+
+# RUN deno task build
+RUN deno cache main.ts
+# Install OpenJDK-17
+RUN apt-get update && \
+ apt-get install -y openjdk-17-jre ca-certificates-java && \
+ apt-get clean && \
+ update-ca-certificates -f
+# Setup JAVA_HOME -- useful for docker commandline
+ENV JAVA_HOME /usr/lib/jvm/java-17-openjdk-amd64/
+RUN export JAVA_HOME
+
+EXPOSE 8000
+EXPOSE 25565-25575
+
+CMD [ "run", "-A", "main.ts" ]
\ No newline at end of file
diff --git a/deno.json b/deno.json
index 73ad739..57057c1 100644
--- a/deno.json
+++ b/deno.json
@@ -5,7 +5,8 @@
"start": "tailwind -i ./static/styles/main.css -o ./static/styles/tailwind.css --minify --watch & deno run -A --watch=static/,routes/ dev.ts",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts",
- "update": "deno run -A -r https://fresh.deno.dev/update ."
+ "update": "deno run -A -r https://fresh.deno.dev/update .",
+ "clean": "rm -rf ./server && rm ./players.cache.json && rm ./mcgrizz.json"
},
"lint": {
"rules": {
diff --git a/islands/statusManager.tsx b/islands/statusManager.tsx
index 96d97a5..2462f1e 100644
--- a/islands/statusManager.tsx
+++ b/islands/statusManager.tsx
@@ -18,30 +18,43 @@ export function StatusManager(
props.onAction && props.onAction(body);
};
- const [status, setStatus] = useState('');
+ const [status, setStatus] = useState("");
- const getStatus = async () => {
- const res = await fetch('/api/manage');
- const body = await res.text();
+ const getStatus = () => {
+ globalThis.statusSource = globalThis.statusSource || new EventSource('/api/manage');
- setStatus(body);
- }
+ globalThis.statusSource.addEventListener('status', (e) => {
+ setStatus(e.data);
+ })
+ };
useEffect(() => {
if (IS_BROWSER) getStatus();
- },[])
+ }, []);
return (
{!!status &&
Server is {status}}
-
diff --git a/islands/terminal.tsx b/islands/terminal.tsx
index 46483e9..a3109ac 100644
--- a/islands/terminal.tsx
+++ b/islands/terminal.tsx
@@ -4,7 +4,7 @@ import { JSX } from "preact/jsx-runtime";
import { Sockpuppet } from "puppet/client";
export function Terminal(props: { channelId: string }) {
- const puppet = useRef(new Sockpuppet("ws://sockpuppet.cyborggrizzly.com"));
+ const puppet = useRef
();
const [lines, setLines] = useState([]);
const divRef = useRef(null);
const storeKey = "commandHistory";
@@ -18,15 +18,21 @@ export function Terminal(props: { channelId: string }) {
const changeHistoryIndex = (by: number) => setHistoryIndex((i) => i + by);
useEffect(() => {
- if (!IS_BROWSER) return;
- puppet.current.joinChannel(props.channelId, (line) => {
- setLines((l) => [...l, line]);
+ if (!IS_BROWSER || puppet.current) return;
+ puppet.current = new Sockpuppet("ws://sockpuppet.cyborggrizzly.com", () => {
+ puppet.current?.joinChannel(props.channelId, (line) => {
+ if (line === "clear") {
+ setLines([]);
+ } else {
+ setLines((l) => [...l, line]);
+ }
+ });
+ setTimeout(() => {
+ const channel = puppet.current?.getChannel(props.channelId);
+ // console.log(channel)
+ channel?.send("log");
+ }, 200);
});
- setTimeout(() => {
- const channel = puppet.current.getChannel(props.channelId)
- // console.log(channel)
- channel?.send("log");
- }, 200);
document.addEventListener("keyup", (e) => {
switch (e.key) {
@@ -50,12 +56,12 @@ export function Terminal(props: { channelId: string }) {
const sendCommand = (e: Event) => {
e.preventDefault();
- puppet.current.getChannel(props.channelId)?.send(
- historyIndex === commandHistory.length
- ? command
- : commandHistory[historyIndex],
+ puppet.current?.getChannel(props.channelId)?.send(
+ commandHistory.at(historyIndex) || command,
);
- setCommandHistory((c) => [...c, command]);
+ setCommandHistory((
+ c,
+ ) => [...c, commandHistory.at(historyIndex) || command]);
setHistoryIndex(commandHistory.length + 1);
setCommand("");
};
diff --git a/routes/api/manage.ts b/routes/api/manage.ts
index f7503a0..dd9f204 100644
--- a/routes/api/manage.ts
+++ b/routes/api/manage.ts
@@ -25,7 +25,26 @@ export const handler: Handlers = {
return new Response("action done");
}
},
- GET() {
- return new Response(SERVER_STATE.status);
+ GET(_req, _ctx) {
+ let listener: (e: CustomEvent) => void;
+ const body = new ReadableStream({
+ async start(controller){
+ const event = `event: status\ndata: ${SERVER_STATE.status}\n\n`
+ controller.enqueue(event);
+ listener = (e) => {
+ const event = `event: status\ndata: ${e.detail}\n\n`
+ controller.enqueue(event);
+ };
+ globalThis.addEventListener('serverstatuschange' as any, listener)
+ },
+ cancel() {
+ globalThis.removeEventListener('serverstatuschange' as any, listener);
+ }
+ })
+
+ return new Response(body.pipeThrough(new TextEncoderStream()), { headers: {
+ "Content-Type": "text/event-stream",
+ "cache-control": "no-cache"
+ }});
}
};
diff --git a/state/serverState.ts b/state/serverState.ts
index 1c655bc..07096d3 100644
--- a/state/serverState.ts
+++ b/state/serverState.ts
@@ -4,7 +4,7 @@ import { Loader } from "../types/mcgrizzconf.ts";
import { getConfFile, updateConfFile } from "../util/confFile.ts";
import { IS_BROWSER } from "$fresh/runtime.ts";
-type MCServerEvent = 'message';
+type MCServerEvent = "message";
type MCServerEventCallback = (msg: string) => void;
class ServerState {
@@ -19,21 +19,21 @@ class ServerState {
private _eulaAccepted = false;
private sockpuppet!: Sockpuppet;
- private _channelId = "blanaba";
+ private _channelId;
public get channelId() {
return this._channelId;
}
public get eulaAccepted() {
return this._eulaAccepted;
}
- private stdin!: WritableStreamDefaultWriter;
+ private stdin!: WritableStreamDefaultWriter;
- private _serverType: Loader = 'unset';
+ private _serverType: Loader = "unset";
public get serverType(): Loader {
return this._serverType;
}
public set serverType(loader: Loader) {
- updateConfFile({loader});
+ updateConfFile({ loader });
this._serverType = loader;
}
@@ -42,26 +42,35 @@ class ServerState {
return this._serverVersion;
}
public set serverVersion(version: string) {
- updateConfFile({version});
+ updateConfFile({ version });
this._serverVersion = version;
}
constructor() {
+ this._channelId = crypto.randomUUID();
+
const conf = getConfFile();
this._serverType = conf.loader;
this._serverVersion = conf.version;
- // if (this.serverType !== 'unset') this._eulaAccepted = checkEULA();
+ this._eulaAccepted = checkEULA();
this.sockpuppet = new Sockpuppet(
"ws://sockpuppet.cyborggrizzly.com",
() => {
+ this.sockpuppet.createChannel(this._channelId);
this.sockpuppet.joinChannel(this.channelId, async (msg) => {
- if (msg === 'log' && !IS_BROWSER) {
- const log = await Deno.readTextFile('./server/logs/latest.log');
- this.channel?.send(log);
- } else
- this.sendStdIn(msg);
+ if (msg === "log" && !IS_BROWSER) {
+ try {
+ const log = await Deno.readTextFile("./server/logs/latest.log");
+ this.channel?.send(log);
+ } catch {
+ 1;
+ }
+ } else {
+ console.log(msg)
+ this.sendStdIn(msg);
+ }
});
},
);
@@ -79,7 +88,6 @@ class ServerState {
await this.stdin.write(msg);
}
-
// "instance" should be moved to a private member once multi-instance support is implemented
public startMCServer(instance = "server") {
this.command = new Deno.Command("java", {
@@ -94,14 +102,23 @@ class ServerState {
stdout: "piped",
});
+
+ this.channel?.send('clear');
+
this.process = this.command.spawn();
-
- const {readable, writable} = new TransformStream();
+
+ const { readable, writable } = new TransformStream();
readable.pipeTo(this.process.stdin);
this.stdin = writable.getWriter();
-
+
this._status = "running";
+ const statusEvent = new CustomEvent('serverstatuschange', {detail: this._status});
+
+ globalThis.dispatchEvent(statusEvent);
this.startStream();
+ // this.process.status.then(() => {
+ // this._status = "stopped";
+ // });
}
private async startStream() {
@@ -120,25 +137,54 @@ class ServerState {
}
if (done) break;
}
+
+ // await this.process.stdout.pipeThrough(new TextDecoderStream()).pipeTo(
+ // new WritableStream({
+ // write: (chunk) => {
+ // this.channel?.send(chunk);
+ // const stdoutMsg = new CustomEvent("stdoutmsg", {
+ // detail: chunk,
+ // });
+ // globalThis.dispatchEvent(stdoutMsg);
+ // },
+ // }),
+ // );
}
public gracefullyStopMCServer() {
this._status = "stopped";
this.sendStdIn("stop");
+ const statusEvent = new CustomEvent('serverstatuschange', {detail: this._status});
+
+ globalThis.dispatchEvent(statusEvent);
+
}
public forceStopMCServer() {
this._status = "stopped";
this.process.kill();
+ const statusEvent = new CustomEvent('serverstatuschange', {detail: this._status});
+
+ globalThis.dispatchEvent(statusEvent);
+
}
public async restartMCServer() {
if (this.status === "running") {
await this.sendStdIn("stop");
+ const statusEvent = new CustomEvent('serverstatuschange', {detail: 'restarting'});
+
+ globalThis.dispatchEvent(statusEvent);
+
// while (true) {
await this.process.status;
// }
}
+
+ const statusEvent = new CustomEvent('serverstatuschange', {detail: this._status});
+
+ globalThis.dispatchEvent(statusEvent);
+
this.startMCServer();
}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..f26cf54
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "include": ["./types.d.ts"]
+}
\ No newline at end of file
diff --git a/types.d.ts b/types.d.ts
new file mode 100644
index 0000000..ccb59ec
--- /dev/null
+++ b/types.d.ts
@@ -0,0 +1,6 @@
+// deno-lint-ignore-file no-var
+export {}
+
+declare global {
+ var statusSource: EventSource;
+}
diff --git a/util/EULA.ts b/util/EULA.ts
index 589565b..a97a2ab 100644
--- a/util/EULA.ts
+++ b/util/EULA.ts
@@ -1,8 +1,14 @@
import { IS_BROWSER } from "$fresh/runtime.ts";
const eulaRegex = /(eula=false)/;
-export const checkEULA = (instance = "server") =>
- !IS_BROWSER && !eulaRegex.test(Deno.readTextFileSync(`./${instance}/eula.txt`));
+export const checkEULA = (instance = "server") => {
+ try {
+ return !IS_BROWSER &&
+ !eulaRegex.test(Deno.readTextFileSync(`./${instance}/eula.txt`));
+ } catch {
+ return false;
+ }
+};
export const acceptEULA = (instance = "server") => {
const eula = Deno.readTextFileSync(`./${instance}/eula.txt`);