From 4a4563ba851aaaebf22a78f2a13e3b50a9a1b592 Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 3 Oct 2023 02:57:35 -0600 Subject: [PATCH] server state and stdio streaming --- .gitignore | 1 + components/Button.tsx | 2 +- components/Loader.tsx | 10 +++++ deno.json | 4 +- fresh.gen.ts | 26 ++++++++---- islands/Counter.tsx | 16 ------- islands/fabricVersions.tsx | 79 +++++++++++++++++++++++++++++++++++ islands/terminal.tsx | 48 +++++++++++++++++++++ routes/_404.tsx | 2 +- routes/_app.tsx | 14 +++---- routes/api/joke.ts | 21 ---------- routes/api/terminal.ts | 1 + routes/setup/eula.tsx | 48 +++++++++++++++++++++ routes/setup/fabric.tsx | 22 ++++++++++ routes/setup/index.tsx | 6 +-- routes/setup/start.tsx | 27 ++++++++++++ state/serverState.ts | 85 ++++++++++++++++++++++++++++++++++++++ static/styles/main.css | 48 +++++++++++++++++++++ static/styles/tailwind.css | 2 +- steps/0_init.ts | 21 ---------- types/fabric.ts | 19 +++++++++ util/EULA.ts | 11 +++++ util/initFabric.ts | 27 ++++++++++++ util/startServer.ts | 17 ++++++++ util/startTail.ts | 25 +++++++++++ 25 files changed, 502 insertions(+), 80 deletions(-) create mode 100644 components/Loader.tsx delete mode 100644 islands/Counter.tsx create mode 100644 islands/fabricVersions.tsx create mode 100644 islands/terminal.tsx delete mode 100644 routes/api/joke.ts create mode 100644 routes/api/terminal.ts create mode 100644 routes/setup/eula.tsx create mode 100644 routes/setup/fabric.tsx create mode 100644 routes/setup/start.tsx create mode 100644 state/serverState.ts delete mode 100644 steps/0_init.ts create mode 100644 types/fabric.ts create mode 100644 util/EULA.ts create mode 100644 util/initFabric.ts create mode 100644 util/startServer.ts create mode 100644 util/startTail.ts diff --git a/.gitignore b/.gitignore index c167189..9abd735 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ _fresh/ fabric/ forge/ vanilla/ +server/ # Config file for MCGRIZZ mcgrizz.json \ No newline at end of file diff --git a/components/Button.tsx b/components/Button.tsx index 5cfbdc9..1492ab3 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -42,7 +42,7 @@ export function Button( return ( -

{props.count}

- - - ); -} diff --git a/islands/fabricVersions.tsx b/islands/fabricVersions.tsx new file mode 100644 index 0000000..005210f --- /dev/null +++ b/islands/fabricVersions.tsx @@ -0,0 +1,79 @@ +import { useEffect, useState } from "preact/hooks"; +import { FabricGame, FabricInstaller, FabricLoader } from "../types/fabric.ts"; +import { Loader } from "../components/Loader.tsx"; +import { Button } from "../components/Button.tsx"; + +export function FabricVersions() { + const [game, setGame] = useState([]); + const [installer, setInstaller] = useState([]); + const [loader, setLoader] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + const [includeSnapshots, setIncludeSnapshots] = useState(false); + + useEffect(() => { + let poll: number; + if (isLoading) { + poll = setInterval(async () => { + const res = await fetch("/api/fabric"); + if (res.status !== 200) return; + const json: { + gameVersions: FabricGame[]; + installerVersions: FabricInstaller[]; + loaderVersions: FabricLoader[]; + } = await res.json(); + setGame(json.gameVersions); + setInstaller(json.installerVersions); + setLoader(json.loaderVersions); + setIsLoading(false); + }, 500); + } + + return () => clearInterval(poll); + }, [isLoading]); + + return isLoading ? : ( +
+
+
+ + +
+ +
+
+ + + + You probably don't need to change this one. + +
+
+ + + + You probably don't need to change this one. + +
+
+
+ +
+
+ ); +} diff --git a/islands/terminal.tsx b/islands/terminal.tsx new file mode 100644 index 0000000..73ac6ca --- /dev/null +++ b/islands/terminal.tsx @@ -0,0 +1,48 @@ +import { IS_BROWSER } from "$fresh/runtime.ts"; +import { useEffect, useRef, useState } from "preact/hooks"; +import { Sockpuppet } from "puppet/client"; + +export function Terminal(props: { channelId: string }) { + const puppet = useRef(new Sockpuppet("ws://sockpuppet.cyborggrizzly.com")); + const [lines, setLines] = useState([]); + const divRef = useRef(null); + + const [command, setCommand] = useState(""); + + useEffect(() => { + if (!IS_BROWSER) return; + puppet.current.joinChannel(props.channelId, (line) => { + setLines((l) => [...l, line]); + }); + }, []); + + useEffect(() => { + if (divRef.current) { + divRef.current.scrollTop = divRef.current.scrollHeight; + } + }, [lines]); + + const sendCommand = (e: Event) => { + e.preventDefault(); + puppet.current.getChannel(props.channelId)?.send(command); + setCommand(""); + }; + + return ( +
+
+ {lines.map((l) =>
{l}
)} +
+
+
+ setCommand((e.target as HTMLInputElement).value)} + /> +
+
+
+ ); +} diff --git a/routes/_404.tsx b/routes/_404.tsx index 6925a37..9df1bd8 100644 --- a/routes/_404.tsx +++ b/routes/_404.tsx @@ -9,7 +9,7 @@ export default function Error404() {
a gif of a bear playing with the camera diff --git a/routes/_app.tsx b/routes/_app.tsx index 69f55bc..56b31a6 100644 --- a/routes/_app.tsx +++ b/routes/_app.tsx @@ -7,7 +7,7 @@ export default function App({ Component }: AppProps) { - + MC GRIZZ
-
+
- +

MC Grizz


@@ -36,11 +36,11 @@ export default function App({ Component }: AppProps) {
-
+
diff --git a/routes/api/joke.ts b/routes/api/joke.ts deleted file mode 100644 index a3f4243..0000000 --- a/routes/api/joke.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HandlerContext } from "$fresh/server.ts"; - -// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/ -const JOKES = [ - "Why do Java developers often wear glasses? They can't C#.", - "A SQL query walks into a bar, goes up to two tables and says “can I join you?”", - "Wasn't hard to crack Forrest Gump's password. 1forrest1.", - "I love pressing the F5 key. It's refreshing.", - "Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”", - "There are 10 types of people in the world. Those who understand binary and those who don't.", - "Why are assembly programmers often wet? They work below C level.", - "My favourite computer based band is the Black IPs.", - "What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.", - "An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.", -]; - -export const handler = (_req: Request, _ctx: HandlerContext): Response => { - const randomIndex = Math.floor(Math.random() * JOKES.length); - const body = JOKES[randomIndex]; - return new Response(body); -}; diff --git a/routes/api/terminal.ts b/routes/api/terminal.ts new file mode 100644 index 0000000..973f400 --- /dev/null +++ b/routes/api/terminal.ts @@ -0,0 +1 @@ +// const \ No newline at end of file diff --git a/routes/setup/eula.tsx b/routes/setup/eula.tsx new file mode 100644 index 0000000..3776579 --- /dev/null +++ b/routes/setup/eula.tsx @@ -0,0 +1,48 @@ +import { Handlers, PageProps } from "$fresh/server.ts"; +import { Button } from "../../components/Button.tsx"; +import { Content } from "../../components/Content.tsx"; +import { SERVER_STATE } from "../../state/serverState.ts"; + +export const handler: Handlers = { + async POST(req, _ctx) { + const url = new URL(req.url); + SERVER_STATE.acceptEULA(); + return Response.redirect(url.origin + '/setup/start'); + } +}; + +export default async function EULA({url}: PageProps) { + // TODO: this does not respect instances and needs to once they are supported + const eulaText = await Deno.readTextFileSync("./server/eula.txt"); + + return ( +
+ +

Minecraft EULA

+

+ This page is provided as a courtesy. The text below is the contents of + "eula.txt," a file that is generated by the server the first time it + runs. In order to run the server, you must agree to the EULA by + changing "false" to "true." +

+

+ To make this easier, there is a button below. The one and only action + this button takes is to change the value in the text file to true, + this is not an agreement between you and CyborgGrizzly Games. +

+

+ If you are unsure of the contents of the EULA, you are encouraged to + read it by following the link in the text file. +

+
{eulaText}
+
+
+ +
+
+
+
+ ); +} diff --git a/routes/setup/fabric.tsx b/routes/setup/fabric.tsx new file mode 100644 index 0000000..4dd6f76 --- /dev/null +++ b/routes/setup/fabric.tsx @@ -0,0 +1,22 @@ +import { Content } from "../../components/Content.tsx"; +import { FabricVersions } from "../../islands/fabricVersions.tsx"; +import { initFabric } from "../../util/initFabric.ts"; + +export default function FabricSetup() { + try { + const vers = Deno.readDirSync("./fabric/versions"); + if (Array.from(vers).length !== 3) throw ""; + } catch { + initFabric(); + } + + return ( +
+ +

Fabric Setup

+

Select the game version you wish to create a server for. If you know you need a different loader and installer version, you can change it here.

+ +
+
+ ); +} diff --git a/routes/setup/index.tsx b/routes/setup/index.tsx index 1961147..0428c16 100644 --- a/routes/setup/index.tsx +++ b/routes/setup/index.tsx @@ -10,13 +10,13 @@ export default function Setup() { diff --git a/routes/setup/start.tsx b/routes/setup/start.tsx new file mode 100644 index 0000000..2b9a622 --- /dev/null +++ b/routes/setup/start.tsx @@ -0,0 +1,27 @@ +import { Handlers } from "$fresh/server.ts"; +import { Content } from "../../components/Content.tsx"; +import { Terminal } from "../../islands/terminal.tsx"; +import { SERVER_STATE } from "../../state/serverState.ts"; + +export const handler: Handlers = { + async GET(req,ctx) { + if (!SERVER_STATE.eulaAccepted) { + const url = new URL(req.url); + return Response.redirect(`${url.origin}/setup/eula`) + } + + const res = await ctx.render(); + return res; + } +} + +export default function StartFabric() { + SERVER_STATE.startMCServer(); + return ( +
+ + + +
+ ); +} diff --git a/state/serverState.ts b/state/serverState.ts new file mode 100644 index 0000000..b3c105e --- /dev/null +++ b/state/serverState.ts @@ -0,0 +1,85 @@ +import { Tail } from "npm:tail"; +import { Sockpuppet } from "puppet/client"; +import { acceptEULA, checkEULA } from "../util/EULA.ts"; + +class ServerState { + private status: "running" | "stopped" = "stopped"; + + private command!: Deno.Command; + private process!: Deno.ChildProcess; + + private tail: Tail; + + private _eulaAccepted: boolean; + + private sockpuppet!: Sockpuppet; + private _channelId = "blanaba"; + public get channelId () { + return this._channelId; + } + public get eulaAccepted() { + return this._eulaAccepted; + } + + constructor() { + this._eulaAccepted = checkEULA(); + this.sockpuppet = new Sockpuppet( + "ws://sockpuppet.cyborggrizzly.com", + () => { + this.sockpuppet.joinChannel(this.channelId, (msg) => {this.sendStdIn(msg)}); + }, + ); + } + + public get stdin() { + return this.process.stdin; + } + + public get channel() { + return this.sockpuppet.getChannel(this.channelId) + } + + public sendStdIn(message: string) { + const msg = new TextEncoder().encode(message); + this.process.stdin.getWriter().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", { + args: [ + "-Xmx2G", + "-jar", + "./server.jar", + "nogui", + ], + cwd: "./" + instance, + stdin: "piped", + stdout: "piped", + }); + + this.process = this.command.spawn(); + + this.startStream(); + } + + private async startStream() { + const stream = this.process.stdout.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const {done, value} = await stream.read(); + if (value) { + this.channel?.send(decoder.decode(value)); + } + if (done) break; + } + } + + public acceptEULA() { + this._eulaAccepted = true; + acceptEULA(); + } +} + +export const SERVER_STATE = new ServerState(); diff --git a/static/styles/main.css b/static/styles/main.css index 696af7c..dfa6d64 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -30,3 +30,51 @@ * { font-family: 'Titillium Web', sans-serif; } + +@layer components { + select, input { + @apply text-black p-2 rounded-xl + } +} + +@layer utilities { + .loader-ball { + animation: color-cycle infinite 3s linear; + animation-delay: var(--loader-delay); + @apply m-auto + } + + .loader { + animation: spin 5s ease-in-out; + animation-iteration-count: infinite; + animation-direction: alternate; + } + + @keyframes spin { + 0% { + transform: rotate(0deg) + } + 100% { + transform: rotate(1440deg) + } + } + + @keyframes color-cycle { + 0% { + @apply bg-fire; + } + 25% { + @apply bg-grape; + } + 50% { + @apply bg-sky; + } + 75% { + @apply bg-wasabi; + } + 100% { + @apply bg-fire; + } + } +} + diff --git a/static/styles/tailwind.css b/static/styles/tailwind.css index 845b60b..676c97d 100644 --- a/static/styles/tailwind.css +++ b/static/styles/tailwind.css @@ -1,2 +1,2 @@ @font-face{font-family:Minecraft;font-style:normal;font-weight:400;src:url(/fonts/minecraft/MinecraftRegular.otf)}@font-face{font-family:Minecraft;font-style:normal;font-weight:700;src:url(/fonts/minecraft/MinecraftBold.otf)}@font-face{font-family:Minecraft;font-style:italic;font-weight:700;src:url(/fonts/minecraft/MinecraftBoldItalic.otf)}@font-face{font-family:Minecraft;font-style:italic;font-weight:400;src:url(/fonts/minecraft/MinecraftItalic.otf)} -/*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{margin-left:auto;margin-right:auto;width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.bottom-1{bottom:.25rem}.right-2{right:.5rem}.top-1{top:.25rem}.left-2{left:.5rem}.bottom-0{bottom:0}.top-0{top:0}.right-0{right:0}.-bottom-1{bottom:-.25rem}.right-1{right:.25rem}.-bottom-2{bottom:-.5rem}.-bottom-4{bottom:-1rem}.m-auto{margin:auto}.mx-auto{margin-left:auto;margin-right:auto}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mb-8{margin-bottom:2rem}.mt-auto{margin-top:auto}.mt-16{margin-top:4rem}.ml-auto{margin-left:auto}.mt-2{margin-top:.5rem}.mr-auto{margin-right:auto}.mt-8{margin-top:2rem}.mt-4{margin-top:1rem}.block{display:block}.flex{display:flex}.h-\[100vh\]{height:100vh}.h-full{height:100%}.h-16{height:4rem}.max-h-full{max-height:100%}.w-full{width:100%}.w-16{width:4rem}.min-w-\[400px\]{min-width:400px}.max-w-screen-md{max-width:768px}.flex-1{flex:1 1 0%}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-4{gap:1rem}.gap-8{gap:2rem}.overflow-y-auto{overflow-y:auto}.rounded-3xl{border-radius:1.5rem}.rounded-xl{border-radius:.75rem}.rounded-md{border-radius:.375rem}.border-4{border-width:4px}.border-2{border-width:2px}.border{border-width:1px}.border-r-2{border-right-width:2px}.border-sky{--tw-border-opacity:1;border-color:rgb(49 167 230/var(--tw-border-opacity))}.border-licorice-800{--tw-border-opacity:1;border-color:rgb(150 60 151/var(--tw-border-opacity))}.border-sky-800{--tw-border-opacity:1;border-color:rgb(17 82 123/var(--tw-border-opacity))}.border-grape-800{--tw-border-opacity:1;border-color:rgb(109 33 135/var(--tw-border-opacity))}.border-sky-950{--tw-border-opacity:1;border-color:rgb(13 44 68/var(--tw-border-opacity))}.bg-licorice{--tw-bg-opacity:1;background-color:rgb(22 10 22/var(--tw-bg-opacity))}.bg-smoke-800{--tw-bg-opacity:1;background-color:rgb(65 65 75/var(--tw-bg-opacity))}.bg-sky{--tw-bg-opacity:1;background-color:rgb(49 167 230/var(--tw-bg-opacity))}.bg-grape{--tw-bg-opacity:1;background-color:rgb(64 10 80/var(--tw-bg-opacity))}.bg-smoke-900{--tw-bg-opacity:1;background-color:rgb(57 57 65/var(--tw-bg-opacity))}.bg-smoke{--tw-bg-opacity:1;background-color:rgb(24 24 27/var(--tw-bg-opacity))}.bg-smoke-200{--tw-bg-opacity:1;background-color:rgb(217 217 222/var(--tw-bg-opacity))}.p-4{padding:1rem}.p-8{padding:2rem}.p-2{padding:.5rem}.p-1{padding:.25rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.text-center{text-align:center}.text-right{text-align:right}.font-pixel{font-family:Minecraft,cursive}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-6xl{font-size:3.75rem;line-height:1}.text-5xl{font-size:3rem;line-height:1}.text-7xl{font-size:4.5rem;line-height:1}.font-bold{font-weight:700}.text-sky{--tw-text-opacity:1;color:rgb(49 167 230/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-smoke-700{--tw-text-opacity:1;color:rgb(76 77 88/var(--tw-text-opacity))}.text-smoke-700\/10{color:#4c4d581a}.text-smoke-900\/10{color:#3939411a}.text-smoke-950\/10{color:#18181b1a}.text-smoke-950\/20{color:#18181b33}.text-smoke-950\/30{color:#18181b4d}.text-smoke-800\/30{color:#41414b4d}.underline{text-decoration-line:underline}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}*{font-family:Titillium Web,sans-serif}.even\:bg-black\/20:nth-child(2n),.even\:bg-black\/\[\.2\]:nth-child(2n){background-color:#0003}.even\:bg-black\/10:nth-child(2n){background-color:#0000001a}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:bg-smoke-500:hover{--tw-bg-opacity:1;background-color:rgb(115 117 132/var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){.dark\:border-sky-950{--tw-border-opacity:1;border-color:rgb(13 44 68/var(--tw-border-opacity))}.dark\:bg-smoke{--tw-bg-opacity:1;background-color:rgb(24 24 27/var(--tw-bg-opacity))}.dark\:bg-smoke-900{--tw-bg-opacity:1;background-color:rgb(57 57 65/var(--tw-bg-opacity))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}} \ No newline at end of file +/*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{margin-left:auto;margin-right:auto;width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}input,select{--tw-text-opacity:1;border-radius:.75rem;color:rgb(0 0 0/var(--tw-text-opacity));padding:.5rem}.absolute{position:absolute}.relative{position:relative}.bottom-4{bottom:1rem}.left-0{left:0}.-bottom-1{bottom:-.25rem}.right-0{right:0}.m-auto{margin:auto}.mx-auto{margin-left:auto;margin-right:auto}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mb-8{margin-bottom:2rem}.ml-auto{margin-left:auto}.mt-8{margin-top:2rem}.mt-4{margin-top:1rem}.mt-16{margin-top:4rem}.block{display:block}.flex{display:flex}.grid{display:grid}.contents{display:contents}.h-\[100vh\]{height:100vh}.h-full{height:100%}.h-\[600px\]{height:600px}.h-20{height:5rem}.h-5{height:1.25rem}.h-16{height:4rem}.max-h-full{max-height:100%}.w-full{width:100%}.w-20{width:5rem}.w-5{width:1.25rem}.w-16{width:4rem}.min-w-\[400px\]{min-width:400px}.max-w-screen-md{max-width:768px}.flex-1{flex:1 1 0%}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-4{gap:1rem}.gap-8{gap:2rem}.overflow-auto{overflow:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded-3xl{border-radius:1.5rem}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-4{border-width:4px}.border-r-2{border-right-width:2px}.border-sky{--tw-border-opacity:1;border-color:rgb(49 167 230/var(--tw-border-opacity))}.border-sky-950{--tw-border-opacity:1;border-color:rgb(13 44 68/var(--tw-border-opacity))}.border-licorice-800{--tw-border-opacity:1;border-color:rgb(150 60 151/var(--tw-border-opacity))}.border-sky-800{--tw-border-opacity:1;border-color:rgb(17 82 123/var(--tw-border-opacity))}.border-grape-800{--tw-border-opacity:1;border-color:rgb(109 33 135/var(--tw-border-opacity))}.bg-licorice{--tw-bg-opacity:1;background-color:rgb(22 10 22/var(--tw-bg-opacity))}.bg-smoke-900{--tw-bg-opacity:1;background-color:rgb(57 57 65/var(--tw-bg-opacity))}.bg-grape{--tw-bg-opacity:1;background-color:rgb(64 10 80/var(--tw-bg-opacity))}.bg-smoke-500{--tw-bg-opacity:1;background-color:rgb(115 117 132/var(--tw-bg-opacity))}.bg-smoke-600{--tw-bg-opacity:1;background-color:rgb(93 94 108/var(--tw-bg-opacity))}.bg-sky{--tw-bg-opacity:1;background-color:rgb(49 167 230/var(--tw-bg-opacity))}.bg-smoke-200{--tw-bg-opacity:1;background-color:rgb(217 217 222/var(--tw-bg-opacity))}.p-4{padding:1rem}.p-8{padding:2rem}.p-2{padding:.5rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.text-center{text-align:center}.text-right{text-align:right}.font-pixel{font-family:Minecraft,cursive}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-6xl{font-size:3.75rem;line-height:1}.text-5xl{font-size:3rem;line-height:1}.font-bold{font-weight:700}.text-sky{--tw-text-opacity:1;color:rgb(49 167 230/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-smoke-800\/30{color:#41414b4d}.underline{text-decoration-line:underline}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.loader-ball{animation:color-cycle 3s linear infinite;animation-delay:var(--loader-delay);margin:auto}.loader{animation:spin 5s ease-in-out;animation-direction:alternate;animation-iteration-count:infinite}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(4turn)}}@keyframes color-cycle{0%{--tw-bg-opacity:1;background-color:rgb(230 28 28/var(--tw-bg-opacity))}25%{--tw-bg-opacity:1;background-color:rgb(64 10 80/var(--tw-bg-opacity))}50%{--tw-bg-opacity:1;background-color:rgb(49 167 230/var(--tw-bg-opacity))}75%{--tw-bg-opacity:1;background-color:rgb(128 134 39/var(--tw-bg-opacity))}to{--tw-bg-opacity:1;background-color:rgb(230 28 28/var(--tw-bg-opacity))}}*{font-family:Titillium Web,sans-serif}.even\:bg-black\/10:nth-child(2n){background-color:#0000001a}.hover\:bg-smoke-500:hover{--tw-bg-opacity:1;background-color:rgb(115 117 132/var(--tw-bg-opacity))}@media (prefers-color-scheme:dark){.dark\:border-sky-950{--tw-border-opacity:1;border-color:rgb(13 44 68/var(--tw-border-opacity))}.dark\:bg-smoke{--tw-bg-opacity:1;background-color:rgb(24 24 27/var(--tw-bg-opacity))}.dark\:bg-smoke-900{--tw-bg-opacity:1;background-color:rgb(57 57 65/var(--tw-bg-opacity))}.dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}} \ No newline at end of file diff --git a/steps/0_init.ts b/steps/0_init.ts deleted file mode 100644 index f0d210c..0000000 --- a/steps/0_init.ts +++ /dev/null @@ -1,21 +0,0 @@ -const fabricHost = 'https://meta.fabricmc.net/v2' - -const gameVersionSlug = '/versions/game' -const loaderVersionSlug = '/versions/loader' -const installerVersionSlug = '/versions/installer' -const versionSlugs = [gameVersionSlug,loaderVersionSlug,installerVersionSlug]; - -for (const slug of versionSlugs) { - const uri = fabricHost + slug; - - const req = await fetch(uri); - const text = await req.text(); - - const path = `./fabric/${slug}.json`; - - await Deno.mkdir(path.split('/').slice(0,-1).join('/'), {recursive: true}); - - await Deno.writeTextFile(path, text, {create: true}); -} - - diff --git a/types/fabric.ts b/types/fabric.ts new file mode 100644 index 0000000..a5a98c5 --- /dev/null +++ b/types/fabric.ts @@ -0,0 +1,19 @@ +export type FabricGame = { + version: string; + stable: boolean; +}; + +export type FabricInstaller = { + url: string; + maven: string; + version: string; + stable: boolean; +}; + +export type FabricLoader = { + separator: string; + build: number; + maven: string; + version: string; + stable: boolean; +}; diff --git a/util/EULA.ts b/util/EULA.ts new file mode 100644 index 0000000..f08d5d1 --- /dev/null +++ b/util/EULA.ts @@ -0,0 +1,11 @@ +const eulaRegex = /(eula=false)/; +export const checkEULA = (instance = 'server') => + !eulaRegex.test(Deno.readTextFileSync(`./${instance}/eula.txt`)); + + +export const acceptEULA = (instance = 'server') => { + const eula = Deno.readTextFileSync(`./${instance}/eula.txt`); + const mod = eula.replace(eulaRegex, 'eula=true'); + console.log(mod) + Deno.writeTextFileSync(`./${instance}/eula.txt`, mod); +} \ No newline at end of file diff --git a/util/initFabric.ts b/util/initFabric.ts new file mode 100644 index 0000000..23da6fc --- /dev/null +++ b/util/initFabric.ts @@ -0,0 +1,27 @@ +export async function initFabric() { + const fabricHost = "https://meta.fabricmc.net/v2"; + + const gameVersionSlug = "/versions/game"; + const loaderVersionSlug = "/versions/loader"; + const installerVersionSlug = "/versions/installer"; + const versionSlugs = [ + gameVersionSlug, + loaderVersionSlug, + installerVersionSlug, + ]; + + for (const slug of versionSlugs) { + const uri = fabricHost + slug; + + const req = await fetch(uri); + const text = await req.text(); + + const path = `./fabric/${slug}.json`; + + await Deno.mkdir(path.split("/").slice(0, -1).join("/"), { + recursive: true, + }); + + await Deno.writeTextFile(path, text, { create: true }); + } +} diff --git a/util/startServer.ts b/util/startServer.ts new file mode 100644 index 0000000..4ae21a5 --- /dev/null +++ b/util/startServer.ts @@ -0,0 +1,17 @@ +import { startTail } from "./startTail.ts"; + +export async function startMCServer(instance = "server") { + const start = new Deno.Command("java", { + args: [ + "-Xmx2G", + "-jar", + "./server.jar", + "nogui", + ], + cwd: "./" + instance, + }); + + start.output(); + + return await startTail(instance); +} diff --git a/util/startTail.ts b/util/startTail.ts new file mode 100644 index 0000000..b6fd1a0 --- /dev/null +++ b/util/startTail.ts @@ -0,0 +1,25 @@ +import { Sockpuppet } from "puppet/client"; +import { Tail } from "npm:tail"; + +export function startTail(instance = "server") { + return new Promise<[Tail, string]>((resolve) => { + const puppet = new Sockpuppet("ws://sockpuppet.cyborggrizzly.com", () => { + const channelId = crypto.randomUUID(); + puppet.createChannel(channelId); + puppet.joinChannel(channelId, (line) => { + console.log(line); + }); + + setTimeout(() => { + const path = `./${instance}/logs/latest.log`; + const tail = new Tail(path, { follow: true }); + + tail.on("line", (line: string) => { + puppet.getChannel(channelId)?.send(line); + console.log(line); + }); + resolve([tail, channelId]); + }, 1000); + }); + }); +}