generate-desktop-compose.mjs 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  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`,networkSocketPath:`/run/yaoyao-networks/${d.service}/gateway.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\n - ${d.service}-network-ipc:/run/yaoyao-networks/${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. init: true
  31. restart: unless-stopped
  32. network_mode: "service:${d.service}-network"
  33. depends_on:
  34. ${d.service}-network:
  35. condition: service_started
  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. ${d.service}-network:
  53. image: localhost/yaoyao/network:1
  54. build:
  55. context: ./deploy/computer
  56. dockerfile: Dockerfile.network
  57. command: ["--init", "--socket"]
  58. init: true
  59. restart: unless-stopped
  60. network_mode: none
  61. dns: ["198.18.0.1"]
  62. read_only: true
  63. cap_drop: ["ALL"]
  64. cap_add: ["NET_ADMIN"]
  65. devices: ["/dev/net/tun:/dev/net/tun"]
  66. mem_limit: 128m
  67. pids_limit: 64
  68. environment:
  69. YAOYAO_COMPOSE_DESKTOP_ID: ${q(d.id)}
  70. volumes:
  71. - ${d.service}-network-ipc:/run/yaoyao-network
  72. `
  73. }
  74. yaml+=base.slice(volumesAt)+'\n'
  75. for(const d of entries)yaml+=` ${d.service}-workspace:\n ${d.service}-ipc:\n ${d.service}-network-ipc:\n`
  76. return yaml
  77. }
  78. if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)){
  79. const base=readFileSync(resolve(root,'compose.yaml'),'utf8')
  80. for(const suffix of ['', '.cursor']){
  81. const source=`deploy/compose-desktops${suffix}.json`
  82. const entries=JSON.parse(readFileSync(resolve(root,source),'utf8'))
  83. const yaml=generateDesktopCompose(base,entries,source)
  84. const name=`compose.desktops${suffix}.yaml`,output=resolve(root,name)
  85. if(process.argv.includes('--check')){if(readFileSync(output,'utf8')!==yaml)throw new Error(`${name} 与桌面定义不同步,请运行 node scripts/generate-desktop-compose.mjs`)}
  86. else writeFileSync(output,yaml)
  87. console.log(`${name}:${entries.length} 台共享桌面`)
  88. }
  89. }