@@ -184,16 +184,36 @@ type PluginConfig struct {
184184
185185// AnsiblePluginConfig holds configuration for the ansible plugin.
186186type AnsiblePluginConfig struct {
187- InventoryFormat string `yaml:"inventory_format,omitempty"`
188- InventoryStyle string `yaml:"inventory_style,omitempty"`
189- InventoryVars map [string ]string `yaml:"inventory_vars,omitempty"`
190- DefaultUser string `yaml:"default_user,omitempty"`
191- DefaultPassword string `yaml:"default_password,omitempty"`
192- SSHPrivateKeyFile string `yaml:"ssh_private_key_file,omitempty"`
193- DefaultLimitMode string `yaml:"default_limit_mode,omitempty"`
194- AskPass bool `yaml:"ask_pass,omitempty"`
195- AskBecomePass bool `yaml:"ask_become_pass,omitempty"`
196- ExtraArgs []string `yaml:"extra_args,omitempty"`
187+ InventoryFormat string `yaml:"inventory_format,omitempty"`
188+ InventoryStyle string `yaml:"inventory_style,omitempty"`
189+ InventoryVars map [string ]string `yaml:"inventory_vars,omitempty"`
190+ DefaultUser string `yaml:"default_user,omitempty"`
191+ DefaultPassword string `yaml:"default_password,omitempty"`
192+ SSHPrivateKeyFile string `yaml:"ssh_private_key_file,omitempty"`
193+ DefaultLimitMode string `yaml:"default_limit_mode,omitempty"`
194+ AskPass bool `yaml:"ask_pass,omitempty"`
195+ AskBecomePass bool `yaml:"ask_become_pass,omitempty"`
196+ ExtraArgs []string `yaml:"extra_args,omitempty"`
197+ Bootstrap AnsibleBootstrapConfig `yaml:"bootstrap,omitempty"`
198+ }
199+
200+ // AnsibleBootstrapConfig holds bootstrap access workflow settings for ansible.
201+ type AnsibleBootstrapConfig struct {
202+ Enabled bool `yaml:"enabled,omitempty"`
203+ Username string `yaml:"username,omitempty"`
204+ Shell string `yaml:"shell,omitempty"`
205+ CreateHome bool `yaml:"create_home,omitempty"`
206+ ExcludeWindowsGuests bool `yaml:"exclude_windows_guests,omitempty"`
207+ SSHPublicKeyFile string `yaml:"ssh_public_key_file,omitempty"`
208+ InstallAuthorizedKey bool `yaml:"install_authorized_key,omitempty"`
209+ SetPassword bool `yaml:"set_password,omitempty"`
210+ Password string `yaml:"password,omitempty"`
211+ GrantSudoNOPASSWD bool `yaml:"grant_sudo_nopasswd,omitempty"`
212+ SudoersFileMode string `yaml:"sudoers_file_mode,omitempty"`
213+ DryRunDefault bool `yaml:"dry_run_default,omitempty"`
214+ Parallelism int `yaml:"parallelism,omitempty"`
215+ Timeout string `yaml:"timeout,omitempty"`
216+ FailFast bool `yaml:"fail_fast,omitempty"`
197217}
198218
199219// DefaultKeyBindings returns a KeyBindings struct with the default key mappings.
@@ -332,7 +352,17 @@ func NewConfig() *Config {
332352 Debug : strings .ToLower (os .Getenv ("PVETUI_DEBUG" )) == trueString ,
333353 CacheDir : ExpandHomePath (os .Getenv ("PVETUI_CACHE_DIR" )),
334354 KeyBindings : DefaultKeyBindings (),
335- ShowIcons : strings .ToLower (os .Getenv ("PVETUI_SHOW_ICONS" )) != "false" ,
355+ Plugins : PluginConfig {
356+ Ansible : AnsiblePluginConfig {
357+ Bootstrap : AnsibleBootstrapConfig {
358+ CreateHome : true ,
359+ ExcludeWindowsGuests : true ,
360+ InstallAuthorizedKey : true ,
361+ DryRunDefault : true ,
362+ },
363+ },
364+ },
365+ ShowIcons : strings .ToLower (os .Getenv ("PVETUI_SHOW_ICONS" )) != "false" ,
336366 }
337367 // Set default values for Realm and ApiPath only
338368 if config .Realm == "" {
@@ -430,6 +460,23 @@ func (c *Config) MergeWithFile(path string) error {
430460 AskPass * bool `yaml:"ask_pass"`
431461 AskBecomePass * bool `yaml:"ask_become_pass"`
432462 ExtraArgs []string `yaml:"extra_args"`
463+ Bootstrap struct {
464+ Enabled * bool `yaml:"enabled"`
465+ Username string `yaml:"username"`
466+ Shell string `yaml:"shell"`
467+ CreateHome * bool `yaml:"create_home"`
468+ ExcludeWindowsGuests * bool `yaml:"exclude_windows_guests"`
469+ SSHPublicKeyFile string `yaml:"ssh_public_key_file"`
470+ InstallAuthorizedKey * bool `yaml:"install_authorized_key"`
471+ SetPassword * bool `yaml:"set_password"`
472+ Password string `yaml:"password"`
473+ GrantSudoNOPASSWD * bool `yaml:"grant_sudo_nopasswd"`
474+ SudoersFileMode string `yaml:"sudoers_file_mode"`
475+ DryRunDefault * bool `yaml:"dry_run_default"`
476+ Parallelism * int `yaml:"parallelism"`
477+ Timeout string `yaml:"timeout"`
478+ FailFast * bool `yaml:"fail_fast"`
479+ } `yaml:"bootstrap"`
433480 } `yaml:"ansible"`
434481 } `yaml:"plugins"`
435482 ShowIcons * bool `yaml:"show_icons"`
@@ -463,7 +510,13 @@ func (c *Config) MergeWithFile(path string) error {
463510 _ , hasGlobalMenuKey = fileConfigRaw .KeyBindings ["global_menu" ]
464511 }
465512
466- if ! isSOPSEncrypted && detectCleartextSensitive (fileConfig .Profiles , fileConfig .Password , fileConfig .TokenSecret , fileConfig .Plugins .Ansible .DefaultPassword ) {
513+ if ! isSOPSEncrypted && detectCleartextSensitive (
514+ fileConfig .Profiles ,
515+ fileConfig .Password ,
516+ fileConfig .TokenSecret ,
517+ fileConfig .Plugins .Ansible .DefaultPassword ,
518+ fileConfig .Plugins .Ansible .Bootstrap .Password ,
519+ ) {
467520 c .hasCleartextSensitive = true
468521 }
469522
@@ -715,6 +768,51 @@ func (c *Config) MergeWithFile(path string) error {
715768 if fileConfig .Plugins .Ansible .ExtraArgs != nil {
716769 c .Plugins .Ansible .ExtraArgs = append ([]string {}, fileConfig .Plugins .Ansible .ExtraArgs ... )
717770 }
771+ if fileConfig .Plugins .Ansible .Bootstrap .Enabled != nil {
772+ c .Plugins .Ansible .Bootstrap .Enabled = * fileConfig .Plugins .Ansible .Bootstrap .Enabled
773+ }
774+ if fileConfig .Plugins .Ansible .Bootstrap .Username != "" {
775+ c .Plugins .Ansible .Bootstrap .Username = fileConfig .Plugins .Ansible .Bootstrap .Username
776+ }
777+ if fileConfig .Plugins .Ansible .Bootstrap .Shell != "" {
778+ c .Plugins .Ansible .Bootstrap .Shell = fileConfig .Plugins .Ansible .Bootstrap .Shell
779+ }
780+ if fileConfig .Plugins .Ansible .Bootstrap .CreateHome != nil {
781+ c .Plugins .Ansible .Bootstrap .CreateHome = * fileConfig .Plugins .Ansible .Bootstrap .CreateHome
782+ }
783+ if fileConfig .Plugins .Ansible .Bootstrap .ExcludeWindowsGuests != nil {
784+ c .Plugins .Ansible .Bootstrap .ExcludeWindowsGuests = * fileConfig .Plugins .Ansible .Bootstrap .ExcludeWindowsGuests
785+ }
786+ if fileConfig .Plugins .Ansible .Bootstrap .SSHPublicKeyFile != "" {
787+ c .Plugins .Ansible .Bootstrap .SSHPublicKeyFile = ExpandHomePath (fileConfig .Plugins .Ansible .Bootstrap .SSHPublicKeyFile )
788+ }
789+ if fileConfig .Plugins .Ansible .Bootstrap .InstallAuthorizedKey != nil {
790+ c .Plugins .Ansible .Bootstrap .InstallAuthorizedKey = * fileConfig .Plugins .Ansible .Bootstrap .InstallAuthorizedKey
791+ }
792+ if fileConfig .Plugins .Ansible .Bootstrap .SetPassword != nil {
793+ c .Plugins .Ansible .Bootstrap .SetPassword = * fileConfig .Plugins .Ansible .Bootstrap .SetPassword
794+ }
795+ if fileConfig .Plugins .Ansible .Bootstrap .Password != "" {
796+ c .Plugins .Ansible .Bootstrap .Password = fileConfig .Plugins .Ansible .Bootstrap .Password
797+ }
798+ if fileConfig .Plugins .Ansible .Bootstrap .GrantSudoNOPASSWD != nil {
799+ c .Plugins .Ansible .Bootstrap .GrantSudoNOPASSWD = * fileConfig .Plugins .Ansible .Bootstrap .GrantSudoNOPASSWD
800+ }
801+ if fileConfig .Plugins .Ansible .Bootstrap .SudoersFileMode != "" {
802+ c .Plugins .Ansible .Bootstrap .SudoersFileMode = fileConfig .Plugins .Ansible .Bootstrap .SudoersFileMode
803+ }
804+ if fileConfig .Plugins .Ansible .Bootstrap .DryRunDefault != nil {
805+ c .Plugins .Ansible .Bootstrap .DryRunDefault = * fileConfig .Plugins .Ansible .Bootstrap .DryRunDefault
806+ }
807+ if fileConfig .Plugins .Ansible .Bootstrap .Parallelism != nil && * fileConfig .Plugins .Ansible .Bootstrap .Parallelism > 0 {
808+ c .Plugins .Ansible .Bootstrap .Parallelism = * fileConfig .Plugins .Ansible .Bootstrap .Parallelism
809+ }
810+ if fileConfig .Plugins .Ansible .Bootstrap .Timeout != "" {
811+ c .Plugins .Ansible .Bootstrap .Timeout = fileConfig .Plugins .Ansible .Bootstrap .Timeout
812+ }
813+ if fileConfig .Plugins .Ansible .Bootstrap .FailFast != nil {
814+ c .Plugins .Ansible .Bootstrap .FailFast = * fileConfig .Plugins .Ansible .Bootstrap .FailFast
815+ }
718816
719817 // Merge show_icons configuration if provided
720818 if fileConfig .ShowIcons != nil {
@@ -750,13 +848,20 @@ func (c *Config) MergeWithFile(path string) error {
750848 return nil
751849}
752850
753- func detectCleartextSensitive (profiles map [string ]ProfileConfig , legacyPassword , legacyTokenSecret , ansibleDefaultPassword string ) bool {
851+ func detectCleartextSensitive (
852+ profiles map [string ]ProfileConfig ,
853+ legacyPassword ,
854+ legacyTokenSecret ,
855+ ansibleDefaultPassword ,
856+ ansibleBootstrapPassword string ,
857+ ) bool {
754858 if hasCleartextSensitiveProfiles (profiles ) {
755859 return true
756860 }
757861 return hasCleartextSensitiveValue (legacyPassword ) ||
758862 hasCleartextSensitiveValue (legacyTokenSecret ) ||
759- hasCleartextSensitiveValue (ansibleDefaultPassword )
863+ hasCleartextSensitiveValue (ansibleDefaultPassword ) ||
864+ hasCleartextSensitiveValue (ansibleBootstrapPassword )
760865}
761866
762867func hasCleartextSensitiveProfiles (profiles map [string ]ProfileConfig ) bool {
@@ -1119,6 +1224,24 @@ func (c *Config) SetDefaults() {
11191224 if c .Plugins .Ansible .SSHPrivateKeyFile != "" {
11201225 c .Plugins .Ansible .SSHPrivateKeyFile = ExpandHomePath (c .Plugins .Ansible .SSHPrivateKeyFile )
11211226 }
1227+ if c .Plugins .Ansible .Bootstrap .Username == "" {
1228+ c .Plugins .Ansible .Bootstrap .Username = "ansible"
1229+ }
1230+ if c .Plugins .Ansible .Bootstrap .Shell == "" {
1231+ c .Plugins .Ansible .Bootstrap .Shell = "/bin/bash"
1232+ }
1233+ if c .Plugins .Ansible .Bootstrap .SudoersFileMode == "" {
1234+ c .Plugins .Ansible .Bootstrap .SudoersFileMode = "0440"
1235+ }
1236+ if c .Plugins .Ansible .Bootstrap .Timeout == "" {
1237+ c .Plugins .Ansible .Bootstrap .Timeout = "2m"
1238+ }
1239+ if c .Plugins .Ansible .Bootstrap .Parallelism <= 0 {
1240+ c .Plugins .Ansible .Bootstrap .Parallelism = 10
1241+ }
1242+ if c .Plugins .Ansible .Bootstrap .SSHPublicKeyFile != "" {
1243+ c .Plugins .Ansible .Bootstrap .SSHPublicKeyFile = ExpandHomePath (c .Plugins .Ansible .Bootstrap .SSHPublicKeyFile )
1244+ }
11221245
11231246 // ShowIcons defaults to true (icons enabled)
11241247 // No explicit default needed since it's already set in NewConfig()
0 commit comments