basic docker config, fixes several critical faults, allows status manager to get status via sse
This commit is contained in:
parent
2487529aaf
commit
6944cbb9f7
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
server/
|
||||||
|
fabric/
|
||||||
|
.vscode/
|
||||||
|
mcgrizz.json
|
||||||
|
players.cache.json
|
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@ -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" ]
|
@ -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",
|
"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",
|
"build": "deno run -A dev.ts build",
|
||||||
"preview": "deno run -A main.ts",
|
"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": {
|
"lint": {
|
||||||
"rules": {
|
"rules": {
|
||||||
|
@ -18,30 +18,43 @@ export function StatusManager(
|
|||||||
props.onAction && props.onAction(body);
|
props.onAction && props.onAction(body);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [status, setStatus] = useState('');
|
const [status, setStatus] = useState("");
|
||||||
|
|
||||||
const getStatus = async () => {
|
const getStatus = () => {
|
||||||
const res = await fetch('/api/manage');
|
globalThis.statusSource = globalThis.statusSource || new EventSource('/api/manage');
|
||||||
const body = await res.text();
|
|
||||||
|
|
||||||
setStatus(body);
|
globalThis.statusSource.addEventListener('status', (e) => {
|
||||||
}
|
setStatus(e.data);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (IS_BROWSER) getStatus();
|
if (IS_BROWSER) getStatus();
|
||||||
},[])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...props}>
|
<div {...props}>
|
||||||
{!!status && <small>Server is {status}</small>}
|
{!!status && <small>Server is {status}</small>}
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<Button color="wasabi" disabled={!status || status === 'running'} onClick={() => sendCommand(ManageAction.start)}>
|
<Button
|
||||||
|
color="wasabi"
|
||||||
|
disabled={!status || status === "running"}
|
||||||
|
onClick={() => sendCommand(ManageAction.start)}
|
||||||
|
>
|
||||||
Start
|
Start
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="fire" disabled={!status || status === 'stopped'} onClick={() => sendCommand(ManageAction.stop)}>
|
<Button
|
||||||
|
color="fire"
|
||||||
|
disabled={!status || status === "stopped"}
|
||||||
|
onClick={() => sendCommand(ManageAction.stop)}
|
||||||
|
>
|
||||||
Stop
|
Stop
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="sky" disabled={!status} onClick={() => sendCommand(ManageAction.restart)}>
|
<Button
|
||||||
|
color="sky"
|
||||||
|
disabled={!status}
|
||||||
|
onClick={() => sendCommand(ManageAction.restart)}
|
||||||
|
>
|
||||||
Restart
|
Restart
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@ import { JSX } from "preact/jsx-runtime";
|
|||||||
import { Sockpuppet } from "puppet/client";
|
import { Sockpuppet } from "puppet/client";
|
||||||
|
|
||||||
export function Terminal(props: { channelId: string }) {
|
export function Terminal(props: { channelId: string }) {
|
||||||
const puppet = useRef(new Sockpuppet("ws://sockpuppet.cyborggrizzly.com"));
|
const puppet = useRef<Sockpuppet>();
|
||||||
const [lines, setLines] = useState<string[]>([]);
|
const [lines, setLines] = useState<string[]>([]);
|
||||||
const divRef = useRef<HTMLDivElement>(null);
|
const divRef = useRef<HTMLDivElement>(null);
|
||||||
const storeKey = "commandHistory";
|
const storeKey = "commandHistory";
|
||||||
@ -18,15 +18,21 @@ export function Terminal(props: { channelId: string }) {
|
|||||||
const changeHistoryIndex = (by: number) => setHistoryIndex((i) => i + by);
|
const changeHistoryIndex = (by: number) => setHistoryIndex((i) => i + by);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!IS_BROWSER) return;
|
if (!IS_BROWSER || puppet.current) return;
|
||||||
puppet.current.joinChannel(props.channelId, (line) => {
|
puppet.current = new Sockpuppet("ws://sockpuppet.cyborggrizzly.com", () => {
|
||||||
|
puppet.current?.joinChannel(props.channelId, (line) => {
|
||||||
|
if (line === "clear") {
|
||||||
|
setLines([]);
|
||||||
|
} else {
|
||||||
setLines((l) => [...l, line]);
|
setLines((l) => [...l, line]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const channel = puppet.current.getChannel(props.channelId)
|
const channel = puppet.current?.getChannel(props.channelId);
|
||||||
// console.log(channel)
|
// console.log(channel)
|
||||||
channel?.send("log");
|
channel?.send("log");
|
||||||
}, 200);
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener("keyup", (e) => {
|
document.addEventListener("keyup", (e) => {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
@ -50,12 +56,12 @@ export function Terminal(props: { channelId: string }) {
|
|||||||
|
|
||||||
const sendCommand = (e: Event) => {
|
const sendCommand = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
puppet.current.getChannel(props.channelId)?.send(
|
puppet.current?.getChannel(props.channelId)?.send(
|
||||||
historyIndex === commandHistory.length
|
commandHistory.at(historyIndex) || command,
|
||||||
? command
|
|
||||||
: commandHistory[historyIndex],
|
|
||||||
);
|
);
|
||||||
setCommandHistory((c) => [...c, command]);
|
setCommandHistory((
|
||||||
|
c,
|
||||||
|
) => [...c, commandHistory.at(historyIndex) || command]);
|
||||||
setHistoryIndex(commandHistory.length + 1);
|
setHistoryIndex(commandHistory.length + 1);
|
||||||
setCommand("");
|
setCommand("");
|
||||||
};
|
};
|
||||||
|
@ -25,7 +25,26 @@ export const handler: Handlers = {
|
|||||||
return new Response("action done");
|
return new Response("action done");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GET() {
|
GET(_req, _ctx) {
|
||||||
return new Response(SERVER_STATE.status);
|
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"
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import { Loader } from "../types/mcgrizzconf.ts";
|
|||||||
import { getConfFile, updateConfFile } from "../util/confFile.ts";
|
import { getConfFile, updateConfFile } from "../util/confFile.ts";
|
||||||
import { IS_BROWSER } from "$fresh/runtime.ts";
|
import { IS_BROWSER } from "$fresh/runtime.ts";
|
||||||
|
|
||||||
type MCServerEvent = 'message';
|
type MCServerEvent = "message";
|
||||||
type MCServerEventCallback = (msg: string) => void;
|
type MCServerEventCallback = (msg: string) => void;
|
||||||
|
|
||||||
class ServerState {
|
class ServerState {
|
||||||
@ -19,7 +19,7 @@ class ServerState {
|
|||||||
private _eulaAccepted = false;
|
private _eulaAccepted = false;
|
||||||
|
|
||||||
private sockpuppet!: Sockpuppet;
|
private sockpuppet!: Sockpuppet;
|
||||||
private _channelId = "blanaba";
|
private _channelId;
|
||||||
public get channelId() {
|
public get channelId() {
|
||||||
return this._channelId;
|
return this._channelId;
|
||||||
}
|
}
|
||||||
@ -28,12 +28,12 @@ class ServerState {
|
|||||||
}
|
}
|
||||||
private stdin!: WritableStreamDefaultWriter;
|
private stdin!: WritableStreamDefaultWriter;
|
||||||
|
|
||||||
private _serverType: Loader = 'unset';
|
private _serverType: Loader = "unset";
|
||||||
public get serverType(): Loader {
|
public get serverType(): Loader {
|
||||||
return this._serverType;
|
return this._serverType;
|
||||||
}
|
}
|
||||||
public set serverType(loader: Loader) {
|
public set serverType(loader: Loader) {
|
||||||
updateConfFile({loader});
|
updateConfFile({ loader });
|
||||||
this._serverType = loader;
|
this._serverType = loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,26 +42,35 @@ class ServerState {
|
|||||||
return this._serverVersion;
|
return this._serverVersion;
|
||||||
}
|
}
|
||||||
public set serverVersion(version: string) {
|
public set serverVersion(version: string) {
|
||||||
updateConfFile({version});
|
updateConfFile({ version });
|
||||||
this._serverVersion = version;
|
this._serverVersion = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this._channelId = crypto.randomUUID();
|
||||||
|
|
||||||
const conf = getConfFile();
|
const conf = getConfFile();
|
||||||
this._serverType = conf.loader;
|
this._serverType = conf.loader;
|
||||||
this._serverVersion = conf.version;
|
this._serverVersion = conf.version;
|
||||||
|
|
||||||
// if (this.serverType !== 'unset') this._eulaAccepted = checkEULA();
|
this._eulaAccepted = checkEULA();
|
||||||
|
|
||||||
this.sockpuppet = new Sockpuppet(
|
this.sockpuppet = new Sockpuppet(
|
||||||
"ws://sockpuppet.cyborggrizzly.com",
|
"ws://sockpuppet.cyborggrizzly.com",
|
||||||
() => {
|
() => {
|
||||||
|
this.sockpuppet.createChannel(this._channelId);
|
||||||
this.sockpuppet.joinChannel(this.channelId, async (msg) => {
|
this.sockpuppet.joinChannel(this.channelId, async (msg) => {
|
||||||
if (msg === 'log' && !IS_BROWSER) {
|
if (msg === "log" && !IS_BROWSER) {
|
||||||
const log = await Deno.readTextFile('./server/logs/latest.log');
|
try {
|
||||||
|
const log = await Deno.readTextFile("./server/logs/latest.log");
|
||||||
this.channel?.send(log);
|
this.channel?.send(log);
|
||||||
} else
|
} catch {
|
||||||
|
1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(msg)
|
||||||
this.sendStdIn(msg);
|
this.sendStdIn(msg);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -79,7 +88,6 @@ class ServerState {
|
|||||||
await this.stdin.write(msg);
|
await this.stdin.write(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// "instance" should be moved to a private member once multi-instance support is implemented
|
// "instance" should be moved to a private member once multi-instance support is implemented
|
||||||
public startMCServer(instance = "server") {
|
public startMCServer(instance = "server") {
|
||||||
this.command = new Deno.Command("java", {
|
this.command = new Deno.Command("java", {
|
||||||
@ -94,14 +102,23 @@ class ServerState {
|
|||||||
stdout: "piped",
|
stdout: "piped",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.channel?.send('clear');
|
||||||
|
|
||||||
this.process = this.command.spawn();
|
this.process = this.command.spawn();
|
||||||
|
|
||||||
const {readable, writable} = new TransformStream();
|
const { readable, writable } = new TransformStream();
|
||||||
readable.pipeTo(this.process.stdin);
|
readable.pipeTo(this.process.stdin);
|
||||||
this.stdin = writable.getWriter();
|
this.stdin = writable.getWriter();
|
||||||
|
|
||||||
this._status = "running";
|
this._status = "running";
|
||||||
|
const statusEvent = new CustomEvent('serverstatuschange', {detail: this._status});
|
||||||
|
|
||||||
|
globalThis.dispatchEvent(statusEvent);
|
||||||
this.startStream();
|
this.startStream();
|
||||||
|
// this.process.status.then(() => {
|
||||||
|
// this._status = "stopped";
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async startStream() {
|
private async startStream() {
|
||||||
@ -120,25 +137,54 @@ class ServerState {
|
|||||||
}
|
}
|
||||||
if (done) break;
|
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() {
|
public gracefullyStopMCServer() {
|
||||||
this._status = "stopped";
|
this._status = "stopped";
|
||||||
this.sendStdIn("stop");
|
this.sendStdIn("stop");
|
||||||
|
const statusEvent = new CustomEvent('serverstatuschange', {detail: this._status});
|
||||||
|
|
||||||
|
globalThis.dispatchEvent(statusEvent);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public forceStopMCServer() {
|
public forceStopMCServer() {
|
||||||
this._status = "stopped";
|
this._status = "stopped";
|
||||||
this.process.kill();
|
this.process.kill();
|
||||||
|
const statusEvent = new CustomEvent('serverstatuschange', {detail: this._status});
|
||||||
|
|
||||||
|
globalThis.dispatchEvent(statusEvent);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async restartMCServer() {
|
public async restartMCServer() {
|
||||||
if (this.status === "running") {
|
if (this.status === "running") {
|
||||||
await this.sendStdIn("stop");
|
await this.sendStdIn("stop");
|
||||||
|
const statusEvent = new CustomEvent('serverstatuschange', {detail: 'restarting'});
|
||||||
|
|
||||||
|
globalThis.dispatchEvent(statusEvent);
|
||||||
|
|
||||||
// while (true) {
|
// while (true) {
|
||||||
await this.process.status;
|
await this.process.status;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statusEvent = new CustomEvent('serverstatuschange', {detail: this._status});
|
||||||
|
|
||||||
|
globalThis.dispatchEvent(statusEvent);
|
||||||
|
|
||||||
this.startMCServer();
|
this.startMCServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
tsconfig.json
Normal file
3
tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"include": ["./types.d.ts"]
|
||||||
|
}
|
6
types.d.ts
vendored
Normal file
6
types.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// deno-lint-ignore-file no-var
|
||||||
|
export {}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
var statusSource: EventSource;
|
||||||
|
}
|
10
util/EULA.ts
10
util/EULA.ts
@ -1,8 +1,14 @@
|
|||||||
import { IS_BROWSER } from "$fresh/runtime.ts";
|
import { IS_BROWSER } from "$fresh/runtime.ts";
|
||||||
|
|
||||||
const eulaRegex = /(eula=false)/;
|
const eulaRegex = /(eula=false)/;
|
||||||
export const checkEULA = (instance = "server") =>
|
export const checkEULA = (instance = "server") => {
|
||||||
!IS_BROWSER && !eulaRegex.test(Deno.readTextFileSync(`./${instance}/eula.txt`));
|
try {
|
||||||
|
return !IS_BROWSER &&
|
||||||
|
!eulaRegex.test(Deno.readTextFileSync(`./${instance}/eula.txt`));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const acceptEULA = (instance = "server") => {
|
export const acceptEULA = (instance = "server") => {
|
||||||
const eula = Deno.readTextFileSync(`./${instance}/eula.txt`);
|
const eula = Deno.readTextFileSync(`./${instance}/eula.txt`);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user