fix: MacOS MDM based Deployment (#277)

* fix: MacOS MDM deployment script

* fix: Handle shell alias for bash on macos

* fix: Code review fixes

* fix: Code review fixes

* feat: Add support for global config file

* feat: Add support for global config file

* fix: Code review fixes

* fix: Avoid blocking CLI for analytics flush
This commit is contained in:
Abhisek Datta
2026-05-21 14:32:30 +05:30
committed by GitHub
parent 875cda2e43
commit b15ce33fe4
24 changed files with 1396 additions and 374 deletions
+105 -2
View File
@@ -1,9 +1,20 @@
package alias
import (
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
)
type bashShell struct{}
var _ Shell = &bashShell{}
// bashLoginFiles are the login startup files in the order bash reads them.
var bashLoginFiles = []string{".bash_profile", ".bash_login", ".profile"}
func NewBashShell() (*bashShell, error) {
return &bashShell{}, nil
}
@@ -20,6 +31,98 @@ func (b bashShell) Name() string {
return "bash"
}
func (b bashShell) Path() string {
return ".bashrc"
func (b bashShell) CandidateRcFiles(homeDir string) []string {
files := []string{filepath.Join(homeDir, ".bashrc")}
for _, name := range bashLoginFiles {
files = append(files, filepath.Join(homeDir, name))
}
return files
}
func (b bashShell) InstallRcFiles(homeDir string, create bool) ([]string, error) {
return bashInstallRcFiles(homeDir, create, runtime.GOOS)
}
// bashInstallRcFiles resolves where bash should load PMG. bash reads .bashrc for
// interactive non-login shells and a login file (.bash_profile, .bash_login, or
// .profile) for login shells such as macOS Terminal, so the lines may need to
// land in both. goos is a parameter for testability.
func bashInstallRcFiles(homeDir string, create bool, goos string) ([]string, error) {
bashrc := filepath.Join(homeDir, ".bashrc")
bashrcExists := fileExists(bashrc)
login := firstExistingFile(homeDir, bashLoginFiles)
var files []string
if bashrcExists {
files = append(files, bashrc)
}
// macOS Terminal starts bash as a login shell, which does not read .bashrc.
// Create .bash_profile so the lines reach login shells too.
if login == "" && create && goos == "darwin" {
login = filepath.Join(homeDir, ".bash_profile")
if err := ensureFile(login); err != nil {
return nil, err
}
}
// Skip the login file when it already sources .bashrc, to avoid loading the
// lines twice.
if login != "" && (!bashrcExists || !referencesBashrc(login)) {
files = append(files, login)
}
if len(files) > 0 {
return files, nil
}
if !create {
return nil, nil
}
// Nothing exists yet: create the canonical file for this OS.
target := bashrc
if goos == "darwin" {
target = filepath.Join(homeDir, ".bash_profile")
}
if err := ensureFile(target); err != nil {
return nil, err
}
return []string{target}, nil
}
// bashrcSourceRe matches a real sourcing of a bashrc file: a `source` or `.`
// command whose argument ends in `.bashrc` (for example `source ~/.bashrc` or
// `. "$HOME/.bashrc"`). Requiring the command keyword avoids matching mere
// mentions in comments or unrelated commands.
var bashrcSourceRe = regexp.MustCompile(`(^|[\s;&|()])(source|\.)\s+\S*\.bashrc($|[\s;'"&|)])`)
// referencesBashrc reports whether the file at path actually sources a bashrc
// file. It skips comment lines and inline comments so a commented mention does
// not wrongly suppress wiring the login file.
func referencesBashrc(path string) bool {
data, err := os.ReadFile(path)
if err != nil {
return false
}
for _, raw := range strings.Split(string(data), "\n") {
line := strings.TrimSpace(raw)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if i := strings.Index(line, "#"); i >= 0 {
line = line[:i]
}
if bashrcSourceRe.MatchString(line) {
return true
}
}
return false
}