From 08e9191c4be18b9d92e5bf7364b37360fbe6d2b6 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Thu, 10 Oct 2024 20:54:09 +0800 Subject: [PATCH 01/24] feat: replace alpine with debian slim --- quests/Dockerfile | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/quests/Dockerfile b/quests/Dockerfile index 8044c7f..dcd3926 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -1,15 +1,21 @@ -FROM alpine:3.18 +FROM debian:bookworm-slim LABEL maintainer="wancat@wancat.cc" -RUN apk add --no-cache \ +RUN apt-get update && apt-get install -y \ vim \ zsh \ - nodejs \ - yarn + curl \ + openssh-server + + +RUN curl -fsSL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh +RUN bash nodesource_setup.sh +RUN apt-get install -y nodejs +RUN npm install -g yarn +RUN node -v && yarn -v WORKDIR /usr/local/lib/container - COPY ./packages/container/package.json ./yarn.lock ./ RUN yarn install --modules-folder /usr/src/node_modules ENV NODE_PATH=/usr/src/node_modules @@ -19,6 +25,9 @@ COPY ./quests/.includes/zshrc /etc/zsh/zshrc ENV USERNAME=commander RUN adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} +COPY ./ssh_pubkey /home/${USERNAME}/.ssh/authorized_keys +RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys +RUN chmod 700 /home/${USERNAME}/.ssh USER ${USERNAME} From 19897e607ff54796b75a8a08ff98cb96b2d43546 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Thu, 10 Oct 2024 20:54:42 +0800 Subject: [PATCH 02/24] wip: connect container through ssh --- quests/Dockerfile | 7 +++-- server/package.json | 1 + server/src/containers/docker.ts | 22 +++++++++---- yarn.lock | 55 +++++++++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 12 deletions(-) diff --git a/quests/Dockerfile b/quests/Dockerfile index dcd3926..99fc632 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -29,7 +29,8 @@ COPY ./ssh_pubkey /home/${USERNAME}/.ssh/authorized_keys RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys RUN chmod 700 /home/${USERNAME}/.ssh -USER ${USERNAME} +RUN service ssh start -WORKDIR /home/${USERNAME} -CMD [ "/bin/zsh" ] +EXPOSE 22 + +CMD [ "/usr/sbin/sshd", "-D" ] diff --git a/server/package.json b/server/package.json index 44b869f..92e6d08 100644 --- a/server/package.json +++ b/server/package.json @@ -40,6 +40,7 @@ "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "socket.io": "^4.7.1", + "ssh2": "^1.16.0", "swagger-ui-express": "^5.0.0", "validator": "^13.11.0", "winston": "^3.11.0", diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 77d5efe..da9e2a2 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -5,14 +5,24 @@ import logger from '../utils/logger.js' const engine = new Docker() const containerOptions = { - AttachStdin: true, - AttachStdout: true, - AttachStderr: true, - Tty: true, - OpenStdin: true, - StdinOnce: false, + // AttachStdin: true, + // AttachStdout: true, + // AttachStderr: true, + // Tty: true, + // OpenStdin: true, + // StdinOnce: false, HostConfig: { NetworkMode: config.docker.network, + PortBindings: { + '22/tcp': [ + { + HostPort: '11122', // random port + }, + ], + }, + }, + ExposedPorts: { + '22/tcp': {}, }, } as Docker.ContainerCreateOptions diff --git a/yarn.lock b/yarn.lock index 0c92a44..ff2dab4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3579,6 +3579,14 @@ cors@~2.8.5: object-assign "^4" vary "^1" +cpu-features@~0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5" + integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA== + dependencies: + buildcheck "~0.0.6" + nan "^2.19.0" + cpu-features@~0.0.9: version "0.0.9" resolved "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.9.tgz" @@ -6823,6 +6831,11 @@ nan@^2.17.0, nan@^2.18.0: resolved "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz" integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== +nan@^2.19.0, nan@^2.20.0: + version "2.21.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.21.0.tgz#203ab765a02e6676c8cb92e1cad9503e7976d55b" + integrity sha512-MCpOGmdWvAOMi4RWnpxS5G24l7dVMtdSHtV87I3ltjaLdFOTO74HVJ+DfYiAXjxGKsYR/UCmm1rBwhMN7KqS1A== + nanoid@^3.3.7: version "3.3.7" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" @@ -8584,6 +8597,17 @@ ssh2@^1.11.0: cpu-features "~0.0.9" nan "^2.18.0" +ssh2@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.16.0.tgz#79221d40cbf4d03d07fe881149de0a9de928c9f0" + integrity sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg== + dependencies: + asn1 "^0.2.6" + bcrypt-pbkdf "^1.0.2" + optionalDependencies: + cpu-features "~0.0.10" + nan "^2.20.0" + sshpk@^1.14.1, sshpk@^1.7.0: version "1.18.0" resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz" @@ -8631,7 +8655,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8649,6 +8673,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" @@ -8709,7 +8742,7 @@ stringstream@~0.0.4: resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8723,6 +8756,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1, strip-ansi@~3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" @@ -9575,7 +9615,7 @@ wordwrap@^1.0.0: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -9593,6 +9633,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" From 5ba0021c37abb41c3787907e07c71ba3d2ede0c8 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Thu, 10 Oct 2024 22:43:39 +0800 Subject: [PATCH 03/24] fix: ssh server --- quests/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quests/Dockerfile b/quests/Dockerfile index 99fc632..c9f98ed 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -26,8 +26,9 @@ ENV USERNAME=commander RUN adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} COPY ./ssh_pubkey /home/${USERNAME}/.ssh/authorized_keys -RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys -RUN chmod 700 /home/${USERNAME}/.ssh +RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys \ + && chmod 700 /home/${USERNAME}/.ssh \ + && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh RUN service ssh start From 4c292fd6ba0762c4d6984d814397d5b8a2f03fc9 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Sun, 13 Oct 2024 15:05:20 +0800 Subject: [PATCH 04/24] wip: allow connection from docker container --- docker-compose.yml | 6 ++++- quests/Dockerfile | 2 ++ server/src/api/socket.ts | 3 ++- server/src/config.ts | 2 +- server/src/containers/docker.ts | 43 +++++++++++++++++++-------------- server/src/index.ts | 2 +- 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f7065bd..3a3be60 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,12 +12,16 @@ services: base: image: linuxodyssey/quest-base + # networks: + # - players build: context: . dockerfile: ./quests/Dockerfile + extra_hosts: + - 'host.docker.internal:host-gateway' networks: internal: players: name: linux-odyssey-players - internal: true + internal: false diff --git a/quests/Dockerfile b/quests/Dockerfile index c9f98ed..aed6ec0 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -30,6 +30,8 @@ RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys \ && chmod 700 /home/${USERNAME}/.ssh \ && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh +RUN apt-get install -y net-tools + RUN service ssh start EXPOSE 22 diff --git a/server/src/api/socket.ts b/server/src/api/socket.ts index 5395370..9ad524b 100644 --- a/server/src/api/socket.ts +++ b/server/src/api/socket.ts @@ -77,7 +77,8 @@ async function connectContainer(socket: Socket, next: (err?: Error) => void) { stream, } next() - } catch { + } catch (err) { + console.error(err) next(new Error('Failed to start container.')) } } diff --git a/server/src/config.ts b/server/src/config.ts index 63af4dd..e1b89dc 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -31,7 +31,7 @@ function getUrl(key: string): string { function createConfig() { const isProduction = process.env.NODE_ENV === 'production' - const host = get('HOST', 'localhost') + const host = get('HOST', '0.0.0.0') const port = Number(get('PORT', 3000)) const baseUrl = get('BASE_URL', `http://${host}:${port}`) const url = new URL(baseUrl) diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index da9e2a2..2b8a653 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -4,45 +4,52 @@ import logger from '../utils/logger.js' const engine = new Docker() -const containerOptions = { +const newContainerOptions = ( + name: string, + imageId: string, + options: { + binds?: string[] + } = {} +): Docker.ContainerCreateOptions => ({ // AttachStdin: true, // AttachStdout: true, // AttachStderr: true, // Tty: true, // OpenStdin: true, // StdinOnce: false, + name, + Image: getQuestImage(imageId), HostConfig: { - NetworkMode: config.docker.network, - PortBindings: { - '22/tcp': [ - { - HostPort: '11122', // random port - }, - ], - }, + NetworkMode: 'linux-odyssey-players', // config.docker.network, + // PortBindings: { + // '22/tcp': [ + // { + // HostIP: '0.0.0.0', + // HostPort: '11122', // random port + // }, + // ], + // }, + Binds: options.binds, + ExtraHosts: ['host.docker.internal:host-gateway'], }, ExposedPorts: { - '22/tcp': {}, + // '22/tcp': {}, }, -} as Docker.ContainerCreateOptions +}) export function createContainer( name: string, imageId: string ): Promise { - const option = { - ...containerOptions, - name, - Image: getQuestImage(imageId), - } - + let binds: string[] = [] if (!config.isProduction && config.docker.mountQuest && imageId !== 'base') { logger.info('Mounting quest folder', imageId) - option.HostConfig!.Binds = [ + binds = [ `${config.projectRoot}/quests/${imageId}/home:/home/commander`, `${config.projectRoot}/packages/container:/usr/local/lib/container`, ] } + const option = newContainerOptions(name, imageId, { binds }) return engine.createContainer(option) } diff --git a/server/src/index.ts b/server/src/index.ts index 3ed074d..22916d8 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -72,7 +72,7 @@ async function main() { app.use('/api/v1', apiRouter) app.use(errorHandler) - server.listen(config.port, config.host, () => { + server.listen(config.port, '0.0.0.0', () => { logger.info(`Server listening at ${config.baseUrl}`) }) } From a6720cfe398264f0fa2541b8140e5002d5dbeb2d Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Sun, 13 Oct 2024 15:59:01 +0800 Subject: [PATCH 05/24] feat: connect through ssh --- quests/Dockerfile | 3 +- quests/sshd_config | 121 ++++++++++++++++++++++++++++++++ server/src/api/socket.ts | 20 +++--- server/src/containers/docker.ts | 91 +++++++++++++++--------- server/src/containers/ssh.ts | 0 5 files changed, 192 insertions(+), 43 deletions(-) create mode 100644 quests/sshd_config create mode 100644 server/src/containers/ssh.ts diff --git a/quests/Dockerfile b/quests/Dockerfile index aed6ec0..de56f7b 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -25,13 +25,12 @@ COPY ./quests/.includes/zshrc /etc/zsh/zshrc ENV USERNAME=commander RUN adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} +COPY ./quests/sshd_config /etc/ssh/sshd_config COPY ./ssh_pubkey /home/${USERNAME}/.ssh/authorized_keys RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys \ && chmod 700 /home/${USERNAME}/.ssh \ && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh -RUN apt-get install -y net-tools - RUN service ssh start EXPOSE 22 diff --git a/quests/sshd_config b/quests/sshd_config new file mode 100644 index 0000000..b5063ab --- /dev/null +++ b/quests/sshd_config @@ -0,0 +1,121 @@ +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/usr/local/bin:/usr/bin:/bin:/usr/games + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +Include /etc/ssh/sshd_config.d/*.conf + +#Port 22 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key +#HostKey /etc/ssh/ssh_host_ed25519_key + +# Ciphers and keying +#RekeyLimit default none + +# Logging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +#PermitRootLogin prohibit-password +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#PubkeyAuthentication yes + +# Expect .ssh/authorized_keys2 to be disregarded by default in future. +#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#PasswordAuthentication yes +#PermitEmptyPasswords no + +# Change to yes to enable challenge-response passwords (beware issues with +# some PAM modules and threads) +KbdInteractiveAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes +#GSSAPIStrictAcceptorCheck yes +#GSSAPIKeyExchange no + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the KbdInteractiveAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via KbdInteractiveAuthentication may bypass +# the setting of "PermitRootLogin prohibit-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and KbdInteractiveAuthentication to 'no'. +UsePAM yes + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +X11Forwarding yes +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +PrintMotd no +#PrintLastLog yes +#TCPKeepAlive yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS no +#PidFile /run/sshd.pid +#MaxStartups 10:30:100 +#PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# Allow client to pass locale environment variables +AcceptEnv LANG LC_* NODE_PATH ZDOTDIR API_ENDPOINT TOKEN + +# override default of no subsystems +Subsystem sftp /usr/lib/openssh/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server diff --git a/server/src/api/socket.ts b/server/src/api/socket.ts index 9ad524b..aff3feb 100644 --- a/server/src/api/socket.ts +++ b/server/src/api/socket.ts @@ -1,8 +1,9 @@ import { Server } from 'socket.io' import type { Socket } from 'socket.io' +import { Duplex } from 'stream' import validator from 'validator' import type { Server as HttpServer } from 'http' -import { Session } from '@linux-odyssey/models' +import { ISession, Session } from '@linux-odyssey/models' import { getAndStartContainer, attachContainer } from '../containers/docker.js' import SessionMiddleware from '../middleware/session.js' import { genJWT } from '../utils/auth.js' @@ -78,30 +79,33 @@ async function connectContainer(socket: Socket, next: (err?: Error) => void) { } next() } catch (err) { - console.error(err) + logger.error(err) next(new Error('Failed to start container.')) } } function onConnect(socket: Socket) { - const { session, stream } = socket.data.context - stream.socket.on('data', (chunk: Buffer) => { + const { session, stream } = socket.data.context as { + session: ISession + stream: Duplex + } + stream.on('data', (chunk: Buffer) => { socket.emit('terminal', chunk.toString()) }) socket.on('terminal', function incoming(message) { - stream.socket.write(message) + stream.write(message) }) const socketCallback = (event: string, ...data: any[]) => { socket.emit(event, ...data) } - listenToSession(session.id, socketCallback) + listenToSession(session._id.toString(), socketCallback) socket.on('disconnect', () => { - stream.socket.write('exit\n') - removeFromSession(session.id, socketCallback) + stream.write('exit\n') + removeFromSession(session._id.toString(), socketCallback) stream.destroy() }) } diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 2b8a653..d5eae85 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -1,3 +1,6 @@ +import fs from 'fs' +import { Duplex } from 'stream' +import { Client } from 'ssh2' import Docker from 'dockerode' import config, { getQuestImage } from '../config.js' import logger from '../utils/logger.js' @@ -11,29 +14,15 @@ const newContainerOptions = ( binds?: string[] } = {} ): Docker.ContainerCreateOptions => ({ - // AttachStdin: true, - // AttachStdout: true, - // AttachStderr: true, - // Tty: true, - // OpenStdin: true, - // StdinOnce: false, name, Image: getQuestImage(imageId), HostConfig: { NetworkMode: 'linux-odyssey-players', // config.docker.network, - // PortBindings: { - // '22/tcp': [ - // { - // HostIP: '0.0.0.0', - // HostPort: '11122', // random port - // }, - // ], - // }, Binds: options.binds, ExtraHosts: ['host.docker.internal:host-gateway'], }, ExposedPorts: { - // '22/tcp': {}, + '22/tcp': {}, }, }) @@ -64,32 +53,68 @@ export async function getAndStartContainer( if (!(await container.inspect()).State.Running) { await container.start() } + await new Promise((resolve) => setTimeout(resolve, 2000)) return container } export async function attachContainer( container: Docker.Container, { token }: { token: string } -) { - const exec = await container.exec({ - AttachStdin: true, - AttachStdout: true, - AttachStderr: true, - Cmd: ['/bin/zsh'], - Tty: true, - Env: [ - `TOKEN=${token}`, - `API_ENDPOINT=${config.backendUrl}`, - 'ZDOTDIR=/etc/zsh', - ], - }) +): Promise { + // console.log('attachContainer', await container.inspect()) + const containerIp = (await container.inspect()).NetworkSettings.Networks[ + 'linux-odyssey-players' + ].IPAddress + console.log('containerIp', containerIp) + console.log('token', token) + const conn = new Client() - const execOutput = await exec.start({ - Detach: false, - Tty: true, + return new Promise((resolve, reject) => { + conn + .on('ready', () => { + console.log('SSH connection ready') + conn.shell( + { + env: { + TOKEN: token, + API_ENDPOINT: 'http://host.docker.internal:3000', + ZDOTDIR: '/etc/zsh', + NODE_PATH: '/usr/src/node_modules', + }, + }, + (err, stream) => { + if (err) { + reject(err) + } + stream.on('error', (err) => { + reject(err) + }) + stream.on('close', () => { + console.log('Stream :: close') + conn.end() + }) + resolve(stream) + } + ) + }) + .on('error', (err) => { + reject(err) + }) + .connect({ + host: containerIp, + port: 22, + username: 'commander', + privateKey: fs.readFileSync('/home/wancat/.ssh/id_ed25519'), + debug: console.log, + }) }) - - return execOutput + // const exec = await container.exec({ + // Env: [ + // `TOKEN=${token}`, + // `API_ENDPOINT=${config.backendUrl}`, + // 'ZDOTDIR=/etc/zsh', + // ], + // }) } export async function deleteContainer(id: string) { diff --git a/server/src/containers/ssh.ts b/server/src/containers/ssh.ts new file mode 100644 index 0000000..e69de29 From 2a3429106c9cd4347ec46cc0cdeb0663960f078b Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Sun, 13 Oct 2024 16:31:17 +0800 Subject: [PATCH 06/24] feat: remove welcome banner --- quests/Dockerfile | 1 + quests/sshd_config | 6 +++--- server/src/config.ts | 2 +- server/src/containers/docker.ts | 21 +++++---------------- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/quests/Dockerfile b/quests/Dockerfile index de56f7b..3ca707b 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -30,6 +30,7 @@ COPY ./ssh_pubkey /home/${USERNAME}/.ssh/authorized_keys RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys \ && chmod 700 /home/${USERNAME}/.ssh \ && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh +RUN rm -f /etc/motd /etc/issue /etc/issue.net /etc/update-motd.d/* RUN service ssh start diff --git a/quests/sshd_config b/quests/sshd_config index b5063ab..66de021 100644 --- a/quests/sshd_config +++ b/quests/sshd_config @@ -34,7 +34,7 @@ Include /etc/ssh/sshd_config.d/*.conf #MaxAuthTries 6 #MaxSessions 10 -#PubkeyAuthentication yes +PubkeyAuthentication yes # Expect .ssh/authorized_keys2 to be disregarded by default in future. #AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 @@ -86,11 +86,11 @@ UsePAM yes #AllowAgentForwarding yes #AllowTcpForwarding yes #GatewayPorts no -X11Forwarding yes +X11Forwarding no #X11DisplayOffset 10 #X11UseLocalhost yes #PermitTTY yes -PrintMotd no +#PrintMotd yes #PrintLastLog yes #TCPKeepAlive yes #PermitUserEnvironment no diff --git a/server/src/config.ts b/server/src/config.ts index e1b89dc..e5a4721 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -61,7 +61,7 @@ function createConfig() { bugReportUrl: getUrl('BUG_REPORT_URL'), docker: { - network: get('DOCKER_NETWORK', 'host'), + network: get('DOCKER_NETWORK', 'linux-odyssey-players'), defaultImage: get('QUEST_IMAGE', 'linuxodyssey/quest-base'), imagePrefix: get('DOCKER_PREFIX', 'linuxodyssey/quest-'), mountQuest: process.env.MOUNT_QUEST === 'true', diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index d5eae85..64bda52 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -17,7 +17,7 @@ const newContainerOptions = ( name, Image: getQuestImage(imageId), HostConfig: { - NetworkMode: 'linux-odyssey-players', // config.docker.network, + NetworkMode: config.docker.network, Binds: options.binds, ExtraHosts: ['host.docker.internal:host-gateway'], }, @@ -53,7 +53,9 @@ export async function getAndStartContainer( if (!(await container.inspect()).State.Running) { await container.start() } - await new Promise((resolve) => setTimeout(resolve, 2000)) + await new Promise((resolve) => { + setTimeout(resolve, 1000) + }) return container } @@ -61,12 +63,9 @@ export async function attachContainer( container: Docker.Container, { token }: { token: string } ): Promise { - // console.log('attachContainer', await container.inspect()) const containerIp = (await container.inspect()).NetworkSettings.Networks[ - 'linux-odyssey-players' + config.docker.network ].IPAddress - console.log('containerIp', containerIp) - console.log('token', token) const conn = new Client() return new Promise((resolve, reject) => { @@ -86,9 +85,6 @@ export async function attachContainer( if (err) { reject(err) } - stream.on('error', (err) => { - reject(err) - }) stream.on('close', () => { console.log('Stream :: close') conn.end() @@ -108,13 +104,6 @@ export async function attachContainer( debug: console.log, }) }) - // const exec = await container.exec({ - // Env: [ - // `TOKEN=${token}`, - // `API_ENDPOINT=${config.backendUrl}`, - // 'ZDOTDIR=/etc/zsh', - // ], - // }) } export async function deleteContainer(id: string) { From 2cc348687a54bbabd493065d2f62edc0b993c4e0 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 10:53:15 +0800 Subject: [PATCH 07/24] feat: generate key pair function --- server/src/utils/crypto.ts | 20 ++++++++++++++++++++ server/tests/utils/crypto.test.ts | 9 +++++++++ 2 files changed, 29 insertions(+) create mode 100644 server/src/utils/crypto.ts create mode 100644 server/tests/utils/crypto.test.ts diff --git a/server/src/utils/crypto.ts b/server/src/utils/crypto.ts new file mode 100644 index 0000000..ed452e8 --- /dev/null +++ b/server/src/utils/crypto.ts @@ -0,0 +1,20 @@ +import { generateKeyPairSync } from 'crypto' + +type KeyPair = { + privateKey: string + publicKey: string +} + +export function generateKeyPair(): KeyPair { + const { privateKey, publicKey } = generateKeyPairSync('ed25519', { + publicKeyEncoding: { + type: 'spki', + format: 'pem', + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + }, + }) + return { privateKey, publicKey } +} diff --git a/server/tests/utils/crypto.test.ts b/server/tests/utils/crypto.test.ts new file mode 100644 index 0000000..4c4b17f --- /dev/null +++ b/server/tests/utils/crypto.test.ts @@ -0,0 +1,9 @@ +import { generateKeyPair } from '../../src/utils/crypto' + +describe('generateKeyPair', () => { + it('should generate a key pair', () => { + const keyPair = generateKeyPair() + expect(keyPair).toHaveProperty('privateKey') + expect(keyPair).toHaveProperty('publicKey') + }) +}) From 03b2993b56f0b01149712eb8cfe98abeae7a4cb0 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 11:23:25 +0800 Subject: [PATCH 08/24] feat: load or create key --- server/src/config.ts | 17 ++++++++++++++--- server/src/utils/crypto.ts | 21 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/server/src/config.ts b/server/src/config.ts index e5a4721..a16399d 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -3,6 +3,7 @@ import dotenv from 'dotenv' import { fileURLToPath } from 'node:url' import path from 'path' import { get } from './utils/env.js' +import { loadOrCreateKeyPair } from './utils/crypto.js' // Import dotenv and load ../.env dotenv.config({ path: '../.env' }) @@ -35,6 +36,13 @@ function createConfig() { const port = Number(get('PORT', 3000)) const baseUrl = get('BASE_URL', `http://${host}:${port}`) const url = new URL(baseUrl) + const projectRoot = getProjectRoot() + const keypairPath = get( + 'KEYPAIR_PATH', + path.join(projectRoot, 'config', 'ssh_key') + ) + const keypair = loadOrCreateKeyPair(keypairPath) + return { host, port, @@ -60,17 +68,19 @@ function createConfig() { surveyUrl: getUrl('SURVEY_URL'), bugReportUrl: getUrl('BUG_REPORT_URL'), + projectRoot, + docker: { network: get('DOCKER_NETWORK', 'linux-odyssey-players'), defaultImage: get('QUEST_IMAGE', 'linuxodyssey/quest-base'), imagePrefix: get('DOCKER_PREFIX', 'linuxodyssey/quest-'), mountQuest: process.env.MOUNT_QUEST === 'true', + keypairPath, + keypair, }, - projectRoot: getProjectRoot(), - log: { - path: get('LOG_PATH', path.join(getProjectRoot(), 'logs')), + path: get('LOG_PATH', path.join(projectRoot, 'logs')), }, testing: { enabled: !isProduction && process.env.TESTING === 'true', @@ -87,6 +97,7 @@ function getProjectRoot(): string { } const config = createConfig() +console.log(config.docker.keypair) export function getQuestImage(id: string): string { return `${config.docker.imagePrefix}${id}` diff --git a/server/src/utils/crypto.ts b/server/src/utils/crypto.ts index ed452e8..fbe948c 100644 --- a/server/src/utils/crypto.ts +++ b/server/src/utils/crypto.ts @@ -1,3 +1,4 @@ +import fs from 'fs' import { generateKeyPairSync } from 'crypto' type KeyPair = { @@ -5,8 +6,26 @@ type KeyPair = { publicKey: string } +export function loadOrCreateKeyPair(privateKeyPath: string): KeyPair { + const publicKeyPath = `${privateKeyPath}.pub` + if (fs.existsSync(privateKeyPath) && fs.existsSync(publicKeyPath)) { + const privateKey = fs.readFileSync(privateKeyPath, 'utf8') + const publicKey = fs.readFileSync(publicKeyPath, 'utf8') + return { privateKey, publicKey } + } + const keyPair = generateKeyPair() + fs.writeFileSync(privateKeyPath, keyPair.privateKey, { + mode: 0o600, + }) + fs.writeFileSync(publicKeyPath, keyPair.publicKey, { + mode: 0o600, + }) + return keyPair +} + export function generateKeyPair(): KeyPair { - const { privateKey, publicKey } = generateKeyPairSync('ed25519', { + const { privateKey, publicKey } = generateKeyPairSync('rsa', { + modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem', From 261c6fc029ad8fb535f8f83d13def9ed2490b5be Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 11:34:53 +0800 Subject: [PATCH 09/24] feat: generate key pair using ssh2 for right public key format --- .gitignore | 2 ++ quests/Dockerfile | 2 +- server/package.json | 1 + server/src/containers/docker.ts | 4 ++-- server/src/utils/crypto.ts | 19 +++++++------------ yarn.lock | 7 +++++++ 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 4032a9b..192793a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +config + # Logs logs *.log diff --git a/quests/Dockerfile b/quests/Dockerfile index 3ca707b..c3def99 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -26,7 +26,7 @@ ENV USERNAME=commander RUN adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} COPY ./quests/sshd_config /etc/ssh/sshd_config -COPY ./ssh_pubkey /home/${USERNAME}/.ssh/authorized_keys +COPY ./config/ssh_key.pub /home/${USERNAME}/.ssh/authorized_keys RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys \ && chmod 700 /home/${USERNAME}/.ssh \ && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh diff --git a/server/package.json b/server/package.json index 92e6d08..432f5dd 100644 --- a/server/package.json +++ b/server/package.json @@ -62,6 +62,7 @@ "@types/passport-local": "^1.0.38", "@types/passport-oauth2": "^1.4.15", "@types/source-map-support": "^0", + "@types/ssh2": "^1.15.1", "@types/swagger-ui-express": "^4.1.6", "@types/validator": "^13.11.9", "concurrently": "^8.2.2", diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 64bda52..09476ce 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -1,4 +1,3 @@ -import fs from 'fs' import { Duplex } from 'stream' import { Client } from 'ssh2' import Docker from 'dockerode' @@ -67,6 +66,7 @@ export async function attachContainer( config.docker.network ].IPAddress const conn = new Client() + console.log(config.docker.keypair.privateKey) return new Promise((resolve, reject) => { conn @@ -100,7 +100,7 @@ export async function attachContainer( host: containerIp, port: 22, username: 'commander', - privateKey: fs.readFileSync('/home/wancat/.ssh/id_ed25519'), + privateKey: config.docker.keypair.privateKey, debug: console.log, }) }) diff --git a/server/src/utils/crypto.ts b/server/src/utils/crypto.ts index fbe948c..4f27a03 100644 --- a/server/src/utils/crypto.ts +++ b/server/src/utils/crypto.ts @@ -1,5 +1,9 @@ import fs from 'fs' -import { generateKeyPairSync } from 'crypto' +import ssh2 from 'ssh2' + +const { + utils: { generateKeyPairSync }, +} = ssh2 type KeyPair = { privateKey: string @@ -24,16 +28,7 @@ export function loadOrCreateKeyPair(privateKeyPath: string): KeyPair { } export function generateKeyPair(): KeyPair { - const { privateKey, publicKey } = generateKeyPairSync('rsa', { - modulusLength: 2048, - publicKeyEncoding: { - type: 'spki', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - }, - }) + const { private: privateKey, public: publicKey } = + generateKeyPairSync('ed25519') return { privateKey, publicKey } } diff --git a/yarn.lock b/yarn.lock index ff2dab4..78ca72a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2067,6 +2067,13 @@ dependencies: "@types/node" "^18.11.18" +"@types/ssh2@^1.15.1": + version "1.15.1" + resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-1.15.1.tgz#4db4b6864abca09eb299fe5354fa591add412223" + integrity sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA== + dependencies: + "@types/node" "^18.11.18" + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz" From be22a121c8b5bd3df377b18c6cea79894aafb937 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 12:06:05 +0800 Subject: [PATCH 10/24] feat: use entrypoint to create user account and copy ssh key --- docker-compose.yml | 3 +++ quests/Dockerfile | 14 ++------------ quests/entrypoint.sh | 16 ++++++++++++++++ server/src/containers/docker.ts | 7 +++++-- 4 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 quests/entrypoint.sh diff --git a/docker-compose.yml b/docker-compose.yml index 3a3be60..c317f96 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,9 @@ services: dockerfile: ./quests/Dockerfile extra_hosts: - 'host.docker.internal:host-gateway' + volumes: + - ./config/ssh_key.pub:/ssh_key.pub:ro + - ./quests/entrypoint.sh:/entrypoint.sh:ro networks: internal: diff --git a/quests/Dockerfile b/quests/Dockerfile index c3def99..180d800 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -21,19 +21,9 @@ RUN yarn install --modules-folder /usr/src/node_modules ENV NODE_PATH=/usr/src/node_modules COPY ./packages/container /usr/local/lib/container COPY ./quests/.includes/zshrc /etc/zsh/zshrc - -ENV USERNAME=commander - -RUN adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} -COPY ./quests/sshd_config /etc/ssh/sshd_config -COPY ./config/ssh_key.pub /home/${USERNAME}/.ssh/authorized_keys -RUN chmod 600 /home/${USERNAME}/.ssh/authorized_keys \ - && chmod 700 /home/${USERNAME}/.ssh \ - && chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh RUN rm -f /etc/motd /etc/issue /etc/issue.net /etc/update-motd.d/* - -RUN service ssh start +COPY ./quests/sshd_config /etc/ssh/sshd_config EXPOSE 22 -CMD [ "/usr/sbin/sshd", "-D" ] +ENTRYPOINT ["/bin/sh", "/entrypoint.sh"] diff --git a/quests/entrypoint.sh b/quests/entrypoint.sh new file mode 100644 index 0000000..a997b3f --- /dev/null +++ b/quests/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +USERNAME=commander + +if ! id -u ${USERNAME} >/dev/null 2>&1; then + adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} && \ + mkdir -p /home/${USERNAME}/.ssh && \ + cp /ssh_key.pub /home/${USERNAME}/.ssh/authorized_keys && \ + chmod 600 /home/${USERNAME}/.ssh/authorized_keys && \ + chmod 700 /home/${USERNAME}/.ssh && \ + chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh +fi + +mkdir /run/sshd +echo "Starting SSH server" +/usr/sbin/sshd -D -e diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 09476ce..3b76583 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -29,7 +29,10 @@ export function createContainer( name: string, imageId: string ): Promise { - let binds: string[] = [] + let binds: string[] = [ + `${config.projectRoot}/config/ssh_key.pub:/ssh_key.pub:ro`, + `${config.projectRoot}/quests/entrypoint.sh:/entrypoint.sh:ro`, + ] if (!config.isProduction && config.docker.mountQuest && imageId !== 'base') { logger.info('Mounting quest folder', imageId) binds = [ @@ -53,7 +56,7 @@ export async function getAndStartContainer( await container.start() } await new Promise((resolve) => { - setTimeout(resolve, 1000) + setTimeout(resolve, 2000) }) return container } From 247f03b71a5b5dade8a3b456ccaf7959e895814b Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 12:30:28 +0800 Subject: [PATCH 11/24] feat: mount quest home to /etc/skel to prevent building images --- server/src/containers/docker.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 3b76583..3c37ad5 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -1,3 +1,4 @@ +import fs from 'fs/promises' import { Duplex } from 'stream' import { Client } from 'ssh2' import Docker from 'dockerode' @@ -25,7 +26,7 @@ const newContainerOptions = ( }, }) -export function createContainer( +export async function createContainer( name: string, imageId: string ): Promise { @@ -33,6 +34,10 @@ export function createContainer( `${config.projectRoot}/config/ssh_key.pub:/ssh_key.pub:ro`, `${config.projectRoot}/quests/entrypoint.sh:/entrypoint.sh:ro`, ] + if (await questHomeExists(imageId)) { + logger.info('Mounting quest home', imageId) + binds.push(`${config.projectRoot}/quests/${imageId}/home:/etc/skel:ro`) + } if (!config.isProduction && config.docker.mountQuest && imageId !== 'base') { logger.info('Mounting quest folder', imageId) binds = [ @@ -44,6 +49,15 @@ export function createContainer( return engine.createContainer(option) } +async function questHomeExists(imageId: string) { + try { + const stat = await fs.stat(`${config.projectRoot}/quests/${imageId}/home`) + return stat.isDirectory() + } catch { + return false + } +} + export async function getAndStartContainer( id: string ): Promise { From a4ed4cf533591f52985828ba8f18046c68517211 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 12:40:53 +0800 Subject: [PATCH 12/24] feat: remove unnecessary quest Dockerfiles --- quests/checkpoint-01/Dockerfile | 3 --- quests/checkpoint-02/Dockerfile | 3 --- quests/checkpoint-03/Dockerfile | 7 ------- quests/copy/Dockerfile | 3 --- quests/entrypoint.sh | 1 + quests/execute/Dockerfile | 6 ------ quests/explore/Dockerfile | 3 --- quests/helloworld/Dockerfile | 3 --- quests/move/Dockerfile | 3 --- quests/path/Dockerfile | 3 --- quests/read/Dockerfile | 3 --- quests/remove/Dockerfile | 4 ---- quests/remove/home/empty/.gitkeep | 0 quests/tab/Dockerfile | 3 --- server/src/containers/docker.ts | 11 ++++++----- server/src/models/sessionManager.ts | 1 + 16 files changed, 8 insertions(+), 49 deletions(-) delete mode 100644 quests/checkpoint-01/Dockerfile delete mode 100644 quests/checkpoint-02/Dockerfile delete mode 100644 quests/checkpoint-03/Dockerfile delete mode 100644 quests/copy/Dockerfile delete mode 100644 quests/execute/Dockerfile delete mode 100644 quests/explore/Dockerfile delete mode 100644 quests/helloworld/Dockerfile delete mode 100644 quests/move/Dockerfile delete mode 100644 quests/path/Dockerfile delete mode 100644 quests/read/Dockerfile delete mode 100644 quests/remove/Dockerfile create mode 100644 quests/remove/home/empty/.gitkeep delete mode 100644 quests/tab/Dockerfile diff --git a/quests/checkpoint-01/Dockerfile b/quests/checkpoint-01/Dockerfile deleted file mode 100644 index 2a8e341..0000000 --- a/quests/checkpoint-01/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY home ${HOME} \ No newline at end of file diff --git a/quests/checkpoint-02/Dockerfile b/quests/checkpoint-02/Dockerfile deleted file mode 100644 index 2a8e341..0000000 --- a/quests/checkpoint-02/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY home ${HOME} \ No newline at end of file diff --git a/quests/checkpoint-03/Dockerfile b/quests/checkpoint-03/Dockerfile deleted file mode 100644 index 377fb37..0000000 --- a/quests/checkpoint-03/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM linuxodyssey/quest-base - -USER root -COPY home . -RUN chown -R ${USERNAME}:${USERNAME} . - -USER ${USERNAME} \ No newline at end of file diff --git a/quests/copy/Dockerfile b/quests/copy/Dockerfile deleted file mode 100644 index 2a8e341..0000000 --- a/quests/copy/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY home ${HOME} \ No newline at end of file diff --git a/quests/entrypoint.sh b/quests/entrypoint.sh index a997b3f..505b5b2 100644 --- a/quests/entrypoint.sh +++ b/quests/entrypoint.sh @@ -4,6 +4,7 @@ USERNAME=commander if ! id -u ${USERNAME} >/dev/null 2>&1; then adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} && \ + find / -name ".gitkeep" -type f -exec rm -f {} + mkdir -p /home/${USERNAME}/.ssh && \ cp /ssh_key.pub /home/${USERNAME}/.ssh/authorized_keys && \ chmod 600 /home/${USERNAME}/.ssh/authorized_keys && \ diff --git a/quests/execute/Dockerfile b/quests/execute/Dockerfile deleted file mode 100644 index 54f88a1..0000000 --- a/quests/execute/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM linuxodyssey/quest-base - -USER root -COPY home ${HOME} - -USER commander \ No newline at end of file diff --git a/quests/explore/Dockerfile b/quests/explore/Dockerfile deleted file mode 100644 index 2a8e341..0000000 --- a/quests/explore/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY home ${HOME} \ No newline at end of file diff --git a/quests/helloworld/Dockerfile b/quests/helloworld/Dockerfile deleted file mode 100644 index 1811d6e..0000000 --- a/quests/helloworld/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY home ${HOME} diff --git a/quests/move/Dockerfile b/quests/move/Dockerfile deleted file mode 100644 index 79a8660..0000000 --- a/quests/move/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY --chown=${USERNAME} home ${HOME} \ No newline at end of file diff --git a/quests/path/Dockerfile b/quests/path/Dockerfile deleted file mode 100644 index 2a8e341..0000000 --- a/quests/path/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY home ${HOME} \ No newline at end of file diff --git a/quests/read/Dockerfile b/quests/read/Dockerfile deleted file mode 100644 index 2a8e341..0000000 --- a/quests/read/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY home ${HOME} \ No newline at end of file diff --git a/quests/remove/Dockerfile b/quests/remove/Dockerfile deleted file mode 100644 index 3e36542..0000000 --- a/quests/remove/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY --chown=${USERNAME} home ${HOME} -RUN mkdir empty \ No newline at end of file diff --git a/quests/remove/home/empty/.gitkeep b/quests/remove/home/empty/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/quests/tab/Dockerfile b/quests/tab/Dockerfile deleted file mode 100644 index 1811d6e..0000000 --- a/quests/tab/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM linuxodyssey/quest-base - -COPY home ${HOME} diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 3c37ad5..848a3fd 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -28,20 +28,21 @@ const newContainerOptions = ( export async function createContainer( name: string, + questId: string, imageId: string ): Promise { let binds: string[] = [ `${config.projectRoot}/config/ssh_key.pub:/ssh_key.pub:ro`, `${config.projectRoot}/quests/entrypoint.sh:/entrypoint.sh:ro`, ] - if (await questHomeExists(imageId)) { - logger.info('Mounting quest home', imageId) - binds.push(`${config.projectRoot}/quests/${imageId}/home:/etc/skel:ro`) + if (await questHomeExists(questId)) { + logger.info('Mounting quest home', questId) + binds.push(`${config.projectRoot}/quests/${questId}/home:/etc/skel:ro`) } if (!config.isProduction && config.docker.mountQuest && imageId !== 'base') { - logger.info('Mounting quest folder', imageId) + logger.info('Mounting quest folder', questId) binds = [ - `${config.projectRoot}/quests/${imageId}/home:/home/commander`, + `${config.projectRoot}/quests/${questId}/home:/home/commander`, `${config.projectRoot}/packages/container:/usr/local/lib/container`, ] } diff --git a/server/src/models/sessionManager.ts b/server/src/models/sessionManager.ts index 798732c..2489704 100644 --- a/server/src/models/sessionManager.ts +++ b/server/src/models/sessionManager.ts @@ -43,6 +43,7 @@ export async function createNewSession( const container = await createContainer( `quest-${quest.id}-${user.username}-${Date.now()}`, + quest.id, quest.image ) From 251139c15968b426d23e7b84a2325d30ca1b66e4 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 17:31:23 +0800 Subject: [PATCH 13/24] feat: move sshd_config to .include folder --- quests/{ => .includes}/sshd_config | 0 quests/Dockerfile | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename quests/{ => .includes}/sshd_config (100%) diff --git a/quests/sshd_config b/quests/.includes/sshd_config similarity index 100% rename from quests/sshd_config rename to quests/.includes/sshd_config diff --git a/quests/Dockerfile b/quests/Dockerfile index 180d800..008533d 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -22,7 +22,7 @@ ENV NODE_PATH=/usr/src/node_modules COPY ./packages/container /usr/local/lib/container COPY ./quests/.includes/zshrc /etc/zsh/zshrc RUN rm -f /etc/motd /etc/issue /etc/issue.net /etc/update-motd.d/* -COPY ./quests/sshd_config /etc/ssh/sshd_config +COPY ./quests/.includes/sshd_config /etc/ssh/sshd_config EXPOSE 22 From 31301419ad344a2e81b398eb9567616fed008a72 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 17:31:35 +0800 Subject: [PATCH 14/24] feat: add ssh connection retry --- .env.sample | 2 +- server/src/config.ts | 4 +-- server/src/containers/docker.ts | 54 ++++++++------------------------- server/src/containers/ssh.ts | 48 +++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 44 deletions(-) diff --git a/.env.sample b/.env.sample index a389a42..06be919 100644 --- a/.env.sample +++ b/.env.sample @@ -1,11 +1,11 @@ HOST=localhost PORT=3000 BASE_URL=http://localhost:5173 -BACKEND_URL=http://localhost:3000 MONGO_URL=mongodb://localhost:27017/odyssey-test SECRET_KEY=sample-secret # Optional +# BACKEND_URL=http://host.docker.internal:3000 # CONTAINER_EXPIRY=60 # 60 minutes # SESSION_MAX_AGE=168 # 7 days # TRUSTED_PROXIES=1.2.3.4 diff --git a/server/src/config.ts b/server/src/config.ts index a16399d..ea258af 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -35,6 +35,7 @@ function createConfig() { const host = get('HOST', '0.0.0.0') const port = Number(get('PORT', 3000)) const baseUrl = get('BASE_URL', `http://${host}:${port}`) + const backendUrl = get('BACKEND_URL', `http://host.docker.internal:${port}`) const url = new URL(baseUrl) const projectRoot = getProjectRoot() const keypairPath = get( @@ -49,7 +50,7 @@ function createConfig() { baseUrl, domain: url.hostname, protocol: url.protocol, - backendUrl: get('BACKEND_URL', baseUrl), + backendUrl, db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'), secret: get('SECRET_KEY', ''), isProduction: process.env.NODE_ENV === 'production', @@ -97,7 +98,6 @@ function getProjectRoot(): string { } const config = createConfig() -console.log(config.docker.keypair) export function getQuestImage(id: string): string { return `${config.docker.imagePrefix}${id}` diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 848a3fd..92acd07 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -1,9 +1,9 @@ import fs from 'fs/promises' import { Duplex } from 'stream' -import { Client } from 'ssh2' import Docker from 'dockerode' import config, { getQuestImage } from '../config.js' import logger from '../utils/logger.js' +import { connectToSSH } from './ssh.js' const engine = new Docker() @@ -70,9 +70,6 @@ export async function getAndStartContainer( if (!(await container.inspect()).State.Running) { await container.start() } - await new Promise((resolve) => { - setTimeout(resolve, 2000) - }) return container } @@ -83,45 +80,20 @@ export async function attachContainer( const containerIp = (await container.inspect()).NetworkSettings.Networks[ config.docker.network ].IPAddress - const conn = new Client() - console.log(config.docker.keypair.privateKey) - return new Promise((resolve, reject) => { - conn - .on('ready', () => { - console.log('SSH connection ready') - conn.shell( - { - env: { - TOKEN: token, - API_ENDPOINT: 'http://host.docker.internal:3000', - ZDOTDIR: '/etc/zsh', - NODE_PATH: '/usr/src/node_modules', - }, - }, - (err, stream) => { - if (err) { - reject(err) - } - stream.on('close', () => { - console.log('Stream :: close') - conn.end() - }) - resolve(stream) - } - ) + for (let i = 0; i < 10; i++) { + try { + // eslint-disable-next-line no-await-in-loop + const stream = await connectToSSH(containerIp, { token }) + return stream + } catch { + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => { + setTimeout(resolve, 1000) }) - .on('error', (err) => { - reject(err) - }) - .connect({ - host: containerIp, - port: 22, - username: 'commander', - privateKey: config.docker.keypair.privateKey, - debug: console.log, - }) - }) + } + } + throw new Error('Failed to connect to SSH') } export async function deleteContainer(id: string) { diff --git a/server/src/containers/ssh.ts b/server/src/containers/ssh.ts index e69de29..ffb1338 100644 --- a/server/src/containers/ssh.ts +++ b/server/src/containers/ssh.ts @@ -0,0 +1,48 @@ +import { Client } from 'ssh2' +import { Duplex } from 'stream' +import config from '../config.js' + +export async function connectToSSH( + containerIp: string, + { token }: { token: string } +): Promise { + const conn = new Client() + + return new Promise((resolve, reject) => { + conn + .on('ready', () => { + console.log('SSH connection ready') + conn.shell( + { + env: { + TOKEN: token, + API_ENDPOINT: config.backendUrl, + ZDOTDIR: '/etc/zsh', + NODE_PATH: '/usr/src/node_modules', + }, + }, + (err, stream) => { + if (err) { + reject(err) + } + stream.on('close', () => { + console.log('Stream :: close') + conn.end() + }) + resolve(stream) + } + ) + }) + .on('error', (err) => { + reject(err) + }) + .connect({ + host: containerIp, + port: 22, + username: 'commander', + privateKey: config.docker.keypair.privateKey, + debug: console.log, + timeout: 5000, + }) + }) +} From 58354b7cd8d5e449514b79ae9064b64da1c6bc93 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 17:48:05 +0800 Subject: [PATCH 15/24] feat: clean up /etc/skel and move ssh key to /etc/ssh/authorized_keys/ --- quests/.includes/sshd_config | 2 +- quests/Dockerfile | 2 ++ quests/entrypoint.sh | 11 +++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/quests/.includes/sshd_config b/quests/.includes/sshd_config index 66de021..0cf7758 100644 --- a/quests/.includes/sshd_config +++ b/quests/.includes/sshd_config @@ -37,7 +37,7 @@ Include /etc/ssh/sshd_config.d/*.conf PubkeyAuthentication yes # Expect .ssh/authorized_keys2 to be disregarded by default in future. -#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 +AuthorizedKeysFile /etc/ssh/authorized_keys/%u #AuthorizedPrincipalsFile none diff --git a/quests/Dockerfile b/quests/Dockerfile index 008533d..cffeadb 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -22,6 +22,8 @@ ENV NODE_PATH=/usr/src/node_modules COPY ./packages/container /usr/local/lib/container COPY ./quests/.includes/zshrc /etc/zsh/zshrc RUN rm -f /etc/motd /etc/issue /etc/issue.net /etc/update-motd.d/* +RUN rm -rf /etc/skel && mkdir -p /etc/skel +RUN mkdir -p /etc/ssh/authorized_keys && chmod 755 /etc/ssh/authorized_keys COPY ./quests/.includes/sshd_config /etc/ssh/sshd_config EXPOSE 22 diff --git a/quests/entrypoint.sh b/quests/entrypoint.sh index 505b5b2..78d14fc 100644 --- a/quests/entrypoint.sh +++ b/quests/entrypoint.sh @@ -5,13 +5,12 @@ USERNAME=commander if ! id -u ${USERNAME} >/dev/null 2>&1; then adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} && \ find / -name ".gitkeep" -type f -exec rm -f {} + - mkdir -p /home/${USERNAME}/.ssh && \ - cp /ssh_key.pub /home/${USERNAME}/.ssh/authorized_keys && \ - chmod 600 /home/${USERNAME}/.ssh/authorized_keys && \ - chmod 700 /home/${USERNAME}/.ssh && \ - chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh + + PUB_KEY=/etc/ssh/authorized_keys/${USERNAME} + cp /ssh_key.pub ${PUB_KEY} + chmod 644 ${PUB_KEY} fi -mkdir /run/sshd +mkdir -p /run/sshd echo "Starting SSH server" /usr/sbin/sshd -D -e From 91ebaec22c7f22049a4a1bb6f38280becde81ee3 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 14 Oct 2024 18:32:02 +0800 Subject: [PATCH 16/24] feat: update document and remove some debug logging --- README.md | 34 +++++++++++++++++++++++++++++++--- docker-compose.prod.yml | 1 + server/src/api/socket.ts | 2 +- server/src/containers/ssh.ts | 3 --- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2873065..e8d1dd1 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ ## Known Issues -- The player's terminal doesn't work correctly on Mac OS and Windows for now. Please use Linux or Virtual Machines. You can track [this issue](https://github.com/linux-odyssey/linux-odyssey/issues/165) for updates. - MongoDB requires x86 architecture to run for v5+ versions. You can track [this issue](https://github.com/linux-odyssey/linux-odyssey/issues/166) for updates. ## Installation @@ -34,6 +33,16 @@ 6. **utils**: other utility code shared between packages 7. **game (in the future)**: core logic of game playing, will be extracted from server +## Configuration + +The configuration is stored in the `.env` file. You can copy from the sample file. + + cp .env.sample .env + +The backend will use a SSH key pair to login to the player's container. The private key is on the host, and the public key is copied to the player's container. + +The key pair is stored in `./config/ssh_key` and `./config/ssh_key.pub`. If it doesn't exist, the backend will generate a new pair and store them. + ## Docker Setup ### Docker compose files explaination @@ -62,10 +71,13 @@ Building quest images: ### Build Quest Images in Details -In each quest folder, there is a `game.yml` file, which is the quest definition. And a optional `Dockerfile` can overwrite the base image. If you want to include additional files, put them in a `home` folder. The files in `home` will **not** be copied to the image automatically, you have to copy them in the `Dockerfile`. +In each quest folder, there is a `game.yml` file, which is the quest definition. + +If you want to include additional files, put them in a `home` folder. The files in `home` will be copied to the user's home on container creation automatically. quests/ ├── Dockerfile # Base image + ├── entrypoint.sh # Entrypoint for the container ├── discover │ └── game.yml ├── helloworld @@ -81,17 +93,22 @@ In each quest folder, there is a `game.yml` file, which is the quest definition. └── home └── ancient_scroll.txt +An optional `Dockerfile` can overwrite the base image. You can use this to install additional dependencies or modify the environment. + +Customized `entrypoint.sh` for each quest is not yet supported. + To build all quests: yarn build:quests The base image is built with docker-compose, so you can use `docker compose build base` to rebuild it. Once you rebuild the base image, you have to rebuild all quests. -### Mounting Host Directory +### Optional: Mounting Host Directory To improve the development experience, we can mount the host directory to the container in the pattern: /path/to/linux-odyssey/quests/${quest_id}/home:/home/commander + /path/to/linux-odyssey/packages/container:/usr/local/lib/container You can edit the files on your host, and the changes will be reflected in the container. This is disabled by default. To enable it, you can set the following environment variables: @@ -121,6 +138,10 @@ The default username and password is `alex` and `Alex1234`. ## Testing +Unit tests: + + yarn test:unit + Run Cypress locally, using the current development containers: docker compose up -d @@ -145,7 +166,14 @@ Run Cypress in container, create brand-new containers along with it: # Server side docker compose -f docker-compose.prod.yml pull + yarn build:quests docker compose -f docker-compose.prod.yml up -d +### Necessary Configuration + To enable social login, you should have OAuth client token in a `.env` file: You can copy from `.env.sample` + +**YOU MUST CHANGE THE SECRET_KEY** in `.env` file. + + SECRET_KEY=your-secret-key diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 23685b7..f7b91be 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -29,6 +29,7 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - ./logs:/app/logs + - ./config:/app/config env_file: - .env environment: diff --git a/server/src/api/socket.ts b/server/src/api/socket.ts index aff3feb..2483851 100644 --- a/server/src/api/socket.ts +++ b/server/src/api/socket.ts @@ -80,7 +80,7 @@ async function connectContainer(socket: Socket, next: (err?: Error) => void) { next() } catch (err) { logger.error(err) - next(new Error('Failed to start container.')) + next(new Error('Failed to connect to container.')) } } diff --git a/server/src/containers/ssh.ts b/server/src/containers/ssh.ts index ffb1338..4eb54b9 100644 --- a/server/src/containers/ssh.ts +++ b/server/src/containers/ssh.ts @@ -11,7 +11,6 @@ export async function connectToSSH( return new Promise((resolve, reject) => { conn .on('ready', () => { - console.log('SSH connection ready') conn.shell( { env: { @@ -26,7 +25,6 @@ export async function connectToSSH( reject(err) } stream.on('close', () => { - console.log('Stream :: close') conn.end() }) resolve(stream) @@ -41,7 +39,6 @@ export async function connectToSSH( port: 22, username: 'commander', privateKey: config.docker.keypair.privateKey, - debug: console.log, timeout: 5000, }) }) From b449d84c54ebe1cc7d208703bc61d2c65a09deea Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Wed, 16 Oct 2024 14:13:41 +0800 Subject: [PATCH 17/24] feat: auto create config folder and player network --- README.md | 1 + docker-compose.yml | 4 ++-- quests/entrypoint.sh | 4 ++++ server/src/containers/docker.ts | 13 +++++++++++++ server/src/utils/crypto.ts | 5 +++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8d1dd1..ed1609f 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ This will start the db. it will automatically restarts by Docker. Building quest images: + docker compose build base yarn build:quests ### Build Quest Images in Details diff --git a/docker-compose.yml b/docker-compose.yml index c317f96..7be150e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,8 +12,8 @@ services: base: image: linuxodyssey/quest-base - # networks: - # - players + networks: + - players build: context: . dockerfile: ./quests/Dockerfile diff --git a/quests/entrypoint.sh b/quests/entrypoint.sh index 78d14fc..0127e43 100644 --- a/quests/entrypoint.sh +++ b/quests/entrypoint.sh @@ -2,10 +2,14 @@ USERNAME=commander +echo "Initializing container for $USERNAME" + if ! id -u ${USERNAME} >/dev/null 2>&1; then + echo "Adding user $USERNAME" adduser --disabled-password --gecos '' --shell /bin/zsh ${USERNAME} && \ find / -name ".gitkeep" -type f -exec rm -f {} + + echo "Adding public key for $USERNAME" PUB_KEY=/etc/ssh/authorized_keys/${USERNAME} cp /ssh_key.pub ${PUB_KEY} chmod 644 ${PUB_KEY} diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 92acd07..2aac10a 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -47,9 +47,22 @@ export async function createContainer( ] } const option = newContainerOptions(name, imageId, { binds }) + await createNetworkIfNotExists(config.docker.network) return engine.createContainer(option) } +async function createNetworkIfNotExists(network: string) { + const dockerNetworks = await engine.listNetworks({ + filters: { + name: [network], + }, + }) + if (dockerNetworks.length === 0) { + console.log('Creating player network', network) + await engine.createNetwork({ Name: network }) + } +} + async function questHomeExists(imageId: string) { try { const stat = await fs.stat(`${config.projectRoot}/quests/${imageId}/home`) diff --git a/server/src/utils/crypto.ts b/server/src/utils/crypto.ts index 4f27a03..7f6ed0f 100644 --- a/server/src/utils/crypto.ts +++ b/server/src/utils/crypto.ts @@ -1,4 +1,5 @@ import fs from 'fs' +import path from 'path' import ssh2 from 'ssh2' const { @@ -17,6 +18,10 @@ export function loadOrCreateKeyPair(privateKeyPath: string): KeyPair { const publicKey = fs.readFileSync(publicKeyPath, 'utf8') return { privateKey, publicKey } } + const dir = path.dirname(privateKeyPath) + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }) + } const keyPair = generateKeyPair() fs.writeFileSync(privateKeyPath, keyPair.privateKey, { mode: 0o600, From 84bd2620d8267928058f74e9bd1075916964dbde Mon Sep 17 00:00:00 2001 From: "Chen, Tai-Lin" Date: Wed, 16 Oct 2024 23:53:58 +0800 Subject: [PATCH 18/24] docs: add windows environment setup document draft --- docs/windows-setup.md | 144 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/windows-setup.md diff --git a/docs/windows-setup.md b/docs/windows-setup.md new file mode 100644 index 0000000..0a7204c --- /dev/null +++ b/docs/windows-setup.md @@ -0,0 +1,144 @@ +# Windows Environment Setup + +## Prerequisites + +1. Have a computer with Windows 10 or later. +1. Install WSL and Ubuntu on Windows. +1. Enter Ubuntu terminal. + +## Setup Environment (In Ubuntu terminal) + +1. Update and upgrade + + ``` + $ sudo apt-get update + $ sudo apt-get upgrade + ``` + +1. Install Git + + ``` + $ sudo apt-get install git + ``` + +1. Install Node.js (Version 20.x) + + ``` + $ curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + $ sudo apt-get install nodejs + ``` + +1. [Install Docker](#install-docker) +1. [Install Python 2.7](#install-python-27) + +## Run the Project (In Ubuntu terminal) + +1. Installation + + ``` + $ git clone https://github.com/lancatlin/linux-odyssey.git + $ cd linux-odyssey + $ yarn install + ``` + +1. Docker setup + + ``` + $ docker compose pull + $ docker compose build + $ docker-compose up -d + ``` + + + + +1. Run the project + + ``` + $ yarn build + $ yarn dev + ``` + + + +## References + +### Install Docker + +(Refer to [get.docker.com](https://get.docker.com/)) + +To install the latest stable versions of Docker CLI, Docker Engine, and their +dependencies: + +1. Download the script + + ``` + $ curl -fsSL https://get.docker.com -o install-docker.sh + ``` + +1. verify the script's content + + ``` + $ cat install-docker.sh + ``` + +1. run the script with --dry-run to verify the steps it executes + + ``` + $ sh install-docker.sh --dry-run + ``` + +1. run the script either as root, or using sudo to perform the installation. + + ``` + $ sudo sh install-docker.sh + ``` + +1. Verify the installation + + ``` + $ docker --version + ``` + +### Install Python 2.7 + +1. Install required packages + + ``` + $ sudo apt install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses-dev xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git + ``` + +1. Install pyenv + + ``` + $ curl https://pyenv.run | bash + ``` + +1. Add following lines to bash configuration file (e.g. `~/.bashrc`, `~/.bash_profile`, `~/.zshrc`) + + ``` + export PATH="$HOME/.pyenv/bin:$PATH" + eval "$(pyenv init --path)" + eval "$(pyenv init -)" + eval "$(pyenv virtualenv-init -)" + ``` + +1. Restart the shell + +1. Install Python 2.7.18 using pyenv + + ``` + $ pyenv install 2.7.18 + ``` + +1. Set Python 2.7.18 as the global version + + ``` + $ pyenv global 2.7.18 + ``` + +1. Verify the installation + + ``` + $ python --version + ``` From ba251fb933a9ee5746a7548dc060b194ecc731ca Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Thu, 17 Oct 2024 22:08:52 +0800 Subject: [PATCH 19/24] feat: update dump script --- docker-scripts.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docker-scripts.sh b/docker-scripts.sh index 2c14551..f1931d9 100755 --- a/docker-scripts.sh +++ b/docker-scripts.sh @@ -32,15 +32,16 @@ down() { dump() { # Define service name and dump directory name SERVICE_NAME="db" + COMPOSE_FILE="docker-compose.prod.yml" DUMP_NAME="mongodump-$(date --iso).archive" - CONTAINER_ID=$(docker compose ps -q $SERVICE_NAME) + CONTAINER_ID=$(docker compose -f $COMPOSE_FILE ps -q $SERVICE_NAME) # Fetch credentials from Docker environment variables - MONGO_URL=$(docker compose exec -T backend printenv MONGO_URL) + MONGO_URL=$(docker compose -f $COMPOSE_FILE exec -T backend printenv MONGO_URL) echo mongo uri: $MONGO_URL # Dump MongoDB data using credentials - docker compose exec -T $SERVICE_NAME sh -c "mongodump --gzip --archive=/tmp/$DUMP_NAME -d odyssey-test" && + docker compose -f $COMPOSE_FILE exec -T $SERVICE_NAME sh -c "mongodump --gzip --archive=/tmp/$DUMP_NAME --uri=$MONGO_URL" && # Copy the dump from the container to the host # Note: You will need to know the exact container name for this step From 860b257ea73a751f394359e1256c3793a871471e Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Sun, 20 Oct 2024 20:06:39 +0800 Subject: [PATCH 20/24] doc: update Windows setup guide --- README.md | 2 + docker-compose.yml | 5 +- docs/windows-setup.md | 163 +++++++++++++++++++++--------------------- 3 files changed, 85 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index ed1609f..e2f8ac1 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ - MongoDB requires x86 architecture to run for v5+ versions. You can track [this issue](https://github.com/linux-odyssey/linux-odyssey/issues/166) for updates. +Windows user please refer to [docs/windows-setup.md](docs/windows-setup.md) for setup instructions. + ## Installation git clone https://github.com/lancatlin/linux-odyssey.git diff --git a/docker-compose.yml b/docker-compose.yml index 7be150e..f92fea5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,14 +13,14 @@ services: base: image: linuxodyssey/quest-base networks: - - players + - players build: context: . dockerfile: ./quests/Dockerfile extra_hosts: - 'host.docker.internal:host-gateway' volumes: - - ./config/ssh_key.pub:/ssh_key.pub:ro + # - ./config/ssh_key.pub:/ssh_key.pub:ro - ./quests/entrypoint.sh:/entrypoint.sh:ro networks: @@ -28,3 +28,4 @@ networks: players: name: linux-odyssey-players internal: false + external: true diff --git a/docs/windows-setup.md b/docs/windows-setup.md index 0a7204c..7479d42 100644 --- a/docs/windows-setup.md +++ b/docs/windows-setup.md @@ -1,69 +1,40 @@ # Windows Environment Setup -## Prerequisites +## Install WSL and Ubuntu -1. Have a computer with Windows 10 or later. -1. Install WSL and Ubuntu on Windows. -1. Enter Ubuntu terminal. +Please refer to the Official [Setup WSL](https://learn.microsoft.com/en-us/windows/wsl/setup/environment) on Microsoft's website. + +Run the following command in PowerShell as Administrator: + + wsl --install + +Then restart the computer. After restarting, you can open Ubuntu terminal by searching "Ubuntu" in Start menu. You will be prompted to create a new user account. Once you have done that, you can continue with the following steps. ## Setup Environment (In Ubuntu terminal) 1. Update and upgrade ``` - $ sudo apt-get update - $ sudo apt-get upgrade + sudo apt-get update + sudo apt-get upgrade ``` 1. Install Git ``` - $ sudo apt-get install git + sudo apt-get install git ``` 1. Install Node.js (Version 20.x) ``` - $ curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - - $ sudo apt-get install nodejs + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get install nodejs + sudo apt install build-essential + sudo npm install -g yarn ``` -1. [Install Docker](#install-docker) -1. [Install Python 2.7](#install-python-27) - -## Run the Project (In Ubuntu terminal) - -1. Installation - - ``` - $ git clone https://github.com/lancatlin/linux-odyssey.git - $ cd linux-odyssey - $ yarn install - ``` - -1. Docker setup - - ``` - $ docker compose pull - $ docker compose build - $ docker-compose up -d - ``` - - - - -1. Run the project - - ``` - $ yarn build - $ yarn dev - ``` - - - -## References - -### Install Docker +## Install Docker (Refer to [get.docker.com](https://get.docker.com/)) @@ -73,72 +44,98 @@ dependencies: 1. Download the script ``` - $ curl -fsSL https://get.docker.com -o install-docker.sh + curl -fsSL https://get.docker.com -o install-docker.sh ``` 1. verify the script's content ``` - $ cat install-docker.sh + cat install-docker.sh ``` 1. run the script with --dry-run to verify the steps it executes ``` - $ sh install-docker.sh --dry-run + sh install-docker.sh --dry-run ``` +**NOTE: the script will recommend you to use Docker Desktop instead, but we need to use Docker inside WSL for the backend to work.** Just ignore it and wait for the installation to finish. + 1. run the script either as root, or using sudo to perform the installation. ``` - $ sudo sh install-docker.sh + sudo sh install-docker.sh ``` 1. Verify the installation ``` - $ docker --version + docker --version ``` -### Install Python 2.7 - -1. Install required packages +1. Add your user to the docker group ``` - $ sudo apt install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses-dev xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git + sudo usermod -aG docker $USER ``` -1. Install pyenv - - ``` - $ curl https://pyenv.run | bash - ``` - -1. Add following lines to bash configuration file (e.g. `~/.bashrc`, `~/.bash_profile`, `~/.zshrc`) - - ``` - export PATH="$HOME/.pyenv/bin:$PATH" - eval "$(pyenv init --path)" - eval "$(pyenv init -)" - eval "$(pyenv virtualenv-init -)" - ``` - -1. Restart the shell - -1. Install Python 2.7.18 using pyenv - - ``` - $ pyenv install 2.7.18 - ``` - -1. Set Python 2.7.18 as the global version - - ``` - $ pyenv global 2.7.18 - ``` + **NOTE: You need to create a new WSL terminal to apply the changes.** 1. Verify the installation ``` - $ python --version + docker ps ``` + This should return something like: + + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + +## Run the Project (In Ubuntu terminal) + +1. Clone the repository + + ``` + git clone https://github.com/lancatlin/linux-odyssey.git + cd linux-odyssey + ``` + + Optional: To open the project in VSCode, run the following command inside the project folder: + + ``` + code . + ``` + +1. Install dependencies + + ``` + yarn install + ``` + It is ok if there are some warnings and `gyp: ERR` errors, just verify there is `done` at the end. + +1. Copy the environment variables + + ``` + cp .env.sample .env + ``` + +1. Docker setup + + ``` + docker compose pull + docker compose build + docker compose up -d db + ``` + + + + +1. Run the project + + ``` + yarn build + yarn dev + ``` + + + +Enjoy! \ No newline at end of file From 1888781a47925da81d78c40ec6955d782724a6ab Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 21 Oct 2024 11:54:32 +0800 Subject: [PATCH 21/24] fix: copy entrypoint into quest container. Disable mounting for entrypoint & public key. Use Env to pass ssh public key into container. --- docker-compose.prod.yml | 22 +++++++++++++--------- docker-compose.yml | 5 +---- quests/Dockerfile | 1 + quests/entrypoint.sh | 7 ++++--- server/src/containers/docker.ts | 20 +++++++++++++------- 5 files changed, 32 insertions(+), 23 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f7b91be..92ec878 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -32,13 +32,20 @@ services: - ./config:/app/config env_file: - .env + - .env.prod environment: - - MONGO_URL=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@db:27017/${MONGO_DB} + - MONGO_URL=mongodb://${MONGO_USER}:${MONGO_PASS}@db:27017/${MONGO_DB} - DOCKER_NETWORK=linux-odyssey-players - SECRET_KEY=${SECRET_KEY} - TRUSTED_PROXIES=${TRUSTED_PROXIES} - BACKEND_URL=http://backend:3000 - BASE_URL=${BASE_URL} + - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} + - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} + - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} + - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} + - SURVEY_URL=${SURVEY_URL} + - BUG_REPORT_URL=${BUG_REPORT_URL} analytics: image: linuxodyssey/analytics @@ -53,8 +60,11 @@ services: - internal ports: - '3001:3001' + env_file: + - .env + - .env.prod environment: - - MONGO_URL=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@db:27017/${MONGO_DB} + - MONGO_URL=mongodb://${MONGO_USER}:${MONGO_PASS}@db:27017/${MONGO_DB} - ADMIN_USERNAME=${ADMIN_USERNAME} - ADMIN_PASSWORD=${ADMIN_PASSWORD} @@ -73,14 +83,8 @@ services: - MONGO_INITDB_ROOT_USERNAME=admin - MONGO_INITDB_ROOT_PASSWORD=${MONGO_ROOT_PASSWORD} - MONGO_USER=${MONGO_USER} - - MONGO_PASS=${MONGO_PASSWORD} + - MONGO_PASS=${MONGO_PASS} - MONGO_DB=${MONGO_DB} - - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} - - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} - - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} - - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} - - SURVEY_URL=${SURVEY_URL} - - BUG_REPORT_URL=${BUG_REPORT_URL} base: image: linuxodyssey/quest-base diff --git a/docker-compose.yml b/docker-compose.yml index f92fea5..702c4df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,15 +13,12 @@ services: base: image: linuxodyssey/quest-base networks: - - players + - players build: context: . dockerfile: ./quests/Dockerfile extra_hosts: - 'host.docker.internal:host-gateway' - volumes: - # - ./config/ssh_key.pub:/ssh_key.pub:ro - - ./quests/entrypoint.sh:/entrypoint.sh:ro networks: internal: diff --git a/quests/Dockerfile b/quests/Dockerfile index cffeadb..99bfbab 100644 --- a/quests/Dockerfile +++ b/quests/Dockerfile @@ -25,6 +25,7 @@ RUN rm -f /etc/motd /etc/issue /etc/issue.net /etc/update-motd.d/* RUN rm -rf /etc/skel && mkdir -p /etc/skel RUN mkdir -p /etc/ssh/authorized_keys && chmod 755 /etc/ssh/authorized_keys COPY ./quests/.includes/sshd_config /etc/ssh/sshd_config +COPY ./quests/entrypoint.sh /entrypoint.sh EXPOSE 22 diff --git a/quests/entrypoint.sh b/quests/entrypoint.sh index 0127e43..7951103 100644 --- a/quests/entrypoint.sh +++ b/quests/entrypoint.sh @@ -10,9 +10,10 @@ if ! id -u ${USERNAME} >/dev/null 2>&1; then find / -name ".gitkeep" -type f -exec rm -f {} + echo "Adding public key for $USERNAME" - PUB_KEY=/etc/ssh/authorized_keys/${USERNAME} - cp /ssh_key.pub ${PUB_KEY} - chmod 644 ${PUB_KEY} + echo "Public key: $PUB_KEY" + PUB_KEY_PATH=/etc/ssh/authorized_keys/${USERNAME} + echo $PUB_KEY > ${PUB_KEY_PATH} + chmod 644 ${PUB_KEY_PATH} fi mkdir -p /run/sshd diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 2aac10a..1b45aac 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -24,6 +24,7 @@ const newContainerOptions = ( ExposedPorts: { '22/tcp': {}, }, + Env: [`PUB_KEY=${config.docker.keypair.publicKey}`], }) export async function createContainer( @@ -32,21 +33,23 @@ export async function createContainer( imageId: string ): Promise { let binds: string[] = [ - `${config.projectRoot}/config/ssh_key.pub:/ssh_key.pub:ro`, - `${config.projectRoot}/quests/entrypoint.sh:/entrypoint.sh:ro`, + // `${config.docker.hostProjectRoot}/config/ssh_key.pub:/ssh_key.pub:ro`, ] if (await questHomeExists(questId)) { logger.info('Mounting quest home', questId) - binds.push(`${config.projectRoot}/quests/${questId}/home:/etc/skel:ro`) + binds.push( + `${config.docker.hostProjectRoot}/quests/${questId}/home:/etc/skel:ro` + ) } if (!config.isProduction && config.docker.mountQuest && imageId !== 'base') { logger.info('Mounting quest folder', questId) binds = [ - `${config.projectRoot}/quests/${questId}/home:/home/commander`, - `${config.projectRoot}/packages/container:/usr/local/lib/container`, + `${config.docker.hostProjectRoot}/quests/${questId}/home:/home/commander`, + `${config.docker.hostProjectRoot}/packages/container:/usr/local/lib/container`, ] } const option = newContainerOptions(name, imageId, { binds }) + console.log('Creating container', option) await createNetworkIfNotExists(config.docker.network) return engine.createContainer(option) } @@ -65,7 +68,9 @@ async function createNetworkIfNotExists(network: string) { async function questHomeExists(imageId: string) { try { - const stat = await fs.stat(`${config.projectRoot}/quests/${imageId}/home`) + const stat = await fs.stat( + `${config.docker.hostProjectRoot}/quests/${imageId}/home` + ) return stat.isDirectory() } catch { return false @@ -99,7 +104,8 @@ export async function attachContainer( // eslint-disable-next-line no-await-in-loop const stream = await connectToSSH(containerIp, { token }) return stream - } catch { + } catch (err) { + logger.error('Failed to connect to SSH', err) // eslint-disable-next-line no-await-in-loop await new Promise((resolve) => { setTimeout(resolve, 1000) From 44ecd3b87a9cd7270809a696902cef84ac8220ea Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 21 Oct 2024 12:11:24 +0800 Subject: [PATCH 22/24] fix: mounting quest home into production container --- docker-compose.prod.yml | 2 ++ server/src/config.ts | 2 ++ server/src/containers/docker.ts | 9 ++------- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 92ec878..1495d9e 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -30,6 +30,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./logs:/app/logs - ./config:/app/config + - ./quests:/app/quests:ro env_file: - .env - .env.prod @@ -40,6 +41,7 @@ services: - TRUSTED_PROXIES=${TRUSTED_PROXIES} - BACKEND_URL=http://backend:3000 - BASE_URL=${BASE_URL} + - HOST_PROJECT_ROOT=${PWD} - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} diff --git a/server/src/config.ts b/server/src/config.ts index ea258af..4be7e8d 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -76,6 +76,7 @@ function createConfig() { defaultImage: get('QUEST_IMAGE', 'linuxodyssey/quest-base'), imagePrefix: get('DOCKER_PREFIX', 'linuxodyssey/quest-'), mountQuest: process.env.MOUNT_QUEST === 'true', + hostProjectRoot: get('HOST_PROJECT_ROOT', projectRoot), keypairPath, keypair, }, @@ -94,6 +95,7 @@ function createConfig() { function getProjectRoot(): string { const filename = fileURLToPath(import.meta.url) const root = path.join(path.dirname(filename), '..', '..') + console.log('Project root:', root) return root } diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 1b45aac..5714cc1 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -32,9 +32,7 @@ export async function createContainer( questId: string, imageId: string ): Promise { - let binds: string[] = [ - // `${config.docker.hostProjectRoot}/config/ssh_key.pub:/ssh_key.pub:ro`, - ] + let binds: string[] = [] if (await questHomeExists(questId)) { logger.info('Mounting quest home', questId) binds.push( @@ -49,7 +47,6 @@ export async function createContainer( ] } const option = newContainerOptions(name, imageId, { binds }) - console.log('Creating container', option) await createNetworkIfNotExists(config.docker.network) return engine.createContainer(option) } @@ -68,9 +65,7 @@ async function createNetworkIfNotExists(network: string) { async function questHomeExists(imageId: string) { try { - const stat = await fs.stat( - `${config.docker.hostProjectRoot}/quests/${imageId}/home` - ) + const stat = await fs.stat(`${config.projectRoot}/quests/${imageId}/home`) return stat.isDirectory() } catch { return false From d7c5c756f38bd963b38e5d7008ee36b3106297b8 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 21 Oct 2024 12:11:32 +0800 Subject: [PATCH 23/24] feat: add production env sample --- .env.prod.sample | 46 ++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + 2 files changed, 47 insertions(+) create mode 100644 .env.prod.sample diff --git a/.env.prod.sample b/.env.prod.sample new file mode 100644 index 0000000..e269297 --- /dev/null +++ b/.env.prod.sample @@ -0,0 +1,46 @@ +HOST=localhost +PORT=3000 +BASE_URL=https://example.com +SECRET_KEY=YOU_SHOULD_CHANGE_THIS +BACKEND_URL=http://backend:3000 + +MONGO_ROOT_PASSWORD=adminpassword +MONGO_USER=commander +MONGO_PASS=commander +MONGO_DB=linux-odyssey + +# Admin user credentials for analytics +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin + +# Optional +# CONTAINER_EXPIRY=60 # 60 minutes +# SESSION_MAX_AGE=168 # 7 days +# TRUSTED_PROXIES=1.2.3.4 +# valid configurations see isIP & isIPRange in https://github.com/validatorjs/validator.js?tab=readme-ov-file#validators + +# GOOGLE_CLIENT_ID= +# GOOGLE_CLIENT_SECRET= +# GITHUB_CLIENT_ID= +# GITHUB_CLIENT_SECRET= +# SURVEY_URL= +# BUG_REPORT_URL= +# DOCKER_NETWORK= + +# SURVEY_URL= +# BUG_REPORT_URL= + +# Docker related config +# DOCKER_NETWORK=host +# QUEST_IMAGE=linuxodyssey/quest-base +# DOCKER_PREFIX=linuxodyssey/quest- + +# To mount quest folder from host into container, for development purpose +# MOUNT_QUEST=true + +# LOG_PATH= + +# Testing, create a user with the following credentials, disable rate limiting +# TESTING=true +# TESTING_USERNAME=alex +# TESTING_PASSWORD=Alex1234 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 192793a..eeb5072 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ built .env.test.local .env.production.local .env.local +.env.prod # parcel-bundler cache (https://parceljs.org/) .cache From 5cf375905d025988bc3a08891d76eee31ff65ff8 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 21 Oct 2024 12:12:52 +0800 Subject: [PATCH 24/24] feat: remove debug logs --- server/src/config.ts | 1 - server/src/containers/docker.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/config.ts b/server/src/config.ts index 4be7e8d..3f316a8 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -95,7 +95,6 @@ function createConfig() { function getProjectRoot(): string { const filename = fileURLToPath(import.meta.url) const root = path.join(path.dirname(filename), '..', '..') - console.log('Project root:', root) return root } diff --git a/server/src/containers/docker.ts b/server/src/containers/docker.ts index 5714cc1..03d4183 100644 --- a/server/src/containers/docker.ts +++ b/server/src/containers/docker.ts @@ -58,7 +58,7 @@ async function createNetworkIfNotExists(network: string) { }, }) if (dockerNetworks.length === 0) { - console.log('Creating player network', network) + logger.log('Creating player network', network) await engine.createNetwork({ Name: network }) } }