generate-desktop-compose.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. import {readFileSync,writeFileSync} from 'node:fs'
  2. import {resolve} from 'node:path'
  3. import {fileURLToPath} from 'node:url'
  4. const root=resolve(import.meta.dirname,'..')
  5. const recipes={
  6. standard:{image:'${YAOYAO_DESKTOP_IMAGE:-hermes-yaoyao-desktop:0.4.0}',dockerfile:'Dockerfile'},
  7. cursor:{image:'${YAOYAO_CURSOR_DESKTOP_IMAGE:-yaoyao-desktop:cursor}',dockerfile:'Dockerfile.cursor',platform:'linux/amd64'},
  8. }
  9. export function generateDesktopCompose(base,entries,source='deploy/compose-desktops.json'){
  10. if(!Array.isArray(entries)||entries.length<1||entries.length>32)throw new Error('Compose 桌面数量须为 1–32')
  11. for(const d of entries){
  12. if(!d||!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(d.id)||!/^desktop-[a-z0-9-]+$/.test(d.service)||typeof d.name!=='string'||!d.name.trim())throw new Error('Compose 桌面定义无效')
  13. if(d.imageKey!==undefined&&!Object.hasOwn(recipes,d.imageKey))throw new Error('桌面镜像须为 standard 或 cursor')
  14. }
  15. if(new Set(entries.map(d=>d.id)).size!==entries.length||new Set(entries.map(d=>d.service)).size!==entries.length)throw new Error('Compose 桌面定义重复')
  16. const q=JSON.stringify
  17. const targets=entries.map(d=>({id:d.id,name:d.name,socketPath:`/run/yaoyao-desktops/${d.service}/desktop.sock`,imageKey:d.imageKey??'standard'}))
  18. base=base.replace(' HERMES_YAOYAO_LOCAL_VM_HOST: "runner"',' HERMES_YAOYAO_LOCAL_VM_HOST: "runner"\n HERMES_YAOYAO_COMPOSE_DESKTOPS: '+q(JSON.stringify(targets)))
  19. .replace(' - yaoyao-data:/home/node/.yaoyao',' - yaoyao-data:/home/node/.yaoyao\n'+entries.map(d=>` - ${d.service}-ipc:/run/yaoyao-desktops/${d.service}:ro`).join('\n'))
  20. const volumesAt=base.indexOf('\nvolumes:')
  21. if(volumesAt<0||!base.includes('HERMES_YAOYAO_COMPOSE_DESKTOPS'))throw new Error('compose.yaml 模板结构已变化')
  22. let yaml=`# Generated from compose.yaml and ${source}.\n`+base.slice(0,volumesAt)+'\n'
  23. for(const d of entries){
  24. const recipe=recipes[d.imageKey??'standard']
  25. yaml+=` ${d.service}:
  26. image: "${recipe.image}"
  27. ${recipe.platform?` platform: ${recipe.platform}\n`:''} build:
  28. context: ./deploy/computer
  29. dockerfile: ${recipe.dockerfile}
  30. hostname: ${d.service}
  31. extra_hosts:
  32. - "${d.service}:127.0.0.1"
  33. init: true
  34. restart: unless-stopped
  35. network_mode: none
  36. shm_size: 512m
  37. mem_limit: 4g
  38. cpus: 2
  39. pids_limit: 512
  40. cap_drop:
  41. - ALL
  42. cap_add:
  43. - SETUID
  44. - SETGID
  45. - CHOWN
  46. - KILL
  47. environment:
  48. YAOYAO_COMPOSE_DESKTOP_ID: ${q(d.id)}
  49. volumes:
  50. - ${d.service}-workspace:/home/cua/workspace
  51. - ${d.service}-ipc:/run/yaoyao-private/bridge
  52. `
  53. }
  54. yaml+=base.slice(volumesAt)+'\n'
  55. for(const d of entries)yaml+=` ${d.service}-workspace:\n ${d.service}-ipc:\n`
  56. return yaml
  57. }
  58. if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)){
  59. const base=readFileSync(resolve(root,'compose.yaml'),'utf8')
  60. for(const suffix of ['', '.cursor']){
  61. const source=`deploy/compose-desktops${suffix}.json`
  62. const entries=JSON.parse(readFileSync(resolve(root,source),'utf8'))
  63. const yaml=generateDesktopCompose(base,entries,source)
  64. const name=`compose.desktops${suffix}.yaml`,output=resolve(root,name)
  65. if(process.argv.includes('--check')){if(readFileSync(output,'utf8')!==yaml)throw new Error(`${name} 与桌面定义不同步,请运行 node scripts/generate-desktop-compose.mjs`)}
  66. else writeFileSync(output,yaml)
  67. console.log(`${name}:${entries.length} 台共享桌面`)
  68. }
  69. }