Compare commits

..

14 Commits

Author SHA1 Message Date
cd30bedd2b system: some user inmprovements
* not use cgo
* add lock/unlock user

Signed-off-by: Vasiliy Tolstov <v.tolstov@selfip.ru>
2015-11-25 11:26:00 +00:00
Jonathan Boulle
fa0178cd47 Merge pull request #400 from sethp/coreos-cloudinit/gzip-magic
gzip autodetection
2015-11-11 13:49:14 -08:00
Seth Pellegrino
778a47b957 userdata: gzip autodetection
look for the gzip magic number at the beginning of a data source, and,
if found, decompress the input before further processing.

Addresses coreos/bugs#741.
2015-11-11 08:21:00 -08:00
Seth Pellegrino
86909e5bcb test: added coreos-cloudinit_test to test script
Include the root project directory in packages
to be built/tested.
2015-11-11 08:06:36 -08:00
Josh Wood
0fd3cd2fae Merge pull request #406 from joshix/doubledash0
Vmware-guestinfo: Double hyphen long options.
2015-11-10 10:21:06 -08:00
Josh Wood
ad81cf7f78 Vmware-guestinfo: Double hyphen long options.
In line with https://github.com/coreos/docs/issues/650,
revert a little bit of https://github.com/coreos/coreos-cloudinit/pull/404
to document `--longoption` with two hyphens in document and Usage.
2015-11-09 16:31:30 -08:00
Josh Wood
0a500a19ff Merge pull request #405 from omkensey/doc-link-blob-fix
docs: fix links to specific blobs
2015-11-08 23:54:34 -08:00
Joe Thompson
b9f34d93ad docs: fix links to specific blobs
Rather than link to specific blob hashes, now that things have been synced to 0.4.9 we can link to the 0.4 release for original etcd; also fixes a link that was to a blob hash of configuration.md instead of the head of master for etcd.
2015-11-09 02:05:37 -05:00
Josh Wood
2f5d8cc188 Merge pull request #404 from joshix/backdoorname
Docs/vmware-backdoor: Rename backdoor to guestinfo
2015-11-05 15:35:45 -08:00
Josh Wood
b56c0f5609 Docs/vmware-backdoor: Rename backdoor to guestinfo
Based on results of google searches on a few possible titles
to improve on the connotation of backdoor, Guestinfo Configuration
Interface (as part of the RPC API) seems recognizable in vmware
circles. Friends call it just guestinfo.

This changeset also makes docs and usage for this flag appear with
a single hypen (-flag) like most go programs do.
2015-11-05 15:27:33 -08:00
Alex Crawford
cd1994b007 Merge pull request #403 from mdlayher/digitalocean_anchor_gateway
network: do not assign gateway for DigitalOcean anchor IP address
2015-10-27 09:10:34 -07:00
Matt Layher
1fd780befc network: do not assign gateway for DigitalOcean anchor IP address 2015-10-27 10:49:05 -04:00
Rob Szumski
8847a471c5 Merge pull request #393 from endocode/kayrus/vmware
Updated vmware info / Fixed help help message
2015-10-21 09:20:59 -07:00
kayrus
c0c144bd56 Added additional vmware info, fixed cli help 2015-10-19 10:33:27 +02:00
12 changed files with 175 additions and 51 deletions

View File

@@ -9,13 +9,18 @@ Location | Description
|Kernel command line: `cloud-config-url=http://example.com/user_data`.| You can find this string using this command `cat /proc/cmdline`. Usually used in [PXE](/os/docs/latest/booting-with-pxe.html) or [iPXE](/os/docs/latest/booting-with-ipxe.html) boots.| |Kernel command line: `cloud-config-url=http://example.com/user_data`.| You can find this string using this command `cat /proc/cmdline`. Usually used in [PXE](/os/docs/latest/booting-with-pxe.html) or [iPXE](/os/docs/latest/booting-with-ipxe.html) boots.|
|`/var/lib/coreos-install/user_data`| When you install CoreOS manually using the [coreos-install](/os/docs/latest/installing-to-disk.html) tool. Usually used in bare metal installations.| |`/var/lib/coreos-install/user_data`| When you install CoreOS manually using the [coreos-install](/os/docs/latest/installing-to-disk.html) tool. Usually used in bare metal installations.|
|`/usr/share/oem/cloud-config.yml`| Path for OEM images.| |`/usr/share/oem/cloud-config.yml`| Path for OEM images.|
|`/var/lib/coreos-vagrant/vagrantfile-user-data`| Vagrant OEM scripts automatically store Cloud-Config into this path. |
|`/var/lib/waagent/CustomData`| Azure platform uses OEM path for first Cloud-Config initialization and then `/var/lib/waagent/CustomData` to apply your settings.| |`/var/lib/waagent/CustomData`| Azure platform uses OEM path for first Cloud-Config initialization and then `/var/lib/waagent/CustomData` to apply your settings.|
|`http://169.254.169.254/metadata/v1/user-data` `http://169.254.169.254/2009-04-04/user-data` `https://metadata.packet.net/userdata`|DigitalOcean, EC2 and Packet cloud providers correspondingly use these URLs to download Cloud-Config.| |`http://169.254.169.254/metadata/v1/user-data` `http://169.254.169.254/2009-04-04/user-data` `https://metadata.packet.net/userdata`|DigitalOcean, EC2 and Packet cloud providers correspondingly use these URLs to download Cloud-Config.|
|`/usr/share/oem/bin/vmtoolsd --cmd "info-get guestinfo.coreos.config.data"`|Cloud-Config provided by [VMware Guestinfo][VMware Guestinfo]|
|`/usr/share/oem/bin/vmtoolsd --cmd "info-get guestinfo.coreos.config.url"`|Cloud-Config URL provided by [VMware Guestinfo][VMware Guestinfo]|
[VMware Guestinfo]: vmware-guestinfo.md
You can also run the `coreos-cloudinit` tool manually and provide a path to your custom Cloud-Config file: You can also run the `coreos-cloudinit` tool manually and provide a path to your custom Cloud-Config file:
```sh ```sh
sudo coreos-cloudinit --from-file=/home/core/cloud-config.yaml sudo coreos-cloudinit --from-file=/home/core/cloud-config.yaml
``` ```
This command will apply your custom cloud-config. This command will apply your custom cloud-config.

View File

@@ -81,7 +81,7 @@ For more information about the available configuration parameters, see the [etcd
_Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._ _Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._
[etcd-config]: https://github.com/coreos/etcd/blob/9fa3bea5a22265151f0d5063ce38a79c5b5d0271/Documentation/configuration.md [etcd-config]: https://github.com/coreos/etcd/blob/release-0.4/Documentation/configuration.md
#### etcd2 #### etcd2
@@ -117,11 +117,11 @@ Environment="ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379,http://0.0.0.0:4001"
Environment="ETCD_LISTEN_PEER_URLS=http://192.0.2.13:2380,http://192.0.2.13:7001" Environment="ETCD_LISTEN_PEER_URLS=http://192.0.2.13:2380,http://192.0.2.13:7001"
``` ```
For more information about the available configuration parameters, see the [etcd documentation][etcd-config]. For more information about the available configuration parameters, see the [etcd2 documentation][etcd2-config].
_Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._ _Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._
[etcd-config]: https://github.com/coreos/etcd/blob/86e616c6e974828fc9119c1eb0f6439577a9ce0b/Documentation/configuration.md [etcd2-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
#### fleet #### fleet

View File

@@ -1,12 +1,16 @@
# VMware Backdoor # # VMWare Guestinfo Interface
coreos-cloudinit is capable of reading userdata and metadata from the VMware ## Cloud-Config VMWare Guestinfo Variables
backdoor. This datasource can be enable with the `--from-vmware-backdoor` flag.
Userdata and metadata are passed from the hypervisor to the virtual machine
through guest variables. The following guest variables and their expected types
are supported by coreos-cloudinit:
| guest variable | type | coreos-cloudinit accepts configuration from the VMware RPC API's *guestinfo*
facility. This datasource can be enabled with the `--from-vmware-guestinfo`
flag to coreos-cloudinit.
The following guestinfo variables are recognized and processed by cloudinit
when passed from the hypervisor to the virtual machine at boot time. Note that
property names are prefixed with `guestinfo.` in the VMX, e.g., `guestinfo.hostname`.
| guestinfo variable | type |
|:--------------------------------------|:--------------------------------| |:--------------------------------------|:--------------------------------|
| `hostname` | `hostname` | | `hostname` | `hostname` |
| `interface.<n>.name` | `string` | | `interface.<n>.name` | `string` |
@@ -22,5 +26,10 @@ are supported by coreos-cloudinit:
| `coreos.config.url` | `URL` | | `coreos.config.url` | `URL` |
Note: "n", "m", "l", and "x" are 0-indexed, incrementing integers. The Note: "n", "m", "l", and "x" are 0-indexed, incrementing integers. The
identifier for the interfaces does not correspond to anything outside of this identifier for an `interface` does not correspond to anything outside of this
configuration; it is merely for mapping configuration values to each interface. configuration; it serves only to distinguish between multiple `interface`s.
The guide to [booting on VMWare][bootvmware] is the starting point for more
information about configuring and running CoreOS on VMWare.
[bootvmware]: https://github.com/coreos/docs/blob/master/os/booting-on-vmware.md

View File

@@ -164,7 +164,7 @@ func TestConfigCompile(t *testing.T) {
func TestCloudConfigUnknownKeys(t *testing.T) { func TestCloudConfigUnknownKeys(t *testing.T) {
contents := ` contents := `
coreos: coreos:
etcd: etcd:
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
coreos_unknown: coreos_unknown:
@@ -227,7 +227,7 @@ func TestCloudConfigEmpty(t *testing.T) {
// Assert that the parsing of a cloud config file "generally works" // Assert that the parsing of a cloud config file "generally works"
func TestCloudConfig(t *testing.T) { func TestCloudConfig(t *testing.T) {
contents := ` contents := `
coreos: coreos:
etcd: etcd:
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877" discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
update: update:
@@ -236,14 +236,14 @@ coreos:
- name: 50-eth0.network - name: 50-eth0.network
runtime: yes runtime: yes
content: '[Match] content: '[Match]
Name=eth47 Name=eth47
[Network] [Network]
Address=10.209.171.177/19 Address=10.209.171.177/19
' '
oem: oem:
id: rackspace id: rackspace
@@ -367,6 +367,7 @@ users:
gecos: arbitrary comment gecos: arbitrary comment
homedir: /home/place homedir: /home/place
no_create_home: yes no_create_home: yes
lock_passwd: false
primary_group: things primary_group: things
groups: groups:
- ping - ping

View File

@@ -24,6 +24,7 @@ type User struct {
GECOS string `yaml:"gecos"` GECOS string `yaml:"gecos"`
Homedir string `yaml:"homedir"` Homedir string `yaml:"homedir"`
NoCreateHome bool `yaml:"no_create_home"` NoCreateHome bool `yaml:"no_create_home"`
LockPasswd bool `yaml:"lock_passwd"`
PrimaryGroup string `yaml:"primary_group"` PrimaryGroup string `yaml:"primary_group"`
Groups []string `yaml:"groups"` Groups []string `yaml:"groups"`
NoUserGroup bool `yaml:"no_user_group"` NoUserGroup bool `yaml:"no_user_group"`

View File

@@ -15,8 +15,11 @@
package main package main
import ( import (
"bytes"
"compress/gzip"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os" "os"
"runtime" "runtime"
@@ -87,7 +90,7 @@ func init() {
flag.StringVar(&flags.sources.packetMetadataService, "from-packet-metadata", "", "Download Packet data from metadata service") flag.StringVar(&flags.sources.packetMetadataService, "from-packet-metadata", "", "Download Packet data from metadata service")
flag.StringVar(&flags.sources.url, "from-url", "", "Download user-data from provided url") flag.StringVar(&flags.sources.url, "from-url", "", "Download user-data from provided url")
flag.BoolVar(&flags.sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag)) flag.BoolVar(&flags.sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag))
flag.BoolVar(&flags.sources.vmware, "from-vmware-backdoor", false, "Read data from VMware backdoor") flag.BoolVar(&flags.sources.vmware, "from-vmware-guestinfo", false, "Read data from VMware guestinfo")
flag.StringVar(&flags.oem, "oem", "", "Use the settings specific to the provided OEM") flag.StringVar(&flags.oem, "oem", "", "Use the settings specific to the provided OEM")
flag.StringVar(&flags.convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files") flag.StringVar(&flags.convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
flag.StringVar(&flags.workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data") flag.StringVar(&flags.workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
@@ -121,7 +124,7 @@ var (
"from-packet-metadata": "https://metadata.packet.net/", "from-packet-metadata": "https://metadata.packet.net/",
}, },
"vmware": oemConfig{ "vmware": oemConfig{
"from-vmware-backdoor": "true", "from-vmware-guestinfo": "true",
"convert-netconf": "vmware", "convert-netconf": "vmware",
}, },
} }
@@ -147,7 +150,7 @@ func main() {
for k := range oemConfigs { for k := range oemConfigs {
oems = append(oems, k) oems = append(oems, k)
} }
fmt.Printf("Invalid option to --oem: %q. Supported options: %q\n", flags.oem, oems) fmt.Printf("Invalid option to -oem: %q. Supported options: %q\n", flags.oem, oems)
os.Exit(2) os.Exit(2)
} }
@@ -169,7 +172,7 @@ func main() {
dss := getDatasources() dss := getDatasources()
if len(dss) == 0 { if len(dss) == 0 {
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-packet-metadata, --from-url or --from-proc-cmdline") fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-packet-metadata, --from-digitalocean-metadata, --from-vmware-guestinfo, --from-waagent, --from-url or --from-proc-cmdline")
os.Exit(2) os.Exit(2)
} }
@@ -185,6 +188,11 @@ func main() {
log.Printf("Failed fetching user-data from datasource: %v. Continuing...\n", err) log.Printf("Failed fetching user-data from datasource: %v. Continuing...\n", err)
failure = true failure = true
} }
userdataBytes, err = decompressIfGzip(userdataBytes)
if err != nil {
log.Printf("Failed decompressing user-data from datasource: %v. Continuing...\n", err)
failure = true
}
if report, err := validate.Validate(userdataBytes); err == nil { if report, err := validate.Validate(userdataBytes); err == nil {
ret := 0 ret := 0
@@ -399,3 +407,17 @@ func runScript(script config.Script, env *initialize.Environment) error {
} }
return err return err
} }
const gzipMagicBytes = "\x1f\x8b"
func decompressIfGzip(userdataBytes []byte) ([]byte, error) {
if !bytes.HasPrefix(userdataBytes, []byte(gzipMagicBytes)) {
return userdataBytes, nil
}
gzr, err := gzip.NewReader(bytes.NewReader(userdataBytes))
if err != nil {
return nil, err
}
defer gzr.Close()
return ioutil.ReadAll(gzr)
}

View File

@@ -15,6 +15,9 @@
package main package main
import ( import (
"bytes"
"encoding/base64"
"errors"
"reflect" "reflect"
"testing" "testing"
@@ -87,3 +90,58 @@ func TestMergeConfigs(t *testing.T) {
} }
} }
} }
func mustDecode(in string) []byte {
out, err := base64.StdEncoding.DecodeString(in)
if err != nil {
panic(err)
}
return out
}
func TestDecompressIfGzip(t *testing.T) {
tests := []struct {
in []byte
out []byte
err error
}{
{
in: nil,
out: nil,
err: nil,
},
{
in: []byte{},
out: []byte{},
err: nil,
},
{
in: mustDecode("H4sIAJWV/VUAA1NOzskvTdFNzs9Ly0wHABt6mQENAAAA"),
out: []byte("#cloud-config"),
err: nil,
},
{
in: []byte("#cloud-config"),
out: []byte("#cloud-config"),
err: nil,
},
{
in: mustDecode("H4sCORRUPT=="),
out: nil,
err: errors.New("any error"),
},
}
for i, tt := range tests {
out, err := decompressIfGzip(tt.in)
if !bytes.Equal(out, tt.out) || (tt.err != nil && err == nil) {
t.Errorf("bad gzip (%d): want (%s, %#v), got (%s, %#v)", i, string(tt.out), tt.err, string(out), err)
}
}
}

View File

@@ -73,6 +73,11 @@ func Apply(cfg config.CloudConfig, ifaces []network.InterfaceGenerator, env *Env
} }
} }
if err = system.LockUnlockUser(&user); err != nil {
log.Printf("Failed lock/unlock user '%s': %v", user.Name, err)
return err
}
if len(user.SSHAuthorizedKeys) > 0 { if len(user.SSHAuthorizedKeys) > 0 {
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name) log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil { if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil {

View File

@@ -127,7 +127,7 @@ func parseInterface(iface digitalocean.Interface, nameservers []net.IP, useRoute
} }
} }
if iface.AnchorIPv4 != nil { if iface.AnchorIPv4 != nil {
var ip, mask, gateway net.IP var ip, mask net.IP
if ip = net.ParseIP(iface.AnchorIPv4.IPAddress); ip == nil { if ip = net.ParseIP(iface.AnchorIPv4.IPAddress); ip == nil {
return nil, fmt.Errorf("could not parse %q as anchor IPv4 address", iface.AnchorIPv4.IPAddress) return nil, fmt.Errorf("could not parse %q as anchor IPv4 address", iface.AnchorIPv4.IPAddress)
} }
@@ -140,15 +140,11 @@ func parseInterface(iface digitalocean.Interface, nameservers []net.IP, useRoute
}) })
if useRoute { if useRoute {
if gateway = net.ParseIP(iface.AnchorIPv4.Gateway); gateway == nil {
return nil, fmt.Errorf("could not parse %q as anchor IPv4 gateway", iface.AnchorIPv4.Gateway)
}
routes = append(routes, route{ routes = append(routes, route{
destination: net.IPNet{ destination: net.IPNet{
IP: net.IPv4zero, IP: net.IPv4zero,
Mask: net.IPMask(net.IPv4zero), Mask: net.IPMask(net.IPv4zero),
}, },
gateway: gateway,
}) })
} }
} }

View File

@@ -281,19 +281,6 @@ func TestParseInterface(t *testing.T) {
nss: []net.IP{}, nss: []net.IP{},
err: errors.New("could not parse \"bad\" as anchor IPv4 mask"), err: errors.New("could not parse \"bad\" as anchor IPv4 mask"),
}, },
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
AnchorIPv4: &digitalocean.Address{
IPAddress: "1.2.3.4",
Netmask: "255.255.0.0",
Gateway: "bad",
},
},
useRoute: true,
nss: []net.IP{},
err: errors.New("could not parse \"bad\" as anchor IPv4 gateway"),
},
{ {
cfg: digitalocean.Interface{ cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB", MAC: "01:23:45:67:89:AB",
@@ -305,7 +292,6 @@ func TestParseInterface(t *testing.T) {
AnchorIPv4: &digitalocean.Address{ AnchorIPv4: &digitalocean.Address{
IPAddress: "7.8.9.10", IPAddress: "7.8.9.10",
Netmask: "255.255.0.0", Netmask: "255.255.0.0",
Gateway: "11.12.13.14",
}, },
}, },
useRoute: true, useRoute: true,
@@ -326,12 +312,11 @@ func TestParseInterface(t *testing.T) {
nameservers: []net.IP{}, nameservers: []net.IP{},
routes: []route{ routes: []route{
{ {
net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)}, destination: net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
net.ParseIP("5.6.7.8"), gateway: net.ParseIP("5.6.7.8"),
}, },
{ {
net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)}, destination: net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
net.ParseIP("11.12.13.14"),
}, },
}, },
}, },

View File

@@ -18,15 +18,13 @@ import (
"fmt" "fmt"
"log" "log"
"os/exec" "os/exec"
"os/user"
"strings" "strings"
"github.com/coreos/coreos-cloudinit/config" "github.com/coreos/coreos-cloudinit/config"
) )
func UserExists(u *config.User) bool { func UserExists(u *config.User) bool {
_, err := user.Lookup(u.Name) return exec.Command("getent", "passwd", u.Name).Run() == nil
return err == nil
} }
func CreateUser(u *config.User) error { func CreateUser(u *config.User) error {
@@ -81,12 +79,46 @@ func CreateUser(u *config.User) error {
output, err := exec.Command("useradd", args...).CombinedOutput() output, err := exec.Command("useradd", args...).CombinedOutput()
if err != nil { if err != nil {
log.Printf("Command 'useradd %s' failed: %v\n%s", strings.Join(args, " "), err, output) log.Printf("Command 'useradd %s' failed: %v\n%s", strings.Join(args, " "), err, output)
return err
}
return nil
}
func IsLockedUser(u *config.User) bool {
output, err := exec.Command("getent", "shadow", u.Name).CombinedOutput()
if err == nil {
fields := strings.Split(string(output), ":")
if len(fields[1]) > 1 && fields[1][0] == '!' {
return true
}
}
return false
}
func LockUnlockUser(u *config.User) error {
args := []string{}
if u.LockPasswd {
args = append(args, "-l")
} else {
if !IsLockedUser(u) {
return nil
}
args = append(args, "-u")
}
args = append(args, u.Name)
output, err := exec.Command("passwd", args...).CombinedOutput()
if err != nil {
log.Printf("Command 'passwd %s' failed: %v\n%s", strings.Join(args, " "), err, output)
} }
return err return err
} }
func SetUserPassword(user, hash string) error { func SetUserPassword(user, hash string) error {
cmd := exec.Command("/usr/sbin/chpasswd", "-e") cmd := exec.Command("chpasswd", "-e")
stdin, err := cmd.StdinPipe() stdin, err := cmd.StdinPipe()
if err != nil { if err != nil {
@@ -112,3 +144,12 @@ func SetUserPassword(user, hash string) error {
return nil return nil
} }
func UserHome(name string) (string, error) {
output, err := exec.Command("getent", "passwd", name).CombinedOutput()
if err != nil {
return "", err
}
passwd := strings.Split(string(output), ":")
return passwd[5], nil
}

1
test
View File

@@ -21,6 +21,7 @@ SRC="
network network
pkg pkg
system system
.
" "
echo "Checking gofix..." echo "Checking gofix..."