Skip to content

Commit

Permalink
Allow installing with no users (#574)
Browse files Browse the repository at this point in the history
  • Loading branch information
Itxaka authored Oct 10, 2024
1 parent e8bb8cf commit a3aadbb
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 38 deletions.
25 changes: 25 additions & 0 deletions internal/agent/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import (
"github.com/kairos-io/kairos-sdk/utils"
qr "github.com/mudler/go-nodepair/qrcode"
"github.com/mudler/go-pluggable"
yip "github.com/mudler/yip/pkg/schema"
"github.com/pterm/pterm"
"github.com/twpayne/go-vfs/v4"
)

func displayInfo(agentConfig *Config) {
Expand Down Expand Up @@ -215,6 +217,29 @@ func RunInstall(c *config.Config) error {
utils.SetEnv(c.Env)
utils.SetEnv(c.Install.Env)

// If nousers is enabled we do not check for the validity of the users and such
// At this point, the config should be fully parsed and the yip stages ready
if !c.Install.NoUsers {
found := false
cc, _ := c.Config.String()
yamlConfig, err := yip.Load(cc, vfs.OSFS, nil, nil)
if err != nil {
return err
}
for _, stage := range yamlConfig.Stages {
for _, x := range stage {
if len(x.Users) > 0 {
found = true
break
}
}

}
if !found {
return fmt.Errorf("No users found in any stage\nWe require at least one user or the install option 'install.nousers: true' to be set in the config in order to allow installing a system with no users.")
}
}

// UKI path. Check if we are on UKI AND if we are running off a cd, otherwise it makes no sense to run the install
// From the installed system
if internalutils.IsUkiWithFs(c.Fs) {
Expand Down
96 changes: 59 additions & 37 deletions internal/agent/interactive_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,31 +152,45 @@ func InteractiveInstall(debug, spawnShell bool, sourceImgURL string) error {
return err
}

userName, err := prompt("User to setup", "kairos", canBeEmpty, true, false)
createUser, err := prompt("Do you want to create any users? If not, system will not be accesible via terminal or ssh", "y", yesNo, true, false)
if err != nil {
return err
}

userPassword, err := prompt("Password", "", canBeEmpty, true, true)
if err != nil {
return err
}
var userName, userPassword, sshKeys, makeAdmin string

if userPassword == "" {
userPassword = "!"
}
if isYes(createUser) {
userName, err = prompt("User to setup", "kairos", canBeEmpty, true, false)
if err != nil {
return err
}

users, err := prompt("SSH access (rsakey, github/gitlab supported, comma-separated)", "github:someuser,github:someuser2", canBeEmpty, true, false)
if err != nil {
return err
}
userPassword, err = prompt("Password", "", canBeEmpty, true, true)
if err != nil {
return err
}

// Cleanup the users if we selected the default values as they are not valid users
if users == "github:someuser,github:someuser2" {
users = ""
}
if users != "" {
sshUsers = strings.Split(users, ",")
if userPassword == "" {
userPassword = "!"
}

sshKeys, err = prompt("SSH access (rsakey, github/gitlab supported, comma-separated)", "github:someuser,github:someuser2", canBeEmpty, true, false)
if err != nil {
return err
}

makeAdmin, err = prompt("Make the user an admin (with sudo permissions)?", "y", yesNo, true, false)
if err != nil {
return err
}

// Cleanup the users if we selected the default values as they are not valid users
if sshKeys == "github:someuser,github:someuser2" {
sshKeys = ""
}
if sshKeys != "" {
sshUsers = strings.Split(sshKeys, ",")
}
}

// Prompt the user by prompts defined by the provider
Expand Down Expand Up @@ -216,41 +230,49 @@ func InteractiveInstall(debug, spawnShell bool, sourceImgURL string) error {
return InteractiveInstall(debug, spawnShell, sourceImgURL)
}

usersToSet := map[string]schema.User{}
// This is temporal to generate a valid cc file, no need to properly initialize everything
cc := &config.Config{
Install: &config.Install{
Device: device,
},
}

stage := config.NetworkStage.String()
var cloudConfig schema.YipConfig

// Only add the user stage if we have any users
if userName != "" {
var isAdmin []string

if isYes(makeAdmin) {
isAdmin = append(isAdmin, "admin")
}

user := schema.User{
Name: userName,
PasswordHash: userPassword,
Groups: []string{"admin"},
Groups: isAdmin,
SSHAuthorizedKeys: sshUsers,
}

stage := config.NetworkStage.String()
// If we got no ssh keys, we don't need network, do the user as soon as possible
if len(sshUsers) == 0 {
stage = config.InitramfsStage.String()
}

usersToSet = map[string]schema.User{
userName: user,
}
cloudConfig = schema.YipConfig{Name: "Config generated by the installer",
Stages: map[string][]schema.Stage{stage: {
{
Users: map[string]schema.User{
userName: user,
},
},
}}}
} else {
// If no users, we need to set this option to skip the user validation and confirm that we want a system with no users.
cc.Install.NoUsers = true
}

cloudConfig := schema.YipConfig{Name: "Config generated by the installer",
Stages: map[string][]schema.Stage{stage: {
{
Users: usersToSet,
},
}}}

// This is temporal to generate a valid cc file, no need to properly initialize everything
cc := &config.Config{
Install: &config.Install{
Device: device,
},
}
// Merge all yamls into one
dat, err := config.MergeYAML(cloudConfig, cc, result)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Install struct {
ExtraPartitions sdkTypes.PartitionList `yaml:"extra-partitions,omitempty" mapstructure:"extra-partitions"`
ExtraDirsRootfs []string `yaml:"extra-dirs-rootfs,omitempty" mapstructure:"extra-dirs-rootfs"`
Force bool `yaml:"force,omitempty" mapstructure:"force"`
NoUsers bool `yaml:"nousers,omitempty" mapstructure:"nousers"`
}

func NewConfig(opts ...GenericOptions) *Config {
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func structFieldsContainedInOtherStruct(left, right interface{}) {
leftFieldName := leftTypes.Field(i).Name
if leftTypes.Field(i).IsExported() {
It(fmt.Sprintf("Checks that the new schema contians the field %s", leftFieldName), func() {
if leftFieldName == "Source" {
if leftFieldName == "Source" || leftFieldName == "NoUsers" {
Skip("Schema not updated yet")
}
Expect(
Expand Down

0 comments on commit a3aadbb

Please sign in to comment.