-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(windows-agent): Allow users to run it via CLI (#941)
We modify the MSIX packaging to create an [app execution alias](https://learn.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap3-appexecutionalias), which causes a dummy binary to be created in a well-known location (by default `%LOCALAPPDATA%\Microsoft\WindowsApps`) which is included in user's `%PATH%` such that it becomes possible for the user (or scripts running in user's behalf) to run the agent via CLI. That's preferrable over declaring the agent as an [Application](https://learn.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-application) because it won't be "promoted" in the start menu as an application would. Users can still find the CLI executable by searching for its exact name ( `ubuntu-pro-agent` in this case - the `.exe` is optional in the search but required in the appx manifest declaration). Since we didn't remove the startup task declaration, starting the agent via the CLI app execution alias is also considered by the OS as a user interaction with the app, thus the startup task is enabled for that user such that the subsequent logons will have the agent starting automatically. The agent is made into a single-instance application so we prevent a second instance to overwrite logs, replace the gRPC service bindings and corrupt the state of an already running process. We achieve that with a simple mechanism of writing a lock file in the agent's private directory, opened with exclusive access, so a process attempting to open it a second time won't succeed. Since the already-running instance could be child of the ubuntu-pro-agent-launcher.exe (which is an invisible GUI) it's better to not implement the common pattern of GUI apps of activating the already running process window when a instance detects itself as the second one. The end result looks like the following: ![Screenshot 2024-10-14 151735](https://github.com/user-attachments/assets/ca017a9f-f40a-46a4-b013-d4155b6d3667) ![image](https://github.com/user-attachments/assets/3d98fcd9-c71d-4ac8-b8b2-07209c2fb076) - `ubuntu-pro-agent.exe` in PATH - `ubuntu-pro-agent` can be found in the start menu - A terminal window running it is and remains visible - clean, completion, help and version commands still work on a second instance - the default command does not work and exits early enough before touching logs, disk or network resources. --- UDENG-4623 P.S.: I sneaked one minor unrelated fix in 6c1f70e because I was passing nearby. Also 114d656 is temporary and should be fixed after I workout the documentation PR (coming next) related to this one.
- Loading branch information
Showing
6 changed files
with
185 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
windows-agent/cmd/ubuntu-pro-agent/agent/lockfile_linux.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package agent | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"syscall" | ||
|
||
"github.com/ubuntu/decorate" | ||
) | ||
|
||
// createLockFile tries to create or open an empty file with given name with exclusive access. | ||
// If the file already exists AND is still locked, it will fail. | ||
func createLockFile(path string) (f *os.File, err error) { | ||
decorate.OnError(&err, "could not create lock file %s: %v", path, err) | ||
|
||
f, err = os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// This would only fail if the file is locked by another process. | ||
err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not lock file: %v", errors.Join(err, f.Close())) | ||
} | ||
|
||
return f, nil | ||
} |
19 changes: 19 additions & 0 deletions
19
windows-agent/cmd/ubuntu-pro-agent/agent/lockfile_windows.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package agent | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/ubuntu/decorate" | ||
) | ||
|
||
// createLockFile tries to create or open an empty file with given name with exclusive access. | ||
func createLockFile(path string) (f *os.File, err error) { | ||
decorate.OnError(&err, "could not create lock file %s: %v", path, err) | ||
|
||
// On Windows removing fails if the file is opened by another process with ERROR_SHARING_VIOLATION. | ||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) { | ||
return nil, err | ||
} | ||
// If this process is the only instance of this program, then the file won't exist. | ||
return os.OpenFile(path, os.O_CREATE|os.O_EXCL, 0600) | ||
} |