From 06cea4bb3769522e38d70395de182f1cfb1b59dd Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 9 Jan 2026 18:14:26 +0000 Subject: [PATCH] feat(displays): add display detection and management (sway) with daemon APIs and UI controls --- changelog.md | 9 ++ ecoos_daemon/ts/daemon/index.ts | 43 ++++++++- ecoos_daemon/ts/daemon/process-manager.ts | 90 ++++++++++++++++++ ecoos_daemon/ts/daemon/system-info.ts | 12 +++ ecoos_daemon/ts/ui/server.ts | 84 ++++++++++++++++ ecoos_daemon/ts/version.ts | 2 +- .../includes.chroot/opt/eco/bin/eco-daemon | Bin 86979380 -> 86997889 bytes package.json | 2 +- 8 files changed, 239 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index c8b4b3b..746c0ba 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2026-01-09 - 0.4.0 - feat(displays) +add display detection and management (sway) with daemon APIs and UI controls + +- Introduce DisplayInfo type in system-info.ts +- Add ProcessManager methods: getDisplays, setDisplayEnabled, setKioskDisplay (invoke swaymsg via runuser) +- Add daemon methods to expose getDisplays, setDisplayEnabled and setKioskDisplay with runtime/Wayland context and status checks +- Add UI server endpoints: GET /api/displays and POST /api/displays/{name}/(enable|disable|primary) and frontend UI to list and control displays (polling + buttons) +- Bump VERSION and package.json to 0.3.9 + ## 2026-01-09 - 0.3.8 - fix(ci(release-workflow)) use npx tsx to run release-upload.ts in the Gitea release workflow instead of installing tsx globally diff --git a/ecoos_daemon/ts/daemon/index.ts b/ecoos_daemon/ts/daemon/index.ts index e0a31aa..93e0498 100644 --- a/ecoos_daemon/ts/daemon/index.ts +++ b/ecoos_daemon/ts/daemon/index.ts @@ -5,7 +5,7 @@ */ import { ProcessManager } from './process-manager.ts'; -import { SystemInfo } from './system-info.ts'; +import { SystemInfo, type DisplayInfo } from './system-info.ts'; import { Updater } from './updater.ts'; import { UIServer } from '../ui/server.ts'; import { runCommand } from '../utils/command.ts'; @@ -147,6 +147,47 @@ export class EcoDaemon { return this.updater.upgradeToVersion(version); } + async getDisplays(): Promise { + if (this.swayStatus.state !== 'running') { + return []; + } + const uid = await this.getUserUid(); + return this.processManager.getDisplays({ + runtimeDir: `/run/user/${uid}`, + waylandDisplay: this.config.waylandDisplay, + }); + } + + async setDisplayEnabled(name: string, enabled: boolean): Promise<{ success: boolean; message: string }> { + if (this.swayStatus.state !== 'running') { + return { success: false, message: 'Sway is not running' }; + } + this.log(`${enabled ? 'Enabling' : 'Disabling'} display ${name}`); + const uid = await this.getUserUid(); + const result = await this.processManager.setDisplayEnabled( + { runtimeDir: `/run/user/${uid}`, waylandDisplay: this.config.waylandDisplay }, + name, + enabled + ); + return { success: result, message: result ? `Display ${name} ${enabled ? 'enabled' : 'disabled'}` : 'Failed' }; + } + + async setKioskDisplay(name: string): Promise<{ success: boolean; message: string }> { + if (this.swayStatus.state !== 'running') { + return { success: false, message: 'Sway is not running' }; + } + if (this.chromiumStatus.state !== 'running') { + return { success: false, message: 'Chromium is not running' }; + } + this.log(`Moving kiosk to display ${name}`); + const uid = await this.getUserUid(); + const result = await this.processManager.setKioskDisplay( + { runtimeDir: `/run/user/${uid}`, waylandDisplay: this.config.waylandDisplay }, + name + ); + return { success: result, message: result ? `Kiosk moved to ${name}` : 'Failed' }; + } + async start(): Promise { this.log('EcoOS Daemon starting...'); diff --git a/ecoos_daemon/ts/daemon/process-manager.ts b/ecoos_daemon/ts/daemon/process-manager.ts index ba9b9bc..729d71c 100644 --- a/ecoos_daemon/ts/daemon/process-manager.ts +++ b/ecoos_daemon/ts/daemon/process-manager.ts @@ -5,6 +5,7 @@ */ import { runCommand } from '../utils/command.ts'; +import type { DisplayInfo } from './system-info.ts'; export interface SwayConfig { runtimeDir: string; @@ -306,6 +307,95 @@ for_window [app_id="chromium-browser"] fullscreen enable } } + /** + * Get connected displays via swaymsg + */ + async getDisplays(config: { runtimeDir: string; waylandDisplay: string }): Promise { + const env: Record = { + XDG_RUNTIME_DIR: config.runtimeDir, + WAYLAND_DISPLAY: config.waylandDisplay, + }; + + const envString = Object.entries(env) + .map(([k, v]) => `${k}=${v}`) + .join(' '); + + const cmd = new Deno.Command('runuser', { + args: ['-u', this.user, '--', 'sh', '-c', `${envString} swaymsg -t get_outputs`], + stdout: 'piped', + stderr: 'piped', + }); + + try { + const result = await cmd.output(); + if (!result.success) { + console.error('[displays] Failed to get outputs'); + return []; + } + + const outputs = JSON.parse(new TextDecoder().decode(result.stdout)); + return outputs.map((output: { + name: string; + make: string; + model: string; + serial: string; + active: boolean; + current_mode?: { width: number; height: number; refresh: number }; + focused: boolean; + }) => ({ + name: output.name, + make: output.make || 'Unknown', + model: output.model || 'Unknown', + serial: output.serial || '', + active: output.active, + width: output.current_mode?.width || 0, + height: output.current_mode?.height || 0, + refreshRate: Math.round((output.current_mode?.refresh || 0) / 1000), + isPrimary: output.focused, + })); + } catch (error) { + console.error(`[displays] Error: ${error}`); + return []; + } + } + + /** + * Enable or disable a display + */ + async setDisplayEnabled( + config: { runtimeDir: string; waylandDisplay: string }, + name: string, + enabled: boolean + ): Promise { + const command = `output ${name} ${enabled ? 'enable' : 'disable'}`; + console.log(`[displays] ${command}`); + return this.swaymsg(config, command); + } + + /** + * Move the kiosk browser to a specific display + */ + async setKioskDisplay( + config: { runtimeDir: string; waylandDisplay: string }, + name: string + ): Promise { + console.log(`[displays] Setting primary display to ${name}`); + + // Focus the chromium window and move it to the target output + const commands = [ + `[app_id="chromium-browser"] focus`, + `move container to output ${name}`, + `focus output ${name}`, + `[app_id="chromium-browser"] fullscreen enable`, + ]; + + for (const cmd of commands) { + await this.swaymsg(config, cmd); + } + + return true; + } + private async pipeOutput( process: Deno.ChildProcess, name: string diff --git a/ecoos_daemon/ts/daemon/system-info.ts b/ecoos_daemon/ts/daemon/system-info.ts index 7990548..624c094 100644 --- a/ecoos_daemon/ts/daemon/system-info.ts +++ b/ecoos_daemon/ts/daemon/system-info.ts @@ -52,6 +52,18 @@ export interface AudioDevice { isDefault: boolean; } +export interface DisplayInfo { + name: string; // e.g., "DP-1", "HDMI-A-1", "HEADLESS-1" + make: string; // Manufacturer + model: string; // Model name + serial: string; // Serial number + active: boolean; // Currently enabled + width: number; // Resolution width + height: number; // Resolution height + refreshRate: number; // Hz + isPrimary: boolean; // Has the focused window (kiosk) +} + export interface SystemInfoData { hostname: string; cpu: CpuInfo; diff --git a/ecoos_daemon/ts/ui/server.ts b/ecoos_daemon/ts/ui/server.ts index 0219324..95ded38 100644 --- a/ecoos_daemon/ts/ui/server.ts +++ b/ecoos_daemon/ts/ui/server.ts @@ -129,6 +129,28 @@ export class UIServer { } } + if (path === '/api/displays') { + const displays = await this.daemon.getDisplays(); + return new Response(JSON.stringify({ displays }), { headers }); + } + + // Display control endpoints: /api/displays/{name}/{action} + const displayMatch = path.match(/^\/api\/displays\/([^/]+)\/(enable|disable|primary)$/); + if (displayMatch && req.method === 'POST') { + const name = decodeURIComponent(displayMatch[1]); + const action = displayMatch[2]; + + let result; + if (action === 'enable') { + result = await this.daemon.setDisplayEnabled(name, true); + } else if (action === 'disable') { + result = await this.daemon.setDisplayEnabled(name, false); + } else if (action === 'primary') { + result = await this.daemon.setKioskDisplay(name); + } + return new Response(JSON.stringify(result), { headers }); + } + return new Response(JSON.stringify({ error: 'Not Found' }), { status: 404, headers, @@ -384,6 +406,10 @@ export class UIServer { Check for Updates +
+

Displays

+
+

Input Devices

@@ -698,6 +724,64 @@ export class UIServer { fetchUpdates(); setInterval(fetchUpdates, 60000); // Check every minute + // Display management + function updateDisplaysUI(data) { + const list = document.getElementById('displays-list'); + if (!data.displays || data.displays.length === 0) { + list.innerHTML = '
No displays detected
'; + return; + } + list.innerHTML = data.displays.map(d => + '
' + + '
' + + '
' + d.name + '
' + + '
' + + d.width + 'x' + d.height + ' @ ' + d.refreshRate + 'Hz' + + (d.make !== 'Unknown' ? ' • ' + d.make : '') + + '
' + + '
' + + '
' + + (d.isPrimary + ? 'Primary' + : '') + + '' + + '
' + + '
' + ).join(''); + } + + function fetchDisplays() { + fetch('/api/displays') + .then(r => r.json()) + .then(updateDisplaysUI) + .catch(err => console.error('Failed to fetch displays:', err)); + } + + function toggleDisplay(name, enable) { + fetch('/api/displays/' + encodeURIComponent(name) + '/' + (enable ? 'enable' : 'disable'), { method: 'POST' }) + .then(r => r.json()) + .then(result => { + if (!result.success) alert(result.message); + fetchDisplays(); + }) + .catch(err => alert('Error: ' + err)); + } + + function setKioskDisplay(name) { + fetch('/api/displays/' + encodeURIComponent(name) + '/primary', { method: 'POST' }) + .then(r => r.json()) + .then(result => { + if (!result.success) alert(result.message); + fetchDisplays(); + }) + .catch(err => alert('Error: ' + err)); + } + + fetchDisplays(); + setInterval(fetchDisplays, 5000); // Refresh every 5 seconds + // Initial fetch fetch('/api/status') .then(r => r.json()) diff --git a/ecoos_daemon/ts/version.ts b/ecoos_daemon/ts/version.ts index 685b359..0b28a75 100644 --- a/ecoos_daemon/ts/version.ts +++ b/ecoos_daemon/ts/version.ts @@ -1 +1 @@ -export const VERSION = "0.3.6"; +export const VERSION = "0.3.9"; diff --git a/isobuild/config/includes.chroot/opt/eco/bin/eco-daemon b/isobuild/config/includes.chroot/opt/eco/bin/eco-daemon index f17084854528a380538883cf4224e9af2c4a156e..64d4bd1d215f5b49aea3547e2ae0b658585faa2f 100755 GIT binary patch delta 15449 zcmeHt2Y6IP*Z(`^QbG%m(90zvNnlGhz0fv=jx;F>NS0)itR%ZJyP;!HM@9XqSh2xi z@4aFR*t^(!?;U%WZ|2^+VT1Vk`v3et-}8NsJbJTt%9(S1=ggUN&fJ^czccfQ@9)Z7 zFNmOm2`&uLL39+IL}!sCx`?i#o9Hfjh-A@I^b);AibxfGL|@TQ^cMrfKru)R7DL2P zVTxfQP3$Lzi~Yq2kuEYsrWh$k372pSkMN2t;S<>+N92k;kuM5Fp(ql?qC|`qW5if7 zPK+0RQ7Xzrxu_5m#6&SkOcqnbR549V7YB$LVy2iSW{WxEKrvU$6Z6FaagaDzEEJ2x zVsVIAA}U2dEEQFvS_DOns1ir2*J;tlbpcuTx3-VyJL_r&|+1M#8wNPH|l5ub|B z#OLA*u}gd@z7k)HZ^XCaJMq2vLHsCw5D~nPEbKGfnXxRB!bBVQwXLKOe2_1Z~(y!f|&%f2xb$^Avlm= zF2Ov4`2-6H4k9?1U?IUGg2e=f5G*06BnS{JC8#2(CI}ML5Y!UX5rha1C0IsKPtZUR zCWsI;5*$X*L=Yv25i}DlCs;wSl3*3VYJxQcYYEm798Se6C6Wu zEWrkX;|Pu?IDr5JClZ`Qa5BM0f>Q`iB{+@Xbb>Po&Lr4Ga2CPY1m_T(OK={+W`ZpQ zl3**r`2-gbTu5*c!NmlZ5L`-d8NuZQR}frDa23JT1lJH;OK=^*^#nH%+(>W}!OaA> z5Zp>|8^Jb$+X?OO2H^Ng+bAQq9a8oip~^C6kRB~Qgox}PSJxRnW85}FN)q2DHN#` zeJJ`;^rPrcF@Rzq#UP5o6hkP6QkWFODAFkQqZm%HKg9@&bczg$Op1{dqbOVyZVC^D zmm-V8N0CjDLy=37N0CoaKv76hL{UspLNS_R48>TAaTMbz{1l}WWfbKU6%-RFCQ?kI zm`pK+Vk*Tnis=*wP|Tp1NimCJHpLu@11aWG%%hl3v4G+rii0T@QY@laOmPUs5{gQS z0L4;@DvD}~AVm#DEkzwgh~iL+Wfb)k4HRLD2t^~sVH8aiQHmHvGsSX>6%;EeR#B{` zSVOUvVjacd6fG3%DUP5xlHw=|L2)$2F%-vAY@j%f;&_S^C_r%{#Yq$=Q*5L-h2m6- z(7@fyYJ6mL+xN%0oN+Z69myi4&O#rqT=P<%-75yi(8pHO^C@fpSE6kky6 zqWF^HD~hivzM=S*;ya4(DSn{%k>V$cpDBK!_?6-}irsVr17V;HjDa&43>_FcGIV0- z%#g&;g`q1$H-_#EJs6T1dNTB4=*^JAkjl`9p)W%}hW-o#7zQ#7Vi?RYgkdOy$uNu| zjbT5A;SBpTj9^G-$Y9817|Ae-!NuTa@Gy88vKV{}*$g=hxeR#>`3waNg$zXu#SA43 zqZ!69jAaq@4rN%zP|wi7 z5N3!lG%_5<(8LgBh%q!XEN583u##aF!)k^#3~L$IF&xg&!mys<2!8D5 za4f?HhT|BHXE=cY3@0+2#BeghMut-uPGvZa;dF*G7|vwa#BdhF*$n3}oXc|nTu;a-OO8183yfZ;)ghZr7ac!c3mhQ}Bl zXLy3)Nrs&aPcb~r@C?JV49_t<&+r1niwrL@yv*bIhF}%+32E&^SZ!x^h@D9Vf z4DT_#&+q}mhYTMve9Z6(!>0_NF?`PO1;Z|eFB!gK_?qDxhHn|ZWB8uo2ZkRReq#8U z;TMKq8Gd8f%{Fil4$8qeIETT}fukcwCyve>NgQ1`x^i^m=+4oDBblQoM=y@v94Q>B z9DO+Ya`fZq&oO{wAjcq%!5l+4hH{u3!#L77_Tw1Nu|LNMj&zO;j!ce`9HTf~9BvK| zhnFLZ!^e@$k;9S8k;jqGQNU5iQN&TqQNl5rV+_Ywj&U60Is6=@9AzBk92FcBI3{vT z;+V`ag<~qmG>+*U2XM^bn8`7VV>ZVejsrR7a?InH&#{2xAdZ7M7IG}&Sj=$<#}bZ8 zjsV9}jw+67jvz-3M=eJkM~LH4j%6J691R>{jtEC1$6*{z98r!KM>EHAjujj$IaYD3 z=2*kAmSY{q;T$a->p70#IFjQi4#9CW$1xnoa%|u@j^lWa6F9(eBF9M_Cv$A%IECX> zj?*|!=QxAoOpZ+)XK|d(aSq419OrRt=Gej^Iks|~&v60Ag&Y@gT+DF^$E6&Xaa_)E z1;>>fS8-g;aSg|{9M^GN&v66CjT|>|+{|$c$E_T`zV zj(a%n<+zXIevSt?9^`n4<6(|RI3DGAjN@^RCpezu*vat}$I~3oa6HTL9LMtl|-zyvgwv$J-q5aJ@C}A8>rg@e#+z9G`G}%JCV;=Nw;f z?Be*6<13D@Ilkfemg75)?>Tto$j-NSx;rNx~H;&zWg8^cI8ej&v0mDEC104-? zGSJyTl7TJ;x*F(apu2${29gc*G|e|*Sg+)$R6lassHmDZeA9BXP0y3!ibiWmCwec8D#Uv6HZPjy9A zOHNinR-UVgqTS-H7BmoGajCwp&#dD*!I zIWAvTevw{F6U@)?h*0%F502x$^U}a@y1LW&3;u@<~N6ExRbcC@`QNAlz;ip>mfR*Db z%q_^0+ZD-iSp~U@rYkS|_XPDS1+JVz4WdmhPw^(bkGYI7MnPUdVU9dZ{qEO8>9^3A zo4an^V)fsH*R-CcTDP(8E;F{OF=&>DqK)-|Rjt!^x{Ozoht~#UPD?Z+v)G*36ln-W zgQK0c$>EyF!o_m?3b&CeTYPS#n|!3Z+ZZC>>~Szjk~7oXhI`Y3IZL|Af=+Iudsj~a zIY~0zM(>njv(nqt9QHOxgH7JyYnnsV>ni1n?_5Tqe_dutl4-6>Qjb7%Rk+HGwlPx? z4lJz?R%e6*4Z&hF8fyxLYh7m0ZY?&KMk4jWKv-73r0B?p6notg*c&R>z3wuSJW(onMUW$nyL`d-!szeYLHo|eHo zSjo+5BXmyK2va*crZ0U8%i$K5Y5(u$-Mu{@e~MYxM9lsOs!Ru}KR06=jn*zHZX+_h zM^>q;q15khC@t~(%jzmxHx#;!kx5Baet)#AWU{}^C(Ze8V}FTpZlgyJSB2kSp_>=z zhhNT^q5Aj?zki1GU8hc@l=*)Dd{>zwRWjXQ)>P&?$nQT$oTrLJ1FSZ%2s zHo|M9j#jHwX+n8rB^Ca%3F=YdcTM;Er@NM^VarNglht%`h?Wdp;9G8*~)Syg})*}h%VN9q6{sL4E{3LM1NU9g<`+VuV`0H&@9ie z3FwveuUR$^79$!Y*DT$pF?CJ#`=?sK*{>Gs8a$hIEsi3ps3@Gg6BB^yYctb^lnqb) zghY6H?SV%zr-x3q)g@=lRT5hmb|N`)RF%gV&|Nc{W#L&A$8&J%3I)mv3x6%4jh;^) zpwvGq5xkV3-yhV_#r^~6=IsUDe~g;7h=rPFMd6+RPW^<|OH19xyuPWShQ>%!%nq^E z#DgrUgDkW4*Mr=~_MuWPa~qxI#*rSwD2Rl^!K#>Y{!SiHg4bfV?14nw* z(~gP~{J*S^x{cCq&LM0^XRVcWY7-H8b&cD|lenUkJbvS}x)93Uq@x|H-tbz03a=9D=z4)8Pv znxessEm>Y;zYeM^Qw2a<=Pe*9Em2QHpfMxE9$sv&v7Ctxu@VD0JM_}64S{8C;ax&& zq&iq1kNWmBs{k?-*hhPyDi&I<$f%&OMMs5uW>hsdH3h@5B@K$;IK_>*B2*o#D>lQ; z4NHSfC1zbPR9hF@^WGG!QD=7BfOwFoIjxCQsR*{ZE%b~pvM$pcGuF&lV+$%h&ng{f z+TwexQ>P69TUMt_znE*+%D3KCXGI^)pAszIr{CH-TjF9nz4oi>Hq+DNk%zJr2Lor2 z{ko^yk=yBQ2aCt*)AX|TjE@7OCbTFE^0c2}_wF^t!ESaSrc^OK5UcYvMViCaaa-7n zgfrTrk!gBOUshID=AQLK(V0!5hCtJ*Vl&xc+EIWj={%3oS8klXm~ff(o*-T2o64e- zyX>*&3VB37x6x0^GLH(zpQIk6yF5?ry{>*M2nm{zCcW)+)N2Of8&In$VbA?O6S4Ft z8`S>GA7wL|Xwr6z6IZtxiJBv4eXU%8oOYbrb=j4+Eb(w}UqN|dOs+z5dH&JLKvMSz zH+)T1q@f`YR$0`hS#y^3mif&}`i@ScoORJ+(tD#qoft0OB+C7l%-n<3>;Bi4{uplOeDQWOT6W>q8{jj5-FMpSvGg%)Mg ztd!?%avQ^DSH|tBQpeePd@K+O+c>mW#Ga9sl{p(RuBJV`7U9A2+9e9|45xXqBT!S+N7l+B#ZEHzq|3u|} z6@@=N8Ge5*=!I)SwXK6ra2p}gy}kRFr@(ET_m>_2E7yie_y5%KcWdYPW#f--qt7U3 z=eBo)ai{NewsVC2ROi2Qg{Gq04)2NUdy~$s z=b!5~PUAdMdT61- zcweSmqN3P7%_=u&)(Ml!`c%$irG9l2o1!oxA*rLeOjy}Kqp&PW@9}{ zRCiOUO~>l7IM9iY?pa=HCtg;MH9Ow3dhed!5whU0at|x1Q`A~inrrcDk=xU+Q#fn2 zGMANuS}e37Vav1El(y`-O^Bj$m(^$u)DMkdjsv#JU?;gMWSU=9b>;ZWb=>OY%@%{! za0RHZO!~WfjQ)vgZBYZurs!18(yPkX^pZN)E<0GmX64#tR;piLYNzB@N@}UXV#Zox zk2uz=HN@gqKXis&UtwdjmmcR!w@TAHCs`P(Vc{~lR%LAik~EMy$1N(AUtXmIYMHJp z27kb*mBTCc_GKwi#c8z6PNubrb#j?g?#eIs`}3^|LP5DLmc^+;sm9hwNX-k`)~2nq zCG1$a_r{R#H7NTSKmVMJ?Z+7_x%n5!4_ zIY?`3bnHWJzgkt&Q#F(YIwx245lgKKq61sEsglcu9a_rm+PBUU?O9g(6`)!cf%Xsnp)X+mE(Ksp0J+hJ-n4_@vdUzbGLDXveS~ z5U}HECniuVSmAWWGF=qUl)hV(b8cO9tK0Z|DqU64I*oaZ4H3Dy(PQ);7SAx!<7Ztw zz>aswrw;QNsa;eQ?pQ!nKDAnV_n8fHuN)gzxmSzg$r)@#-%XKvg=2MN zBovNCi%oB!G31TU^V(M_-Zfeqk+6L)qtDw|JdIE}WmTOD^BZ+;*`QxCyoW5(L>9GC zS>(-Fc!+oLs7&=E820ECZ>?Iv`s-vRnZvzS)L@#S8Z*OL-l8pcMm)xT@`p2)sPMes z;YX3)MTy-DeT$umacFE-6-(`gW$TP!%T-OJUq8Jrt@im7=w*I~rQ8sw_UA+;yVI7%cBmGkUdDJEfdaX8KCSiCOJ&vJ%x`j#vNd zrUd_@-qRU*M3j2B_;oVO8Riesv325oX%}BVaK2)`AOq$v=?E?Nqi_&d~ z^!C%(sD{k6v8t${ZpE!))M)S0=2$GEIM^e#2287ZX|qgLTqL(>8VW(ZXI0Tbp|&&@ zc2@_&wZSHf$C~iJ}EZ;q9CzIZEd}}N49pr$c%l-;j+{=tc@+T zY-XC=q0FYYuN||BiguP3r*>NSds*d4O3phZ zYqHnq*S77l26~OWHurGSU203VRqcw?U2=dTFlazKX{>AJ_{E*vp~&^=>g?jES0OFG zoIa2A_E^J|PRw-OXe!rl755bQj>k>7=vGdz<$C3)WWhb^IP4Ip4>rXzr~W^u{y(R_<3i;>r~d!kss9CQ{duU@_%+G2iagerAod3*x&V~t z6o7P1O76-~71_@5@>G$WneH`)E>!hD^#zWq9nG<8gz?u|xFg8I9R_7zAyp{~rLVtxUE*;A}4pZ#r+{Sk_)WtFv+ZN1JYO9HFt znb=*SzVxwbs!FIS_7YkU>x&#+!BiDkJ<*miukDK)ML%9aYbv*EV-`%-Tnh;`*ZX(P zjeo3SSF8)Hy0u@d#m{!+%&!>oabzQK8b`w34LioN!is XZn%2L%zypsQu?od6>U=w_3ys`?8tY# delta 4506 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 diff --git a/package.json b/package.json index c0cb90a..d959c3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ecobridge/eco-os", - "version": "0.3.8", + "version": "0.3.9", "private": true, "scripts": { "build": "[ -z \"$CI\" ] && npm version patch --no-git-tag-version || true && node -e \"const v=require('./package.json').version; require('fs').writeFileSync('ecoos_daemon/ts/version.ts', 'export const VERSION = \\\"'+v+'\\\";\\n');\" && pnpm run daemon:bundle && cp ecoos_daemon/bundle/eco-daemon isobuild/config/includes.chroot/opt/eco/bin/ && mkdir -p .nogit/iso && docker build --no-cache -t ecoos-builder -f isobuild/Dockerfile . && docker run --privileged --name ecoos-build ecoos-builder && docker cp ecoos-build:/output/ecoos.iso .nogit/iso/ecoos.iso && docker rm ecoos-build",