mecano
Version:
Common functions for system deployment.
167 lines (156 loc) • 7.28 kB
Markdown
`user([goptions], options, callback)`
--------------------------------------
Create or modify a Unix user.
each = require 'each'
misc = require './misc'
conditions = require './misc/conditions'
child = require './misc/child'
execute = require './execute'
`options` Command options include:
* `name` Login name of the user.
* `home` Value for the user´s login directory, default to the login name appended to "BASE_DIR".
* `shell` Path to the user shell, set to "/sbin/nologin" if "false,
"/bin/bash" if true or default to the system shell value in
"/etc/default/useradd", by default "/bin/bash".
* `system` Create a system account, such user are not created with a home by default, set the "home" option if we it to be created.
* `uid` Numerical value of the user´s ID, must not exist.
* `gid` Group name or number of the user´s initial login group.
* `comment` Short description of the login.
* `password` User password
* `expiredate`
* `inactive`
* `groups`
* `skel`
* `ssh` Run the action on a remote server using SSH, an ssh2 instance or an configuration object used to initialize the SSH connection.
* `log` Function called with a log related messages.
`callback` Received parameters are:
* `err` Error object if any.
* `modified` Number of created or modified users.
Example:
```coffee
mecano.user
name: "myself"
system: true
uid: 490
gid: 10
comment: 'This is myself'
, (err, modified) -> ...
```
The result of the above action can be viewed with the command
`cat /etc/passwd | grep myself` producing an output similar to
"myself:x:490:10:Hive:/home/myself:/bin/bash". You can also check you are a
member of the "wheel" group (gid of "10") with the command `id hive` producing
an output similar to "uid=490(hive) gid=10(wheel) groups=10(wheel)".
module.exports = (goptions, options, callback) ->
[goptions, options, callback] = misc.args arguments, parallel: true
result = child()
finish = (err, gmodified) ->
callback err, gmodified if callback
result.end err, gmodified
misc.options options, (err, options) ->
return finish err if err
gmodified = 0
each( options )
.parallel(goptions.parallel)
.on 'item', (options, next) ->
return next new Error "Option 'name' is required" unless options.name
# options.comment ?= ""
# options.home ?= "/home/#{options.name}"
# options.shell ?= "/sbin/nologin"
options.shell = "/sbin/nologin" if options.shell is false
options.shell = "/bin/bash" if options.shell is true
options.system ?= false
options.gid ?= null
options.groups = options.groups.split ',' if typeof options.groups is 'string'
return next new Error "Invalid option 'shell': #{JSON.strinfigy options.shell}" if options.shell? typeof options.shell isnt 'string'
modified = false
user_info = groups_info = null
do_info = ->
options.log? "Get user information for #{options.name}"
options.ssh?.passwd = null # Clear cache if any
misc.ssh.passwd options.ssh, (err, users) ->
return next err if err
options.log? "Got #{JSON.stringify users[options.name]}"
user_info = users[options.name]
# Create user if it does not exist
return do_create() unless user_info
# Compare user attributes unless we need to compare groups membership
return do_compare() unless options.groups
# Renew group cache
options.ssh?.cache_group = null # Clear cache if any
misc.ssh.group options.ssh, (err, groups) ->
return next err if err
groups_info = groups
do_compare()
# if info then do_compare() else do_create()
do_create = ->
cmd = 'useradd'
cmd += " -r" if options.system
cmd += " -M" unless options.home
cmd += " -d #{options.home}" if options.home
cmd += " -s #{options.shell}" if options.shell
cmd += " -c #{misc.string.escapeshellarg options.comment}" if options.comment
cmd += " -u #{options.uid}" if options.uid
cmd += " -g #{options.gid}" if options.gid
cmd += " -e #{options.expiredate}" if options.expiredate
cmd += " -f #{options.inactive}" if options.inactive
cmd += " -G #{options.groups.join ','}" if options.groups
cmd += " -k #{options.skel}" if options.skel
cmd += " #{options.name}"
execute
ssh: options.ssh
cmd: cmd
log: options.log
stdout: options.stdout
stderr: options.stderr
code_skipped: 9
, (err, created) ->
return next err if err
if created
modified = true
do_password()
else
options.log? "User defined elsewhere than '/etc/passwd', exit code is 9"
next()
do_compare = ->
for k in ['uid', 'home', 'shell', 'comment', 'gid']
modified = true if options[k]? and user_info[k] isnt options[k]
if options.groups then for group in options.groups
return next err "Group does not exist: #{group}" unless groups_info[group]
modified = true if groups_info[group].user_list.indexOf(options.name) is -1
options.log? "Did user information changed: #{modified}"
if modified then do_modify() else do_password()
do_modify = ->
cmd = 'usermod'
cmd += " -d #{options.home}" if options.home
cmd += " -s #{options.shell}" if options.shell
cmd += " -c #{misc.string.escapeshellarg options.comment}" if options.comment?
cmd += " -g #{options.gid}" if options.gid
cmd += " -G #{options.groups.join ','}" if options.groups
cmd += " -u #{options.uid}" if options.uid
cmd += " #{options.name}"
execute
ssh: options.ssh
cmd: cmd
log: options.log
stdout: options.stdout
stderr: options.stderr
, (err, _, __, stderr) ->
return next new Error "User #{options.name} is logged in" if err?.code is 8
return next err if err
do_password()
do_password = ->
return next() unless options.password
# TODO, detect changes in password
execute
ssh: options.ssh
cmd: "echo #{user.password} | passwd --stdin #{user.username}"
log: options.log
stdout: options.stdout
stderr: options.stderr
, (err) ->
next err
do_info()
.on 'both', (err) ->
finish err, gmodified
result