Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(docs): add starting tutorial.md #1859

Merged
merged 9 commits into from
Mar 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
EXTERNAL_HOST=
CERTIFICATE_FILE=
PRIVATE_KEY_FILE=
PUBLIC_KEY_FILE=
101 changes: 101 additions & 0 deletions STARTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Getting started as a user

Documention type: Tutorial

Last updated: 2003c90b4aecc5150116e83a0f14d18f44bf3536

---

This project isn't really geared towards an player of the game, but if you want to try it out, this is how to do so:

## Requirements

// TODO: Link to the install docs for each item

- Internet
- Linux
- Git
- Docker
- Docker Compose
- NodeJS (v20.x - v21.x)
- The debug copy of the game

## Setup

After getting the requirements above ready, perform the following steps:

### On the computer where you are running the server

1. Open a terminal or command prompt
2. Type `git clone https://github.com/rustymotors/server.git rusty-server` and press enter
3. Type `cd rusty-server` and press enter
4. Type `./mcos/pull_nginx_image.sh` and press enter (this step may take a while, depending on your internet speed)
5. Type `docker-compose up -d` and press enter
6. Type `corepack enable pnpm` and press enter
7. Type `pnpm install` and press enter
8. Type `pnpm run build` and press enter
9. Type `cp .env.example .env` and press enter
10. Open the `.env` file with your favorite text editing program (the simpler the better) and edit the values as follows:
a. `EXTERNAL_HOST=` - Add the hostname or ip of the computer you are running this server on. This value MUST be reachable from where you install the game.
b. `CERTIFICATE_FILE=` - The relitive path from the `rusty-server` folder to the SSL certificate. If you want to use the one I have included with the repository (highly recomended), the value will be `data/mcouniverse.crt`
c. `PRIVATE_KEY_FILE` - The path to the private key. Use `data/private_key.pem` for the working copy
d. `PUBLIC_KEY_FILE` - The path to the public key. Use `data/pub.key` for the working copy
11. Type `npm start` and press enter
12. If the last message on your screen is `Server listening at http://0.0.0.0:3000`, then the server is running!

### On the computer where you are running the game

1. Open a web browser
2. In the address bar, type `<enternal_ip>/cert` where \<enternal_ip\> is the value you saved in the .env file on the server. Do not include the brackets, and press enter
3. Some text should load. The first line should contain `BEGIN CERTIFICATE`
4. Click "Save" when asked if you want to open or save
5. Select the folder you want to save in (I like the Desktop), but before you press "save", make sure to edit the file name so it says `"cert.crt"`. THE QUOTES ARE IMPORTANT
6. Sorry for the caps. Click "Save" if you haven't already
7. In the folder where you saved the certificate, you should see a file named "cert" with an icon that looks like a certificate. Double-click on it.

---

## 7.5. From this point on, I'm going to tell you to do a bunch of things that Windows will warn you NOT to do. This is because in order to have the game connect to our server we have to use some very old security methods that are no longer advised to be used. If you don't trust me, that's fine. But it won't work otherwise. Let's proceed.

Check notice on line 58 in STARTING.md

View check run for this annotation

Trunk.io / Trunk Check

markdownlint(MD026)

[new] Trailing punctuation in heading

8. We are looking at a windows that says "The CA Root Certificate is not trusted". Press "Install Certificate"
9. Click "Next"
10. You are now on a screen that asks where you want to save this certificate.
11. Click on the "Place all certificates in the following store" and click "browse"
12. Select "Trusted Roor Certification Authorities". This should be the second one down.
13. Click "Ok"
14. Click "Next"
15. Click "Finish"
16 You now have a window warning you that you are installing a untrusted root certificate that is unknown to Windows. Click "Yes"
16. Click "ok" and then click "Ok" again.
17. Return to your web browser.
18. In the address bar, type `<enternal_ip>/key` where \<enternal_ip\> is the value you saved in the .env file on the server. Do not include the brackets, and press enter
19. A long string of letters and numbers should load.
20. Click "Save" when asked if you want to open or save
21. Select the folder you want to save in (I like the Desktop), but before you press "save", make sure to edit the file name so it says `"pub.key"`. Again, make sure to include the quotes
22. Click "Save"
23. Copy the file you just saved to your game install folder. Tell Wndows it is ok to overwrite the existing file of the same name.
24. Return to your web browser
25. In the address bar, type `<enternal_ip>/registry` where \<enternal_ip\> is the value you saved in the .env file on the server. Do not include the brackets, and press enter
26. Some text will load. The first line should contain "Windows Registry Editor"
27. Click "Save" when asked if you want to open or save
28. Select the folder you want to save in (I like the Desktop), but before you press "save", make sure to edit the file name so it says `"client.reg"`. Again, make sure to include the quotes
29. Click "Save"

30. This next step has a slight change, depending on what version of Windows you have installed the game under
a. If you are using Windows XP (or lower), you will need to edit this file we just save. To do so, right-click on the saved file and choose "edit"
If Windows prompts you that the file is not signed, click "open".
Every line that starts `HKEY_LOCAL_MACHINE\Software\WOW6432Node\`, change to say `HKEY_LOCAL_MACHINE\Software\`. Leve the rest of the line. Save.
b. If you are using a version of Windows that is newer then Windows XP, no action is needed here
31. Double-click on the saved file.
32. f Windows prompts you that the file is not signed, click "open".
33. Windows will ask you if you are sure you want to import this registry file. Click "yes"
34. Click "Ok"

## Running

If you haven't followed all the steps under the Setup section, this will probably not work well

37. Double-click the debug copy of the game
38. There are two pre-created logins, depending on what part you to see:
a. Username: "admin", Password: "admin" - No existing account, profile creation possible (not playabl, even if it says it was created)
b. Username: "molly", Password: "molly" - Existing account, can login to the server and reach the lobby. Any activities past that probably won't work.
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
db:
image: postgres:latest@sha256:6b841c8f6a819884207402f1209a8116844365df15fca8cf556fc54a24c70800
image: postgres:latest
restart: always
environment:
POSTGRES_DB: rm
Expand All @@ -25,7 +25,7 @@ services:
- no-new-privileges:true

adminer:
image: adminer@sha256:b75eae89431e8469613b844e76382a26efc8601c17f446bcd81665bc87ca9a1f
image: adminer
restart: always
ports:
- 8080:8080
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rusty-motors",
"version": "1.0.1",
"version": "0.5.0",
"description": "This is a game server, being written from scratch, for a very old and long dead game. The owners of said game have shown no interest in bringing it back, but even so all names of their IP have been avoided to prevent issues.",
"type": "module",
"exports": {
Expand Down Expand Up @@ -44,11 +44,11 @@
"@rustymotors/shared": "workspace:^",
"@rustymotors/shared-packets": "workspace:^",
"@sentry/esbuild-plugin": "^2.16.0",
"fastify": "^4.25.2",
"pino": "^8.18.0",
"fastify": "^4.26.2",
"pino": "^8.19.0",
"pino-pretty": "^11.0.0",
"short-unique-id": "^5.0.3",
"slonik": "37",
"slonik": "^37.6.0",
"tsx": "^4.7.1",
"zod": "^3.22.4"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/gateway/src/GatewayServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export class Gateway {
}

// Start the web server
await addWebRoutes(this.webServer);
addWebRoutes(this.webServer);

this.webServer.listen(
{
Expand Down
27 changes: 0 additions & 27 deletions packages/gateway/src/encryption.d.ts

This file was deleted.

86 changes: 0 additions & 86 deletions packages/gateway/src/index.d.ts

This file was deleted.

8 changes: 0 additions & 8 deletions packages/gateway/src/web.d.ts

This file was deleted.

25 changes: 16 additions & 9 deletions packages/gateway/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ import { generateToken } from "../../nps/services/token.js";
*
* @param {import("fastify").FastifyInstance} webServer The web server
*/
export async function addWebRoutes(
webServer: import("fastify").FastifyInstance,
) {
export function addWebRoutes(webServer: import("fastify").FastifyInstance) {
webServer.addContentTypeParser("*", function (request, payload, done) {
let data = "";
payload.on("data", (chunk) => {
Expand All @@ -43,7 +41,7 @@ export async function addWebRoutes(
});
});

webServer.get("/", async (_request, reply) => {
webServer.get("/", (_request, reply) => {
return reply.send("Hello, world!");
});

Expand Down Expand Up @@ -114,27 +112,36 @@ export async function addWebRoutes(
return reply.send(generateShardList(config.host));
});

webServer.get("/cert", (_request, reply) => {
webServer.get("/cert", async (_request, reply) => {
const config = getServerConfiguration({});
if (typeof config.host === "undefined") {
throw new Error("No host defined in config");
}
return reply.send(handleGetCert(config));
const certFile = await handleGetCert(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=cert.crt");
reply.send(certFile);
Comment on lines +115 to +123
Copy link

Choose a reason for hiding this comment

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

The async implementation for the /cert route handler is well done. However, consider adding error handling for the handleGetCert function to manage potential failures gracefully. Additionally, it's important to ensure this new code path is covered by tests to maintain code quality and reliability.

    webServer.get("/cert", async (_request, reply) => {
        const config = getServerConfiguration({});
        if (typeof config.host === "undefined") {
            throw new Error("No host defined in config");
        }
+       try {
        const certFile = await handleGetCert(config);
        reply.header("Content-Type", "text/plain");
        reply.header("Content-Disposition", "attachment; filename=cert.crt");
        reply.send(certFile);
+       } catch (error) {
+           reply.status(500).send('Internal Server Error');
+       }
    });

Would you like assistance in creating unit tests for this route handler?


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
webServer.get("/cert", async (_request, reply) => {
const config = getServerConfiguration({});
if (typeof config.host === "undefined") {
throw new Error("No host defined in config");
}
return reply.send(handleGetCert(config));
const certFile = await handleGetCert(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=cert.crt");
reply.send(certFile);
webServer.get("/cert", async (_request, reply) => {
const config = getServerConfiguration({});
if (typeof config.host === "undefined") {
throw new Error("No host defined in config");
}
try {
const certFile = await handleGetCert(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=cert.crt");
reply.send(certFile);
} catch (error) {
reply.status(500).send('Internal Server Error');
}

});

webServer.get("/key", (_request, reply) => {
webServer.get("/key", async (_request, reply) => {
const config = getServerConfiguration({});
if (typeof config.host === "undefined") {
throw new Error("No host defined in config");
}
return reply.send(handleGetKey(config));
const keyFile = await handleGetKey(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=pub.key");
reply.send(keyFile);
Comment on lines +126 to +134
Copy link

Choose a reason for hiding this comment

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

Similar to the /cert handler, the /key route handler's async implementation is good. However, adding error handling for the handleGetKey function would improve robustness. Ensuring test coverage for this part of the code is also crucial.

    webServer.get("/key", async (_request, reply) => {
        const config = getServerConfiguration({});
        if (typeof config.host === "undefined") {
            throw new Error("No host defined in config");
        }
+       try {
        const keyFile = await handleGetKey(config);
        reply.header("Content-Type", "text/plain");
        reply.header("Content-Disposition", "attachment; filename=pub.key");
        reply.send(keyFile);
+       } catch (error) {
+           reply.status(500).send('Internal Server Error');
+       }
    });

Would you like help in creating unit tests for this route handler?


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
webServer.get("/key", async (_request, reply) => {
const config = getServerConfiguration({});
if (typeof config.host === "undefined") {
throw new Error("No host defined in config");
}
return reply.send(handleGetKey(config));
const keyFile = await handleGetKey(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=pub.key");
reply.send(keyFile);
webServer.get("/key", async (_request, reply) => {
const config = getServerConfiguration({});
if (typeof config.host === "undefined") {
throw new Error("No host defined in config");
}
try {
const keyFile = await handleGetKey(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=pub.key");
reply.send(keyFile);
} catch (error) {
reply.status(500).send('Internal Server Error');
}
});

});

webServer.get("/registry", (_request, reply) => {
const config = getServerConfiguration({});
if (typeof config.host === "undefined") {
throw new Error("No host defined in config");
}
return reply.send(handleGetRegistry(config));
const regFile = handleGetRegistry(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=client.reg");
reply.send(regFile);
Comment on lines +142 to +145
Copy link

Choose a reason for hiding this comment

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

The /registry route handler is not async, unlike the /cert and /key handlers. For consistency and to leverage async file handling, consider updating this handler to be async as well. Also, ensure this part of the code is covered by tests.

    webServer.get("/registry", (_request, reply) => {
        const config = getServerConfiguration({});
        if (typeof config.host === "undefined") {
            throw new Error("No host defined in config");
        }
+       try {
-        const regFile = handleGetRegistry(config);
+        const regFile = await handleGetRegistry(config);
        reply.header("Content-Type", "text/plain");
        reply.header("Content-Disposition", "attachment; filename=client.reg");
        reply.send(regFile);
+       } catch (error) {
+           reply.status(500).send('Internal Server Error');
+       }
    });

Would you like help in creating unit tests for this route handler?


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const regFile = handleGetRegistry(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=client.reg");
reply.send(regFile);
try {
const regFile = await handleGetRegistry(config);
reply.header("Content-Type", "text/plain");
reply.header("Content-Disposition", "attachment; filename=client.reg");
reply.send(regFile);
} catch (error) {
reply.status(500).send('Internal Server Error');
}

});
}
51 changes: 51 additions & 0 deletions packages/gateway/test/web.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it, expect, vi } from "vitest";

Check failure on line 1 in packages/gateway/test/web.test.ts

View check run for this annotation

Trunk.io / Trunk Check

prettier

Incorrect formatting, autoformat by running 'trunk fmt'
import { generateShardList } from "../../shard/src/ShardServer";
import {
handleGetCert,
handleGetKey,
handleGetRegistry,
} from "../../shard/src/index";

function mockConfig() {
return {
certificateFile: "test",
privateKeyFile: "test",
publicKeyFile: "test",
host: "test",
};
}

describe("web", () => {
it("handleGetCert", async () => {
vi.mock("fs/promises", async (importOriginal) => {
return {
...(await importOriginal<typeof import("node:fs/promises")>()),

Check failure on line 22 in packages/gateway/test/web.test.ts

View check run for this annotation

Trunk.io / Trunk Check

eslint

[new] Parsing error: Unexpected token )
readFile: () => {
return "test";
},
};
});
const config = mockConfig();
const result = await handleGetCert(config);
expect(result).toBe("test");
});

it("handleGetRegistry", () => {
const config = mockConfig();
const result = handleGetRegistry(config);
expect(result).toContain('Windows Registry Editor Version 5.00');
expect(result).toContain('"ShardUrlDev"="http://test/ShardList/"');
});

it("handleGetKey", async () => {
const config = mockConfig();
const result = await handleGetKey(config);
expect(result).toBe("test");
});

it("generateShardList", () => {
const config = mockConfig();
const result = generateShardList(config.host);
expect(result).toContain("LoginServerIP=test");
});
});
Loading