sudo shell script mkdir - shell

myscript.sh is
#/!bin/sh
mkdir -p $1
cp -p a.txt ./$1
cp b.txt /usr
If I invoke it with sudo ./myscript.sh, the directory $1 is owned by root, so the user can't modify a.txt (which is a problem). I could change the script to
#/!bin/sh
mkdir -p $1
cp -p a.txt ./$1
sudo cp b.txt /usr
and invoke with just ./myscript.sh but I get the impression this is bad practice. How to proceed in the general case, where I don't know the user, so chown doesn't help?

SUDO_USER environment variable is set by sudo to the name of the user who invoked sudo. You can use it for chown.
As of bad practices, you don't check anything and your $1 argument substitution is broken for filenames with spaces. If that much doesn't matter, should you care for the rest?

You should add this line
chmod ug+rw a.txt
With this one, the user will have read/write permissions on 'a.txt'.

Related

bash: how to avoid a command from being used and run

Is there a way to prevent a bash or zsh terminal to run a specific command?
Let's say I want to prevent running by mistake a rm on directory ~/foo. So I want to prevent the following command to be run:
rm -r ~/foo
Another example much broader:
mycustomcommand -param1 foo
In the example above, foo can change, but every time one tries to run:
mycustomcommand -param1 anything
It should block and fail.
Therefore, if I run the above command in my local terminal (or a server, etc) the command is blocked (and even better with an error/warning message).
How can we achieve such behavior?
Linux permissions
Linux has users, groups, file permissions and file attributes to restrict what may be done with the each file and this is the simple and reliable way.
E.g. When file cannot be deleted, even by root (without resetting the attributes), set:
$ sudo chattr +i cannot_delete_me.txt
$ rm cannot_delete_me.txt
rm: cannot remove 'cannot_delete_me.txt': Operation not permitted
To disallow removing the file without a question by non-root account, it is enough to set corret file permissions:
$ chmod a-w cannot_delete_me.txt
$ rm cannot_delete_me.txt
rm: remove write-protected regular empty file 'cannot_delete_me.txt'?
# Now it is possible to delete it when typing y + enter
To disallow deleting a file by non-root account without changing the permissions, change parent directory write permission:
$ mkdir foo && cd foo
$ touch cannot_delete_me.txt
$ chmod a-w .
$ rm cannot_delete_me.txt
rm: cannot remove 'cannot_delete_me.txt': Permission denied
Shadowing the command name
Any command may be called in many ways, different paths, so it is not secure to shadow command name:
$ function rm { echo "I am dummy rm function. I will not remove: $1"; }
$ export -f rm
$ rm foo
I am dummy rm function. I will not remove: foo
/bin/rm foo # It will delete foo
Use Docker
When you don't trust the script you are calling, call it inside the Docker container. It allows binding directories from host system with e.g only read permissions, so you are sure that given script will access things you allowed.
Inside the Docker image, you also may replace unwanted Linux commands.
Let's see the example:
$ mkdir my_script_environment_image && cd my_script_environment_image
Create Dockerfile inside my_script_environment_image directory:
FROM debian:latest
RUN echo '#/bin/bash \n\
echo "This is a dummy rm. I will not remove: $1" \n\
' > /bin/rm
Build the image with defined name:
sudo docker build --tag my_script_environment .
Call a temporary (with --rm) container which will run shell or your script.
sudo docker run -it --rm my_script_environment /bin/bash
root#f6d62a754b38:/# touch my_file.txt
root#f6d62a754b38:/# rm my_file.txt
This is a dummy rm. I will not remove: my_file.txt
So your command has been replaced by custom command and whole Docker environment.
In practice, I'm using Docker to control the scope of script/application effects.

Understanding a docker entrypoint script

The script is located here: https://github.com/docker-library/ghost/blob/master/docker-entrypoint.sh
#!/bin/bash
set -e
if [[ "$*" == npm*start* ]]; then
baseDir="$GHOST_SOURCE/content"
for dir in "$baseDir"/*/ "$baseDir"/themes/*/; do
targetDir="$GHOST_CONTENT/${dir#$baseDir/}"
mkdir -p "$targetDir"
if [ -z "$(ls -A "$targetDir")" ]; then
tar -c --one-file-system -C "$dir" . | tar xC "$targetDir"
fi
done
if [ ! -e "$GHOST_CONTENT/config.js" ]; then
sed -r '
s/127\.0\.0\.1/0.0.0.0/g;
s!path.join\(__dirname, (.)/content!path.join(process.env.GHOST_CONTENT, \1!g;
' "$GHOST_SOURCE/config.example.js" > "$GHOST_CONTENT/config.js"
fi
ln -sf "$GHOST_CONTENT/config.js" "$GHOST_SOURCE/config.js"
chown -R user "$GHOST_CONTENT"
set -- gosu user "$#"
fi
exec "$#"
From what I know, it says that if you use some variation of npm start to move some files around from $GHOST_SOURCE to $GHOST_CONTENT, do something to the config.js file, link the config file, set ownership of the content files, and then execute npm start as the user user. Otherwise, it just runs your commands normally.
The specifics are what are hard for me to understand because there are a lot of things from bash that I've never seen before. So I have a lot of questions.
for dir in "$baseDir"/*/ "$baseDir"/themes/*/; do
In the above, why do they specify both /*/ and /themes/*/? Shouldn't /*/ contain themes? Is * not a wildcard for some reason?
targetDir="$GHOST_CONTENT/${dir#$baseDir/}"
In the above, what is the point of # in the variable expansion?
tar -c --one-file-system -C "$dir" . | tar xC "$targetDir"
In the above, does this somehow save time? Why not use something like rsync? I understand the point of -C, but why -c and --one-file-system?
sed -r '
s/127\.0\.0\.1/0.0.0.0/g;
s!path.join\(__dirname, (.)/content!path.join(process.env.GHOST_CONTENT, \1!g;
' "$GHOST_SOURCE/config.example.js" > "$GHOST_CONTENT/config.js"
What does this sed command do? I know it's a replacement, but why the "$GHOST_SOURCE/config.example.js" > "$GHOST_CONTENT/config.js" as the end?
ln -sf "$GHOST_CONTENT/config.js" "$GHOST_SOURCE/config.js"
In the above, what is the point of this symlink? Why try to link them to each other if both files already exist?
set -- gosu user "$#"
In the above what does calling set with no args do?
I hope that's not too much. I felt making a separate question for each of these would be too much especially since it's all related to each other.
for dir in "$baseDir"/*/ "$baseDir"/themes/*/; do
In the above, why do they specify both /*/ and /themes/*/? Shouldn't
/*/ contain themes? Is * not a wildcard for some reason?
themes/ is in the first match, but themes/*/ is not, so you need the second entry to include the contents of themes.
targetDir="$GHOST_CONTENT/${dir#$baseDir/}"
In the above, what is the point of # in the variable expansion?
It removes the $baseDir prefix from $dir. So for example:
bash$ dir=/home/bmitch/data/docker
bash$ echo $dir
/home/bmitch/data/docker
bash$ echo ${dir#/home/bmitch}
/data/docker
tar -c --one-file-system -C "$dir" . | tar xC "$targetDir"
In the above, does this somehow save time? Why not use something like
rsync? I understand the point of -C, but why -c and --one-file-system?
rsync may not be installed on every machine by default, tar is fairly universal. The -c is to create, vs extract, and --one-file-system avoids tar continuing to an outside mount point (nfs, symlink to root, etc).
sed -r '
s/127\.0\.0\.1/0.0.0.0/g;
s!path.join\(__dirname, (.)/content!path.join(process.env.GHOST_CONTENT, \1!g;
' "$GHOST_SOURCE/config.example.js" > "$GHOST_CONTENT/config.js"
What does this sed command do? I know it's a replacement, but why the
"$GHOST_SOURCE/config.example.js" > "$GHOST_CONTENT/config.js" as the
end?
config.example.js is the input (last arg to the sed), config.js is the output (after the >). So it takes the config.example.js, change the ip address from 127.0.0.1 to 0.0.0.0, effectively listening on all interfaces/ip's instead of just internally on the loopback. The second half of the sed is changing the path.join arguments from __dirname to process.env.GHOST_CONTENT.
ln -sf "$GHOST_CONTENT/config.js" "$GHOST_SOURCE/config.js"
In the above, what is the point of this symlink? Why try to link them
to each other if both files already exist?
The $GHOST_SOURCE/config.js is replaced (-f) with a link to $GHOST_CONTENT/config.js. Symbolic links give a file name reference to another actual file, so there will be two names, but one copy of the data, which means you will only have a single configuration in this situation.
set -- gosu user "$#"
In the above what does calling set with no args do?
This changes the values of $1, $2, ... $n to be $1=gosu, $2=user, $3=the old $1, $4=the old $2..., essentially adding the gosu and user to the beginning of the passed parameters to the script. The -- makes sure that set doesn't interpret any values from $# as a flag for itself.

How to create subfolders and files if not present inside a script without multiple mkdir?

What is a better way to create sub folders in a shell script? Instead of using the following method?
mkdir /var/log
mkdir /var/log/celery
mkdir /var/log/celery/stdout
mkdir /var/log/celery/stderr
touch /var/log/celery/stdout/stdout.log <<< I'm hoping the use this path create folder if doesn't exists....
touch /var/log/celery/stderr/stderr.log
mkdir has a -p flag that will create parent directories but touch will not create directories that do not exist.
That still cuts the above down to:
mkdir -p /var/log/celery/stdout /var/log/celery/stderr
touch /var/log/celery/stdout/stdout.log /var/log/celery/stderr/stderr.log
Which in a shell that supports brace expansion could even be:
mkdir -p /var/log/celery/{stdout,stderr}
touch /var/log/celery/{stdout/stdout.log,stderr/stderr.log}
And actually, if you have brace expansion but not mkdir -p you could do:
mkdir /var/log{,/celery{,/{stdout,stderr}}}
touch /var/log/celery/{stdout/stdout.log,stderr/stderr.log}
But there isn't any way to combine the mkdir and touch steps with standard tools that I'm aware of.
The -p option of mkdir will create the intermediate folders of the path if they don't exists (and of course, if you have the appropriate privileges):
mkdir -p /var/log/celery/stderr
To create the file, you can append the touch after the operator &&, so the touch operation only occurs if the directory either was created successfully or already exists:
mkdir -p /var/log/celery/stderr && touch "$_/stderr.log"
(Basically, the $_ will pass the dir path to the touch command)
UNTESTED:
$ needir () { mkdir -p $1; echo $1; }
$ touch $(needir /var/log/celery/stderr)/stderr.log
and put "needir" in your .profile, or better yet, in a function library on your path that you source when you login. you'd be surprised how often you'll be using it.

Cannot create directory with tilde

Given this script
# cat foo.sh
echo $HOME
set -x
mkdir ~
I am getting this result
# ./foo.sh
/home/Steven Penny
++ mkdir ''
mkdir: cannot create directory `': No such file or directory
If I run mkdir "$HOME" I have no problem. I think the issue is the space in the path, but can someone shed some light on this?
Looking at your debug output
++ mkdir ''
looks like "~" in mkdir ~ is expanding to nothing, thus causing mkdir to fail. As mkdir $HOME works fine it means you have enough space in the disk.
Can you check if echo ~ is displaying your home directory.
The problem was that the /etc/passwd file was missing.
To elaborate, even one this simple will work
Steven Penny::0:0::/home/Steven Penny

Why might "mkdir -p" lead to a Bash permission error?

If I make a directory with mkdir -p, it causes problems with scripts
$ mkdir -p test2/test2
$ cd test2/test2
$ echo '#!/bin/sh
> echo hello' > hello.sh
$ ./hello.sh
bash: ./hello.sh: Permission denied
This is nothing to do with mkdir. You simply haven't given hello.sh executable permissions. You need the following:
chmod +x hello.sh
Check your permissions
Check the permissions on your directories and the script itself. There may be a problem there, although it's unlikely.
ls -lad test2/test2
ls -l test2/test2/hello.sh
You can always use the --mode flag with mkdir if your permissions aren't being set correctly for some reason. See chmod(1) and mkdir(1) for more information.
Execute the file directly
You can execute the script with Bash directly, rather than relying on a shebang line or the executable bit, as long as the file is readable by the current user. For example:
bash test2/test2/hello.sh
Change file permissions
If you can execute the file when invoked explicitly with Bash, then you just need to make sure your file has the execute bit set. For example:
chmod 755 test2/test2/hello.sh

Resources