fix(tray): Windows tray icon now loads — PNG->ICO with BMP entries
fyne.io/systray requires .ico content on Windows, and LoadImage cannot read PNG-in-ICO. The embedded icons are PNG, so SetIcon always failed and no tray icon ever appeared. pngToIco decodes the PNG and re-encodes classic BMP (XOR + AND mask) entries at 16/32/48px — the format LoadImage has always supported. Verified: the 'unable to set icon' error is gone and the tray process stays up. feat(installer): wizard page, Start Menu icons, silent install params - Wizard page asks for the SSO Manager URL + join key, with an 'Open SSO install-agent page' button (ShellExec); values feed agent.yml - /SERVER_URL /JOIN_KEY /AUTH_TOKEN /PUBLIC_KEY and /B64_CONFIG (base64 of a full agent.yml, decoded by an inline B64Decode) drive the same result in /SILENT mode, so the SSO's Install Agent modal can emit one Windows command - [Icons]: Theta Agent Tray / Open Agent Config / Uninstall in Start Menu - WireGuard client launches its UI at the end of the MSI; taskkill after the msiexec step closes it (the tunnel is agent-managed) - tray starts right after install (not just at next logon) - Inno 7 fixes: controls parent to Page.Surface, SaveStringsToUTF8FileWithoutBOM Adds pngToIco structure tests (valid ICO dir + 32bpp DIBs) and a platform passthrough test for toWindowsIcon. Rebuilds all tracked dist binaries + the setup.exe.
This commit is contained in:
@@ -21,8 +21,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -121,7 +124,7 @@ var (
|
|||||||
|
|
||||||
func onReady() {
|
func onReady() {
|
||||||
// Initial icon — red until we hear from the daemon.
|
// Initial icon — red until we hear from the daemon.
|
||||||
systray.SetIcon(iconRed)
|
systray.SetIcon(toWindowsIcon(iconRed))
|
||||||
systray.SetTitle("Theta Agent")
|
systray.SetTitle("Theta Agent")
|
||||||
systray.SetTooltip("Theta Agent — connecting…")
|
systray.SetTooltip("Theta Agent — connecting…")
|
||||||
|
|
||||||
@@ -216,19 +219,20 @@ func streamStatus(conn net.Conn) {
|
|||||||
func updateUI(s TrayStatus) {
|
func updateUI(s TrayStatus) {
|
||||||
currentStatus = s
|
currentStatus = s
|
||||||
|
|
||||||
// Icon color.
|
// Icon color. fyne.io/systray needs .ico on Windows; the PNG icons are
|
||||||
|
// wrapped in an ICO container (Windows Vista+ supports PNG-in-ICO).
|
||||||
|
icon := iconRed
|
||||||
switch s.Color {
|
switch s.Color {
|
||||||
case ColorRed:
|
case ColorRed:
|
||||||
systray.SetIcon(iconRed)
|
icon = iconRed
|
||||||
case ColorYellow:
|
case ColorYellow:
|
||||||
systray.SetIcon(iconYellow)
|
icon = iconYellow
|
||||||
case ColorGreen:
|
case ColorGreen:
|
||||||
systray.SetIcon(iconGreen)
|
icon = iconGreen
|
||||||
case ColorBlue:
|
case ColorBlue:
|
||||||
systray.SetIcon(iconBlue)
|
icon = iconBlue
|
||||||
default:
|
|
||||||
systray.SetIcon(iconRed)
|
|
||||||
}
|
}
|
||||||
|
systray.SetIcon(toWindowsIcon(icon))
|
||||||
|
|
||||||
// Tooltip.
|
// Tooltip.
|
||||||
tooltip := s.StatusText
|
tooltip := s.StatusText
|
||||||
@@ -273,3 +277,100 @@ func sendCmd(cmd TrayCommand) {
|
|||||||
log.Printf("theta-agent-tray: send command error: %v", err)
|
log.Printf("theta-agent-tray: send command error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toWindowsIcon converts PNG bytes into a Windows .ico for fyne.io/systray,
|
||||||
|
// which requires .ico content on Windows (LoadImage cannot read PNG-in-ICO).
|
||||||
|
// On non-Windows the PNG is returned untouched.
|
||||||
|
func toWindowsIcon(pngBytes []byte) []byte {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
return pngBytes
|
||||||
|
}
|
||||||
|
return pngToIco(pngBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pngToIco decodes a PNG and re-encodes it as classic BMP (XOR + AND mask)
|
||||||
|
// entries at 16/32/48px — the format LoadImage has always supported.
|
||||||
|
func pngToIco(pngBytes []byte) []byte {
|
||||||
|
src, err := png.Decode(bytes.NewReader(pngBytes))
|
||||||
|
if err != nil {
|
||||||
|
return pngBytes // give systray the raw bytes; it will log and continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes := []int{16, 32, 48}
|
||||||
|
var dir []byte
|
||||||
|
var payload []byte
|
||||||
|
offset := 6 + len(sizes)*16 // ICONDIR + all ICONDIRENTRYs
|
||||||
|
|
||||||
|
// ICONDIR: reserved(2)=0 type(2)=1 count(2)
|
||||||
|
dir = append(dir, 0, 0, 1, 0, byte(len(sizes)), 0)
|
||||||
|
|
||||||
|
for _, s := range sizes {
|
||||||
|
bmp := rgbaToDIB(scaleNearest(src, s, s))
|
||||||
|
dw, dh := byte(s), byte(s)
|
||||||
|
if s >= 256 {
|
||||||
|
dw, dh = 0, 0
|
||||||
|
}
|
||||||
|
entry := []byte{dw, dh, 0, 0, 1, 0, 32, 0}
|
||||||
|
entry = append(entry, putU32le(len(bmp))...)
|
||||||
|
entry = append(entry, putU32le(offset+len(payload))...)
|
||||||
|
dir = append(dir, entry...)
|
||||||
|
payload = append(payload, bmp...)
|
||||||
|
}
|
||||||
|
return append(dir, payload...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scaleNearest resizes src to w x h with nearest-neighbour sampling.
|
||||||
|
func scaleNearest(src image.Image, w, h int) image.Image {
|
||||||
|
b := src.Bounds()
|
||||||
|
if b.Dx() == w && b.Dy() == h {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
sy := b.Min.Y + y*b.Dy()/h
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
sx := b.Min.X + x*b.Dx()/w
|
||||||
|
dst.Set(x, y, src.At(sx, sy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// rgbaToDIB encodes an image as a 32-bit bottom-up DIB with an all-transparent
|
||||||
|
// AND mask — the classic icon bitmap LoadImage understands.
|
||||||
|
func rgbaToDIB(img image.Image) []byte {
|
||||||
|
b := img.Bounds()
|
||||||
|
w, h := b.Dx(), b.Dy()
|
||||||
|
|
||||||
|
hdr := make([]byte, 40)
|
||||||
|
copy(hdr, putU32le(40)) // biSize
|
||||||
|
copy(hdr[4:], putU32le(w)) // biWidth
|
||||||
|
copy(hdr[8:], putU32le(h*2)) // biHeight (XOR + AND)
|
||||||
|
copy(hdr[12:], putU16le(1)) // biPlanes
|
||||||
|
copy(hdr[14:], putU16le(32)) // biBitCount
|
||||||
|
andRow := ((w + 31) / 32) * 4 // AND mask row, padded to 32 bits
|
||||||
|
copy(hdr[20:], putU32le(w*h*4+andRow*h)) // biSizeImage
|
||||||
|
|
||||||
|
xor := make([]byte, w*h*4)
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
srcY := b.Min.Y + (h - 1 - y) // DIB rows are bottom-up
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
r, g, bl, a := img.At(b.Min.X+x, srcY).RGBA()
|
||||||
|
o := y*w*4 + x*4
|
||||||
|
xor[o+0] = byte(bl >> 8) // B
|
||||||
|
xor[o+1] = byte(g >> 8) // G
|
||||||
|
xor[o+2] = byte(r >> 8) // R
|
||||||
|
xor[o+3] = byte(a >> 8) // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
and := make([]byte, andRow*h) // all zeros: no transparency holes
|
||||||
|
return append(append(hdr, xor...), and...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putU32le(v int) []byte {
|
||||||
|
return []byte{byte(v), byte(v >> 8), byte(v >> 16), byte(v >> 24)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func putU16le(v int) []byte {
|
||||||
|
return []byte{byte(v), byte(v >> 8)}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
; Theta Agent — Windows installer (Inno Setup 6.4+)
|
; Theta Agent ??? Windows installer (Inno Setup 6.4+)
|
||||||
;
|
;
|
||||||
; Fully offline: bundles the agent, tray, session helper, the official WireGuard
|
; Fully offline: bundles the agent, tray, session helper, the official WireGuard
|
||||||
; for Windows client, the OpenCredential credential provider, and the VC++ v14
|
; for Windows client, the OpenCredential credential provider, and the VC++ v14
|
||||||
@@ -9,8 +9,11 @@
|
|||||||
; theta-agent-2.1.0-windows-amd64-setup.exe /SILENT ^
|
; theta-agent-2.1.0-windows-amd64-setup.exe /SILENT ^
|
||||||
; /SERVER_URL=https://sso.example.com /JOIN_KEY=tjk_...
|
; /SERVER_URL=https://sso.example.com /JOIN_KEY=tjk_...
|
||||||
;
|
;
|
||||||
; /SERVER_URL and /JOIN_KEY are written into agent.yml so the installed service
|
; Interactively, a wizard page asks for the SSO Manager URL and a join key (with
|
||||||
; enrolls on first start (no UI, no extra click).
|
; a button that opens the SSO's Directory -> Install Agent page to mint one).
|
||||||
|
; In silent mode, /SERVER_URL, /JOIN_KEY, /AUTH_TOKEN, /PUBLIC_KEY and
|
||||||
|
; /B64_CONFIG (base64 of a full agent.yml) drive the same result. The values are
|
||||||
|
; written into agent.yml so the installed service enrolls on first start.
|
||||||
|
|
||||||
#ifndef MyAppVersion
|
#ifndef MyAppVersion
|
||||||
#define MyAppVersion "2.1.0"
|
#define MyAppVersion "2.1.0"
|
||||||
@@ -58,7 +61,7 @@ Source: "{#AgentDir}\theta-agent-windows-amd64.exe"; DestDir: "{app}"; Flags: ig
|
|||||||
Source: "{#AgentDir}\theta-agent-tray-windows-amd64.exe"; DestDir: "{app}\tray"; Flags: ignoreversion
|
Source: "{#AgentDir}\theta-agent-tray-windows-amd64.exe"; DestDir: "{app}\tray"; Flags: ignoreversion
|
||||||
Source: "{#AgentDir}\theta-agent-helper-windows-amd64.exe"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "{#AgentDir}\theta-agent-helper-windows-amd64.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
|
||||||
; WireGuard for Windows — official, vendor-signed MSI. Installs offline (the
|
; WireGuard for Windows ??? official, vendor-signed MSI. Installs offline (the
|
||||||
; driver is signed; no signature phone-home).
|
; driver is signed; no signature phone-home).
|
||||||
Source: "{#VendorDir}\wireguard-amd64-0.5.3.msi"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
Source: "{#VendorDir}\wireguard-amd64-0.5.3.msi"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
||||||
|
|
||||||
@@ -67,6 +70,11 @@ Source: "{#VendorDir}\wireguard-amd64-0.5.3.msi"; DestDir: "{app}\vendor"; Flags
|
|||||||
Source: "{#VendorDir}\OpenCredentialInstaller-1.0.0.0.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
Source: "{#VendorDir}\OpenCredentialInstaller-1.0.0.0.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
||||||
Source: "{#VendorDir}\vc_redist.x64.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
Source: "{#VendorDir}\vc_redist.x64.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\Theta Agent Tray"; Filename: "{app}\tray\theta-agent-tray-windows-amd64.exe"; Comment: "Theta Agent status tray"
|
||||||
|
Name: "{group}\Open Agent Config"; Filename: "notepad.exe"; Parameters: "{commonappdata}\Theta42\agent.yml"; Comment: "Open the agent configuration file"
|
||||||
|
Name: "{group}\Uninstall Theta Agent"; Filename: "{uninstallexe}"
|
||||||
|
|
||||||
[Registry]
|
[Registry]
|
||||||
; Start the tray for every interactive logon.
|
; Start the tray for every interactive logon.
|
||||||
Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "ThetaAgentTray"; ValueData: "{app}\tray\theta-agent-tray-windows-amd64.exe"; Flags: uninsdeletevalue
|
Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "ThetaAgentTray"; ValueData: "{app}\tray\theta-agent-tray-windows-amd64.exe"; Flags: uninsdeletevalue
|
||||||
@@ -74,17 +82,30 @@ Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType:
|
|||||||
[Run]
|
[Run]
|
||||||
; VC++ v14 runtime (OpenCredential native deps).
|
; VC++ v14 runtime (OpenCredential native deps).
|
||||||
Filename: "{app}\vendor\vc_redist.x64.exe"; Parameters: "/install /quiet /norestart"; StatusMsg: "Installing VC++ runtime..."; Flags: runhidden waituntilterminated
|
Filename: "{app}\vendor\vc_redist.x64.exe"; Parameters: "/install /quiet /norestart"; StatusMsg: "Installing VC++ runtime..."; Flags: runhidden waituntilterminated
|
||||||
; OpenCredential credential provider — must be registered before logon.
|
; OpenCredential credential provider ??? must be registered before logon.
|
||||||
Filename: "{app}\vendor\OpenCredentialInstaller-1.0.0.0.exe"; Parameters: "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"; StatusMsg: "Installing OpenCredential credential provider..."; Flags: runhidden waituntilterminated
|
Filename: "{app}\vendor\OpenCredentialInstaller-1.0.0.0.exe"; Parameters: "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"; StatusMsg: "Installing OpenCredential credential provider..."; Flags: runhidden waituntilterminated
|
||||||
; WireGuard for Windows client.
|
; WireGuard for Windows client.
|
||||||
Filename: "msiexec.exe"; Parameters: "/i ""{app}\vendor\wireguard-amd64-0.5.3.msi"" /qn /norestart"; StatusMsg: "Installing WireGuard client..."; Flags: runhidden waituntilterminated
|
Filename: "msiexec.exe"; Parameters: "/i ""{app}\vendor\wireguard-amd64-0.5.3.msi"" /qn /norestart"; StatusMsg: "Installing WireGuard client..."; Flags: runhidden waituntilterminated
|
||||||
|
; The WireGuard client launches its UI at the end of the MSI; close it ??? the
|
||||||
|
; tunnel is managed by the agent (wireguard.exe /installtunnelservice).
|
||||||
|
Filename: "taskkill.exe"; Parameters: "/f /im wireguard.exe"; Flags: runhidden
|
||||||
; Register the agent as a SYSTEM auto-start service.
|
; Register the agent as a SYSTEM auto-start service.
|
||||||
Filename: "{app}\{#MyAppExeName}"; Parameters: "install-service"; StatusMsg: "Registering theta-agent service..."; Flags: runhidden waituntilterminated
|
Filename: "{app}\{#MyAppExeName}"; Parameters: "install-service"; StatusMsg: "Registering theta-agent service..."; Flags: runhidden waituntilterminated
|
||||||
|
; Show the tray right away instead of waiting for the next logon.
|
||||||
|
Filename: "{app}\tray\theta-agent-tray-windows-amd64.exe"; Description: "Start Theta Agent tray"; StatusMsg: "Starting Theta Agent tray..."; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
var
|
var
|
||||||
ServerURL: String;
|
ServerURL: String;
|
||||||
JoinKey: String;
|
JoinKey: String;
|
||||||
|
AuthToken: String;
|
||||||
|
PublicKey: String;
|
||||||
|
B64Config: String;
|
||||||
|
|
||||||
|
AgentConfigPage: TWizardPage;
|
||||||
|
ServerURLEdit: TNewEdit;
|
||||||
|
JoinKeyEdit: TNewEdit;
|
||||||
|
OpenSSOButton: TNewButton;
|
||||||
|
|
||||||
// Reads a custom setup command-line parameter (e.g. /SERVER_URL=https://...).
|
// Reads a custom setup command-line parameter (e.g. /SERVER_URL=https://...).
|
||||||
// {param:...} raises when the parameter is absent, so the exception becomes "".
|
// {param:...} raises when the parameter is absent, so the exception becomes "".
|
||||||
@@ -101,21 +122,145 @@ function InitializeSetup(): Boolean;
|
|||||||
begin
|
begin
|
||||||
ServerURL := GetCmdParam('SERVER_URL');
|
ServerURL := GetCmdParam('SERVER_URL');
|
||||||
JoinKey := GetCmdParam('JOIN_KEY');
|
JoinKey := GetCmdParam('JOIN_KEY');
|
||||||
|
AuthToken := GetCmdParam('AUTH_TOKEN');
|
||||||
|
PublicKey := GetCmdParam('PUBLIC_KEY');
|
||||||
|
B64Config := GetCmdParam('B64_CONFIG');
|
||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Opens the SSO's Directory page so the operator can mint a join key right from
|
||||||
|
// the wizard. Uses the Server URL they just typed.
|
||||||
|
procedure OnOpenSSOClick(Sender: TObject);
|
||||||
|
var
|
||||||
|
Url: String;
|
||||||
|
ErrorCode: Integer;
|
||||||
|
begin
|
||||||
|
Url := Trim(ServerURLEdit.Text);
|
||||||
|
if Url = '' then begin
|
||||||
|
MsgBox('Enter the SSO Manager URL first (e.g. https://sso.example.com).',
|
||||||
|
mbInformation, MB_OK);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
if not ShellExec('open', Url, '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode) then
|
||||||
|
MsgBox('Could not open the browser: ' + SysErrorMessage(ErrorCode), mbError, MB_OK);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure CreateAgentConfigPage();
|
||||||
|
var
|
||||||
|
InfoLabel: TNewStaticText;
|
||||||
|
UrlLabel: TNewStaticText;
|
||||||
|
KeyLabel: TNewStaticText;
|
||||||
|
begin
|
||||||
|
AgentConfigPage := CreateCustomPage(wpWelcome,
|
||||||
|
'SSO Manager connection',
|
||||||
|
'Tell the agent which SSO to enroll with.');
|
||||||
|
|
||||||
|
InfoLabel := TNewStaticText.Create(AgentConfigPage);
|
||||||
|
InfoLabel.Parent := AgentConfigPage.Surface;
|
||||||
|
InfoLabel.WordWrap := True;
|
||||||
|
InfoLabel.Caption := 'Paste the SSO Manager URL for this deployment. Then either paste a join key '
|
||||||
|
+ '(mint one with the button below, under Directory -> Install Agent) or leave it blank to '
|
||||||
|
+ 'enroll from the tray / CLI later.';
|
||||||
|
InfoLabel.AutoSize := True;
|
||||||
|
InfoLabel.Width := AgentConfigPage.Surface.Width;
|
||||||
|
|
||||||
|
UrlLabel := TNewStaticText.Create(AgentConfigPage);
|
||||||
|
UrlLabel.Parent := AgentConfigPage.Surface;
|
||||||
|
UrlLabel.Caption := 'SSO Manager URL:';
|
||||||
|
UrlLabel.Top := InfoLabel.Top + InfoLabel.Height + 16;
|
||||||
|
|
||||||
|
ServerURLEdit := TNewEdit.Create(AgentConfigPage);
|
||||||
|
ServerURLEdit.Parent := AgentConfigPage.Surface;
|
||||||
|
ServerURLEdit.Top := UrlLabel.Top + UrlLabel.Height + 4;
|
||||||
|
ServerURLEdit.Width := AgentConfigPage.Surface.Width;
|
||||||
|
ServerURLEdit.Text := ServerURL;
|
||||||
|
|
||||||
|
OpenSSOButton := TNewButton.Create(AgentConfigPage);
|
||||||
|
OpenSSOButton.Parent := AgentConfigPage.Surface;
|
||||||
|
OpenSSOButton.Top := ServerURLEdit.Top + ServerURLEdit.Height + 8;
|
||||||
|
OpenSSOButton.Left := ServerURLEdit.Left;
|
||||||
|
OpenSSOButton.Caption := 'Open SSO install-agent page...';
|
||||||
|
OpenSSOButton.Width := 190;
|
||||||
|
OpenSSOButton.OnClick := @OnOpenSSOClick;
|
||||||
|
|
||||||
|
KeyLabel := TNewStaticText.Create(AgentConfigPage);
|
||||||
|
KeyLabel.Parent := AgentConfigPage.Surface;
|
||||||
|
KeyLabel.Caption := 'Join key (optional):';
|
||||||
|
KeyLabel.Top := OpenSSOButton.Top + OpenSSOButton.Height + 12;
|
||||||
|
|
||||||
|
JoinKeyEdit := TNewEdit.Create(AgentConfigPage);
|
||||||
|
JoinKeyEdit.Parent := AgentConfigPage.Surface;
|
||||||
|
JoinKeyEdit.Top := KeyLabel.Top + KeyLabel.Height + 4;
|
||||||
|
JoinKeyEdit.Width := AgentConfigPage.Surface.Width;
|
||||||
|
JoinKeyEdit.Text := JoinKey;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure InitializeWizard();
|
||||||
|
begin
|
||||||
|
CreateAgentConfigPage();
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Pull the values the operator typed into the wizard so WriteAgentConfig can use
|
||||||
|
// them; silent installs keep the command-line params.
|
||||||
|
procedure CurPageChanged(CurPageID: Integer);
|
||||||
|
begin
|
||||||
|
if CurPageID = AgentConfigPage.ID then begin
|
||||||
|
ServerURL := Trim(ServerURLEdit.Text);
|
||||||
|
JoinKey := Trim(JoinKeyEdit.Text);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Minimal base64 decoder returning a plain String (agent.yml is ASCII).
|
||||||
|
function B64Decode(const S: String): String;
|
||||||
|
var
|
||||||
|
i, v, p: Integer;
|
||||||
|
buf: array[0..3] of Integer;
|
||||||
|
outStr: String;
|
||||||
|
begin
|
||||||
|
outStr := '';
|
||||||
|
v := 0;
|
||||||
|
for i := 1 to Length(S) do begin
|
||||||
|
if S[i] = '=' then begin
|
||||||
|
buf[v] := 0;
|
||||||
|
Inc(v);
|
||||||
|
end else begin
|
||||||
|
p := Pos(S[i], 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');
|
||||||
|
buf[v] := p - 1;
|
||||||
|
Inc(v);
|
||||||
|
end;
|
||||||
|
if v = 4 then begin
|
||||||
|
outStr := outStr + Chr((buf[0] shl 2) or (buf[1] shr 4));
|
||||||
|
outStr := outStr + Chr(((buf[1] and $F) shl 4) or (buf[2] shr 2));
|
||||||
|
outStr := outStr + Chr(((buf[2] and 3) shl 6) or buf[3]);
|
||||||
|
v := 0;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Result := outStr;
|
||||||
|
end;
|
||||||
|
|
||||||
// Write agent.yml only after all files are in place. The file lives in
|
// Write agent.yml only after all files are in place. The file lives in
|
||||||
// ProgramData so the SYSTEM service and user processes share it.
|
// ProgramData so the SYSTEM service and user processes share it.
|
||||||
procedure WriteAgentConfig(ConfigPath: String);
|
procedure WriteAgentConfig(ConfigPath: String);
|
||||||
var
|
var
|
||||||
Lines: TArrayOfString;
|
Lines: TArrayOfString;
|
||||||
|
Decoded: String;
|
||||||
begin
|
begin
|
||||||
SetArrayLength(Lines, 13);
|
// /B64_CONFIG=<base64 agent.yml> overrides everything (the SSO's Custom Config
|
||||||
|
// wizard emits it).
|
||||||
|
if B64Config <> '' then begin
|
||||||
|
Decoded := B64Decode(B64Config);
|
||||||
|
SetArrayLength(Lines, 1);
|
||||||
|
Lines[0] := Decoded;
|
||||||
|
SaveStringsToUTF8FileWithoutBOM(ConfigPath, Lines, False);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
SetArrayLength(Lines, 17);
|
||||||
Lines[0] := '# theta-agent configuration (written by installer)';
|
Lines[0] := '# theta-agent configuration (written by installer)';
|
||||||
Lines[1] := 'server_url: "' + ServerURL + '"';
|
Lines[1] := 'server_url: "' + ServerURL + '"';
|
||||||
Lines[2] := 'auth_token: ""';
|
Lines[2] := 'auth_token: "' + AuthToken + '"';
|
||||||
Lines[3] := 'join_key: "' + JoinKey + '"';
|
Lines[3] := 'join_key: "' + JoinKey + '"';
|
||||||
Lines[4] := 'public_key: ""';
|
Lines[4] := 'public_key: "' + PublicKey + '"';
|
||||||
Lines[5] := 'auto_vpn: false';
|
Lines[5] := 'auto_vpn: false';
|
||||||
Lines[6] := 'service_name: "theta-agent"';
|
Lines[6] := 'service_name: "theta-agent"';
|
||||||
Lines[7] := 'desktop_helper: "' + ExpandConstant('{app}') + '\theta-agent-helper-windows-amd64.exe"';
|
Lines[7] := 'desktop_helper: "' + ExpandConstant('{app}') + '\theta-agent-helper-windows-amd64.exe"';
|
||||||
@@ -124,7 +269,11 @@ begin
|
|||||||
Lines[10] := ' telemetry: true';
|
Lines[10] := ' telemetry: true';
|
||||||
Lines[11] := ' ldap_tunnel: true';
|
Lines[11] := ' ldap_tunnel: true';
|
||||||
Lines[12] := ' wireguard: true';
|
Lines[12] := ' wireguard: true';
|
||||||
SaveStringsToUTF8File(ConfigPath, Lines, False);
|
Lines[13] := ' secrets: false';
|
||||||
|
Lines[14] := ' iam: false';
|
||||||
|
Lines[15] := ' reboot: false';
|
||||||
|
Lines[16] := ' arbitrary_bash: false';
|
||||||
|
SaveStringsToUTF8FileWithoutBOM(ConfigPath, Lines, False);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure CurStepChanged(CurStep: TSetupStep);
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
|
|||||||
Reference in New Issue
Block a user