I wonder whether someone can help me with what I'm trying to achieve
Basically, the objective is one script to create new user on more than 70 linux hosts if required.
Everything works apart from the highlighted part. It gave me an output
passwd: Unknown user name ''. when try to set remote password for new user. It seems like it's trying to set password locally and not remotely.
The idea is all information about username,password,uid,comments is pull out from the file IDENTIFY. That way, if we create new user, people only edit the file than the script.
All fields in the IDENTIFY file separates by white space.
Please help how can I achieve this
#!/bin/bash
HOSTS="/tmp/test/serverlist"
DDID="IDENTIFY"
HOMEPATH="/home/$UNAME"
for i in `cat $HOSTS` ;
do
UNIQUE=`awk -F " " '{print $1 }' $DDID`
RUID=`ssh $i 'grep "$UNIQUE" /etc/passwd'`
if [[ -ne "$RUID" ]]
then
echo "User ID is currently available on $i, ready to add new user"
UNAME=`awk -F " " '{print $2 }' $DDID`
PASSWORD=`awk -F " " '{print $3 }' $DDID`
ROLE=`awk -F " " '{print $4 }' $DDID`
`ssh $i useradd -u "$UNIQUE" -d "$HOMEPATH" -s /bin/bash -c "$ROLE" -m -k /etc/skel/ "$UNAME"`
`ssh $i echo "$PASSWORD" | passwd --stdin "$UNAME"`
else
echo "User ID exist on $i, check new ID"
fi
done
I would do one ssh per host and ship the scripting to the remote host, for speed and simplicity.
For getting hosts from your config file, just:
while read i
do
...
done < file
For good error handling, echo each host, time and user as you start each, into a log. you can display the log to the terminal, too, with 'tee -a' or 'tail -f log_file &' (just remember to kill $!). Screens can go poof, and you should keep a report.
A user name can be a substring of any field (like roo or oot !), so put some boundaries in the grep:
You can put a whole script on the command line, or just send it into ssh bash. I prefer "echo '...'|ssh ...." but "<<!" is pretty popular, too, just be careful to escape meta like $ from local expansion. No need to leave files lying around or have multiple scripts. Don't forget to set up env on remote shell, like '. ./.profile' or the like.
Please omit the backticks! They will run the output as commands. If the output would be "reboot" ... guess what happens!
Further, you can combine two adjacent ssh statements in one ssh statement:
The calling host sees one multi-line "string".
Within the "" quotes it replaces each $VAR by its value.
$ROLE should be quoted on the remote host; you need either \"$ROLE\" or '$ROLE'. The '' ticks do not replace $ROLE by its value, but this is meaningless on the remote host, because it is already replaced by the calling host. And the calling host treats a ' tick as simple characters if it appears within a "string".
There are many more possible optimizations; DGPicket pointed out some...
#!/bin/bash
HOSTS="/tmp/test/serverlist"
DDID="IDENTIFY"
HOMEPATH="/home/$UNAME"
# read from $DDID
while read UNIQUE UNAME PASSWORD ROLE
do
# read from $HOST
while read i
do
echo "
# remote script start
# (in quotes, passed to /bin/sh on HOST; don't use quotes here!)
PATH=/bin:/usr/bin:/usr/sbin:/sbin
export PATH
if getent passwd $UNIQUE >/dev/null
then
echo 'UID $UNIQUE exists on $i, check new ID'
elif getent passwd $UNAME >/dev/null
then
echo 'User $UNAME exists on $i'
else
echo 'UID $UNIQUE is currently available on $i, ready to add new user'
useradd -u $UNIQUE -d '$HOMEPATH' -s /bin/bash -c '$ROLE' -m -k /etc/skel/ $UNAME
echo '$PASSWORD' | passwd --stdin $UNAME
fi
# remote script end
" | ssh -x $i /bin/sh
done < $HOSTS
done < $DDID
This script allows more than one line in the IDENTITY file.
---------- Post updated at 05:02 PM ---------- Previous update was at 04:58 PM ----------
Thanks Guys for the -p option in useradd.
The password side of my script is now working
However, it doesn't create home directory using the script and also the if arguments kept re-create user instead of saying "User ID does exist....." if I ran the script twice
Can someone have another second look please?
#!/bin/bash
HOSTS="/tmp/test/serverlist"
DDID="IDENTIFY"
HOMEPATH="/home/$UNAME"
for i in `cat $HOSTS` ;
do
UNIQUE=`awk -F " " '{print $1}' $DDID`
RUID=`ssh $i 'grep "$UNIQUE" /etc/passwd'`
if [[ -ne "$RUID" ]]
then
echo "=====User ID is currently available on $i, ready to add new user====="
UNAME=`awk -F " " '{print $2 }' $DDID`
PASSWORD=`awk -F " " '{print $3 }' $DDID`
ROLE=`awk -F " " '{print $4 }' $DDID`
#`ssh $i useradd -u "$UNIQUE" -d "$HOMEPATH" -s /bin/bash -c "$ROLE" -m -k /etc/skel/ "$UNAME"`
ssh $i useradd -u "$UNIQUE" -d "$HOMEPATH" -c "$ROLE" -p $(openssl passwd "$PASSWORD") "$UNAME"
echo "==========User $UNAME created=========="
else
echo "*****User ID does exist on $i, check new ID*****"
fi
done
-m will only create the home directory if CREATE_HOME in /etc/login.defs is set to yes.
Don't think this test is correct, UID may appear in other places in password file (eg phone extension, etc), -ne incorrect for testing for blank strings:
RUID=`ssh $i 'grep "$UNIQUE" /etc/passwd'`
if [[ -ne "$RUID" ]]
The userid was $1 last I looked! OK, maybe user name, but isn't that what admins should be keying on? If you want the uid to match on all hosts, it's yp/nis time.
It's horrible isn't it. The reason is that we need $UNIQUE to be expanded client side (before it's passwd over ssh) the double quotes are needed in the awk program so awk ends up seeing something like $3=="2213" .
These double and triple levels of quoting is one of the main reasons I nearly always try and build my scripts client side, then scp them to the server and execute there. Anything longer than one or two lines turns into a quoting storm that's very difficult to read or debug.
One way to avoid grief is to put everything in ', except ' itself and $varaibles and file globs. For $variable, you nip out of ' land and into " land for the variable and then go back to ' land. ' itself is '"'"' or '\'' file globs must be barefoot.
Yes, that technique helps somewhat but I still end up reverting to trial and error more often than not.
Especially with awk which inexplicably seems to need odd numbers of backslash characters (3,5 or 7!) to get some complex REs working (and worse still it varies depending the expression being within // or "").
So you can see the double quotes aren't really doing much and should probably also have backslash to protect them from the remote shell and pass them thru to awk (my intention was for awk to see $3="2341" but as UIDs are numeric this will still work)