diff --git a/docker-compose.yml b/docker-compose.yml index b2b3e9f..e509387 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,12 +87,6 @@ services: context: . dockerfile: ./quests/Dockerfile - helloworld: - image: lancatlin/linux-odyssey:helloworld - build: - context: . - dockerfile: quests/helloworld/Dockerfile - networks: internal: players: diff --git a/packages/models/quest.js b/packages/models/quest.js index 04c9d10..4c500bd 100644 --- a/packages/models/quest.js +++ b/packages/models/quest.js @@ -79,6 +79,10 @@ const Quest = model( type: String, required: true, }, + image: { + type: String, + required: true, + }, requirements: { type: [ { diff --git a/quests/helloworld/Dockerfile b/quests/helloworld/Dockerfile index e845379..f053a50 100644 --- a/quests/helloworld/Dockerfile +++ b/quests/helloworld/Dockerfile @@ -1,5 +1,3 @@ FROM lancatlin/linux-odyssey:base -COPY ./quests/helloworld/home ${HOME} - -CMD [ "/bin/zsh" ] +COPY ./home ${HOME} diff --git a/quests/spell/Dockerfile b/quests/spell/Dockerfile index 631485c..f053a50 100644 --- a/quests/spell/Dockerfile +++ b/quests/spell/Dockerfile @@ -1,3 +1,3 @@ FROM lancatlin/linux-odyssey:base -CMD [ "/bin/zsh" ] +COPY ./home ${HOME} diff --git a/server/src/config.js b/server/src/config.js index 75f43d8..b904580 100644 --- a/server/src/config.js +++ b/server/src/config.js @@ -40,6 +40,9 @@ const config = { trustedProxies: getTrustProxies('TRUSTED_PROXIES'), surveyUrl: getUrl('SURVEY_URL', ''), bugReportUrl: getUrl('BUG_REPORT_URL', ''), + dockerImage: get('DOCKER_IMAGE', 'lancatlin/linux-odyssey'), + + mountQuest: get('MOUNT_QUEST', ''), } config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`) diff --git a/server/src/containers/docker.js b/server/src/containers/docker.js index 5f21147..b1485ce 100644 --- a/server/src/containers/docker.js +++ b/server/src/containers/docker.js @@ -4,7 +4,6 @@ import config from '../config.js' const engine = new Docker() const containerOptions = { - Image: 'lancatlin/linux-odyssey:helloworld', AttachStdin: true, AttachStdout: true, AttachStderr: true, @@ -13,23 +12,24 @@ const containerOptions = { StdinOnce: false, HostConfig: { NetworkMode: config.dockerNetwork, - Binds: - !config.isProduction && config.hostPwd - ? [ - `${config.hostPwd}/quests/helloworld/home:/home/commander`, - `${config.hostPwd}/packages/container:/usr/local/lib/container`, - ] - : [], }, } // const network = engine.getNetwork(config.dockerNetwork) -export function createContainer(name) { - return engine.createContainer({ +export function createContainer(name, tag) { + const option = { ...containerOptions, name, - }) + Image: `${config.dockerImage}:${tag}`, + } + if (!config.isProduction && config.hostPwd && config.mountQuest) { + option.HostConfig.Bind = [ + `${config.hostPwd}/quests/${config.mountQuest}/home:/home/commander`, + `${config.hostPwd}/packages/container:/usr/local/lib/container`, + ] + } + return engine.createContainer(option) } export async function getAndStartContainer(id) { @@ -45,7 +45,6 @@ export async function getAndStartContainer(id) { } export async function attachContainer(container, { token }) { - // Create an exec instance with bash shell const exec = await container.exec({ AttachStdin: true, AttachStdout: true, diff --git a/server/src/game/sessionManager.js b/server/src/game/sessionManager.js index 19e79c8..97ef708 100644 --- a/server/src/game/sessionManager.js +++ b/server/src/game/sessionManager.js @@ -33,7 +33,8 @@ export async function createNewSession(user, questId) { }) const container = await createContainer( - `quest-${quest.id}-${user.username}-${Date.now()}` + `quest-${quest.id}-${user.username}-${Date.now()}`, + quest.image ) const sessionHandler = new SessionHandler( diff --git a/server/src/utils/quest.js b/server/src/utils/quest.js index ffb4041..b189059 100644 --- a/server/src/utils/quest.js +++ b/server/src/utils/quest.js @@ -7,31 +7,38 @@ import { Quest } from '@linux-odyssey/models' const questDirectory = path.join(process.cwd(), '..', 'quests') export default async function loadAndUpdateQuests() { - const questNames = await fs.readdir(questDirectory) + const questNames = await fs.readdir(questDirectory, { + withFileTypes: true, + }) const quests = Promise.all( questNames - .filter((name) => !name.startsWith('.')) - .map(async (id) => { - // Check if id is directory - const stat = await fs.stat(path.join(questDirectory, id)) - if (!stat.isDirectory()) { - return null + .filter((dirent) => dirent.isDirectory() && !dirent.name.startsWith('.')) + .map(async (dirent) => { + const id = dirent.name + const questPath = path.join(questDirectory, id) + const files = await fs.readdir(questPath) + if (!files.includes('game.yml')) { + throw new Error(`Quest ${id} is missing game.yml`) } - const fullPath = path.join(questDirectory, id, 'game.yml') try { - // Read and parse the README.md file. - const body = await fs.readFile(fullPath, 'utf-8') + const body = await fs.readFile( + path.join(questPath, 'game.yml'), + 'utf-8' + ) const quest = yaml.parse(body, { merge: true, }) + const image = files.includes('Dockerfile') ? id : 'base' + return Quest.findByIdAndUpdate( id, { _id: id, + image, ...quest, }, {