-
+
MC Grizz
@@ -36,11 +36,11 @@ export default function App({ Component }: AppProps) {
-
- Made with love by Emma@Cyborggrizzly 🏳️⚧️❤️
-
+
+ Made with love by Emma@Cyborggrizzly 🏳️⚧️❤️
+
-
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);
+ });
+ });
+}