Starting Midnight Commander `mc` with sudo alias and preserve path - bash

Is it possible to start the mc-wrapper with sudo and still use the last selected directory on the console when quitting sudo mc (requirement number 4)? My default alias looks like this.
alias mc='EDITOR="${EDITOR-mcedit}" . /usr/lib/mc/mc-wrapper.sh'
Some errors (for the Googlers)
sudo: mc: command not found
sudo: .: command not found # "." == "source"
My requirements
Ubuntu 18.04.1.
The alias should work with and without sudo call.
If possible, a single alias for mc in /etc/bash.bashrc for all users.
The directory you changed to with sudo mc should be "preserved" after closing the Midnight Commander. This means that you will not be in the same directory as you started sudo mc (provided it is not the same directory).
Optional requirements
See if the alias was started with super powers.
See if the alias was started with sudo.
If the alias mc was started without super powers or sudo, ask if the program mc should still be started with sudo rights.
If the question is answered No, use my default alias.
In all other cases, the first four requirements should be met.
The editor (e.g. mcedit or vi) within mc should be selectable via another alias like mcvi (for vi) without code duplication.
Arguments should be passed on to the program, like sudo mc /opt/ /mnt/

Here's one hacky solution (tested, but the last two optional requirements are still missing).
/etc/bash.bashrc
alias sudo='sudo ' # fixes "sudo: mc: command not found" (breaks with an argument: sudo -H ll)
# sudo apt update && sudo apt install dialog mc pwgen
#
# Start the alias with a "real" command (instead of a shell keyword like "if") so that sudo is not confused.
# This first command (env) will eat sudo and all arguments! Even the following file redirection including the angle bracket is already executed without sudo.
alias mc='env > "/tmp/mc.env.$(whoami).$$"
MC_USER="$(whoami)"
MC_ENV_FILE="/tmp/mc.env.${MC_USER}.$$"
# cat "${MC_ENV_FILE}"
if [ "$(cat "${MC_ENV_FILE}" | grep "$(id -nu 0)" | wc -l)" -gt "3" ]; then
# echo "This alias was called with super powers."
MC_ROOT="root"
fi
if [ "$(cat "${MC_ENV_FILE}" | grep "^SUDO_" | wc -l)" -gt "3" ]; then
# echo "This alias was called with sudo (possibly sudo -i or sudo -s was entered before)."
MC_SUDO="sudo"
fi
if [ "${MC_ROOT}" == "root" ] || [ "${MC_SUDO}" == "sudo" ]; then
MC_DIALOG="0"
else
# echo "This alias was called as normal user."
dialog --cr-wrap --defaultno --title "sudo mc" --yesno "Do you want super powers (sudo/root)?\n\n(Alternatively you can use \"sudo mc\" directly next time.)\n\nAgain: Do you want super powers (sudo/root)?" 9 64
MC_DIALOG="$?"
tput reset
fi
if [ "${MC_DIALOG}" != "0" ]; then
# echo "No, do not use sudo and stay normal user."
# echo "Standard wrapper (without arguments)..."
EDITOR="${EDITOR-mcedit}" . /usr/lib/mc/mc-wrapper.sh # does not work with sudo because "." is not a program like "ls" or "grep"!
else
# echo "Yes, exec those decisive commands with super powers."
# echo "Custom wrapper (also without arguments)..."
MC_PWD_FILE_DIRNAME="${TMPDIR-/tmp}/mc-${MC_USER}/"
MC_PWD_FILE="${MC_PWD_FILE_DIRNAME}mc.pwd.$$.$(pwgen 13 1)"
sudo mkdir -p "$MC_PWD_FILE_DIRNAME"
sudo chown "$(sudo whoami)":"$(sudo whoami)" "$MC_PWD_FILE_DIRNAME"
sudo chmod 0700 "$MC_PWD_FILE_DIRNAME"
sudo EDITOR="${EDITOR-mcedit}" /usr/bin/mc -P "$MC_PWD_FILE"
sudo chown -R "$MC_USER":"$MC_USER" "$MC_PWD_FILE_DIRNAME"
if test -r "$MC_PWD_FILE"; then
MC_PWD=$(cat "$MC_PWD_FILE")
if test -n "$MC_PWD" && test -d "$MC_PWD"; then
cd "$MC_PWD"
fi
unset MC_PWD
fi
rm -f "$MC_PWD_FILE"
unset MC_PWD_FILE
unset MC_PWD_FILE_DIRNAME
fi
unset MC_DIALOG
unset MC_SUDO
unset MC_ROOT
rm -f "${MC_ENV_FILE}"
unset MC_ENV_FILE
unset MC_USER
# This terminating line break is required:
'
I didn't manage to use a function mcwrapper (and $(declare -f mcwrapper)) and I don't think it's that easy either!?

Related

How to handle additional files when building and running a AUR package?

So I was just playing around and created this simple Shell script:
TestScript.sh
#!/bin/bash
read -p "read or write? " INP
if [[ "${INP}" == "write" ]]
then
read -p "Write your text: " TEXT
touch /usr/share/textfile.txt
echo "${TEXT}" >> /usr/share/textfile.txt
else
cat /usr/share/textfile.txt
fi
which of course can easily read and write into a file under /usr after gaining sudo priviliges:
sudo sh TestScript.sh
Based on this file I create a test PKGBUILD to install TestScript.sh via pacman later on:
PKGBUILD
# Maintainer: Your Name <youremail#domain.com>
pkgname='test-script'
pkgver=1
pkgrel=1
pkgdesc="AUR test package"
arch=('x86_64')
license=('custom')
source=('TestScript.sh')
md5sums=('SKIP')
package() {
mkdir -p "${pkgdir}/usr/bin"
cp "${srcdir}/TestScript.sh" "${pkgdir}/usr/bin/TestScript"
chmod +x "${pkgdir}/usr/bin/TestScript"
}
Followed by
makepkg
sudo pacman -U test-script-1-1-x86_64.pkg.tar.xz
I can run
TestScript
from the command line. But just as I thought, I am unable to write into /usr/share/textfile.txt .
I was searching the Arch-Wiki page up and down, but I couldn't find out how to handle this situation. I basically just want to have a location where I can properly read and write a file without messing up the user space.
Does anyone have an idea?

How to use lxc exc to issue multiple commands as specific user

My goal is to execute two commands in a specific folder as ubuntu from outside of it's lxc container.
I've tried a couple of things but I figured this example is the closest I have to working.
If I run
root#host$ lxc exec my-containter -- sudo --login --user ubuntu eval "cd /home/ubuntu/mydir && pwd && whoami && env && npm install && echo done"
I get an npm install error that can't find some module, but it looks like I'm the right user
However if I manually do it as two steps it does work... but I'm trying to put this in a bash script, so that I can keep doing operations on the host, so I think I need it as one.
root#host$ lxc exec my-containter -- sudo --login --user ubuntu
ubuntu#my-container$ eval "cd /home/ubuntu/mydir && pwd && whoami && env && npm install && echo done";
I discovered that my PATH environment variable is different in these two situations, the one that is failing is missing a specific path for nvm/npm. I tried exporting it during the eval command, but it seems like the resources available to me have already been found? What could I do to make the PATH variable populate the same way in the single line scenario?
PATH from 1-line (non-interactive)
PATH=/home/ubuntu/bin:/home/ubuntu/.local/bin:/home/ubuntu/bin:/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/snap/bin:/snap/bin
PATH from 2-lines (interactive)
PATH=/home/ubuntu/bin:/home/ubuntu/.local/bin:/home/ubuntu/.nvm/versions/node/v8.9.4/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/snap/bin
I've also noticed this nvm code at the bottom on my .bashrc file. From what I've read it sounds like the .bashrc file only gets executed in interactive mode.
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
The below command should do the job for you
lxc exec my-containter -- sudo --login --user ubuntu bash -ilc "cd /home/ubuntu/mydir && pwd && whoami && npm install && echo done"
The .bashrc file has below at the top
case $- in
*i*) ;;
*) return;;
This code prevents the rest of the part of .bashrc to be executed in case of a non-interactive bash. So to make it interactive you should add the -i flag

Why `~/.bashrc` is not executed when run docker container?

I have a docker file as below. launch.sh is the entry point in this docker image.
FROM ubuntu:16.04
USER root
RUN apt-get update && apt-get install -y \
curl \
vim \
net-tools \
git \
iputils-ping \
wget
RUN apt-get install -y python
RUN apt-get update && apt-get install -y gcc g++ make libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
ENV NVM_DIR /root/.nvm
RUN . $NVM_DIR/nvm.sh && \
nvm install 7.9.0 && npm install -g npm#5.6.0
ADD ./Docker/launch.sh /workspace/
CMD ["/bin/sh", "/workspace/launch.sh"]
The content of launch.sh is:
#!/bin/bash
cd /workspace/demo
npm install
node index.js
when I run the docker container: docker run IMAGE_NAME, I got this error:
npm: not found
node: not found
The node in this image is managed by nvm which has been installed and its script has been set on /root/.bashrc file. But I don't know why it can't find the nodejs commands. But if I run the container by docker run -it IMAGE_NAME bash, then manually run workspace/launch.sh command, everything works fine. It seems the ~/.bashrc is not executed when run the image. How can I let the container source .bashrc?
The content of /root/.bashrc is:
# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace
# append to the history file, don't overwrite it
shopt -s histappend
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000
# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize
# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color) color_prompt=yes;;
esac
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
fi
fi
if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
PS1='${debian_chroot:+($debian_chroot)}\u#\h:\w\$ '
fi
unset color_prompt force_color_prompt
# If this is an xterm set the title to user#host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u#\h: \w\a\]$PS1"
;;
*)
;;
esac
# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
#alias dir='dir --color=auto'
#alias vdir='vdir --color=auto'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
fi
# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
#if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
# . /etc/bash_completion
#fi
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
Each command runs a separate sub-shell, so the environment variables are not preserved and .bashrc is not sourced (see this answer).
You have to source your script manually in the same process where you run your command so it would be:
CMD source /root/.bashrc && /workspace/launch.sh
provided your launch.sh is an executable.
As per documentation exec form you are using does not invoke a command shell, so it won't work with your .bashrc.
Edit:
BASH wasn't your default shell so
CMD /bin/bash -c "source /root/.bashrc && /workspace/launch.sh"
was needed in order to run your script.
If you want yo set your shell as BASH by default, you can use SHELL instruction as described in documentation, e.g.:
SHELL ["/bin/bash", "-c"]
None of the existing answers accurately answer the title question: Why ~/.bashrc is not executed when run docker container?
There are two things to be aware of:
Use login shell
According to the bash man page:
When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. The --noprofile option may be used when the shell is started to inhibit this behavior.
Therefore, in order to have .profile/.bashrc read automatically upon invocation of bash, it is necessary to invoke bash with the --login or -l option.
You can do this in a couple ways:
1. Set the shell to include -l option. For example,
SHELL ["/bin/bash", "-l", "-c"]
2. Invoke -l for specific commands using the exec form of RUN:
CMD ["/bin/bash", "-l", "-c", "/workspace/launch.sh"]
Note top of .bashrc
From the man page above, we know the order in which profile files are searched and loaded. If you look at /root/.profile you may see something like this:
# ~/.profile: executed by Bourne-compatible login shells.
if [ "$BASH" ]; then
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
fi
mesg n 2> /dev/null || true
This is how ~/.bashrc gets source for a bash shell. Therefore, we can expect ~/.bashrc to be sourced when the bash shell is used.
However, look carefully near the top of your .bashrc file:
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
This means that effectively the remaining contents of .bashrc are ignored except for interactive shells.
One answer suggests using the -i option of bash to invoke an interactive shell. This does work because the environment variable PS1 is set for interactive shells, and therefore .bashrc continues.
However, perhaps you don't want an interactive shell. In this case, there are a few options:
1. Comment out the return line. You can use something like this in your Dockerfile:
RUN sed -e '/[ -z "$PS1" ] && return/s/^/#/g' -i /root/.bashrc
This modification to .bashrc will prevent its early exit from non-interactive invocations.
2. Move the nvm setup to .profile. Move the last three lines of your .bashrc file to .profile so they're executed unconditionally.
3. Manually source .bashrc. As other answers have already noted, you can certainly manually source .bashrc as needed, as in,
RUN source /root/.bashrc && /workspace/launch.sh
Observe that much of the content of .bashrc makes the most sense for interactive shells and is usually unnecessary otherwise, which may make option 2 above the most appealing.
with CMD and shell form
CMD /bin/bash -i "/workspace/launch.sh"
Edit
should also work with ENTRYPOINT and and using exec form using
ENTRYPOINT ["bash","-i","/workspace/entrypoint.sh"]
I believe the -i flag works in the intended way, the .bashrc file is used as intended, the other solutions did not work for me, the .bashrc file was never used
solution may not be ideal for everyone, with the -i flag the program may prompt for user interaction
ps: I used docker create and docker start -i "container name"
You can add source /path/to/bashrc in launch.sh and change the CMD to the following instead of changing to bash through CMD itself:
CMD ["/workspace/launch.sh"]
Alternatively, You can do the following in your Dockerfile instead of depending on bashrc
ENV NVM_DIR /root/.nvm
ENV NODE_VERSION 7.9.0
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules #Ensure that this is the actual path
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
RUN . $NVM_DIR/nvm.sh && \
nvm install $NODE_VERSION && npm install -g npm#5.6.0

How can I use the root password provided by the user in my bash installation script

I'm trying to install a cronjob to run a bash shell script on a relative's machine. They will run the install and I can't access it remotely yet (that's what my script is for - but that's not the issue here). I use kdialog to request their root password and then want to use that to sudo various commands. My code below is failing by a) revealing the root p/w on the terminal and b) failing to pipe it to the various sudos. Help?
#!/bin/bash
kdialog --password "Please enter your root password to install theCronScript.sh and set up cron"
# Sanity checks =========================================╕
if test -z "$BASH" ; then
printf "$SCRIPT:$LINENO: please run this script with the BASH shell\n">&2
exit 192
fi
#========================================================╛
# Global variables=======================================╕
PW="$?"
THISDIR="$(pwd)"
GETIPFILE='theCronScript.sh'
CRONPERIOD='/15 * * * * '
TARGETCRONDIR='/etc/cron.hourly'
#========================================================╛
echo "hi"
# txt file exists check =================================╕
echo "Checking:"
if [ ! -f "$THISDIR/$GETIPFILE" ]; then #there's no file to install
kdialog --msgbox "I cannot find $GETIPFILE to upload\nPlease check attachments in recent e-mails from Greg and download $GETIPFILE to $THISDIR"
exit
else
if [ -f "$TARGETCRONDIR/$GETIPFILE" ]; then #the target already exists
kdialog --title "Replace or Keep" --warningyesno "A similar file already exists.\n Do you want to replace it (recommended)?\n(The original file will be saved with a different name _OLD)"
if [ $? = 0 ]; then # rename, then replace the existing file
#echo $PW is probably unneccessary beyond the first use but just in case...
RNGETIPFILE=$GETIPFILE'_OLD'
echo $PW | sudo -S mv $TARGETCRONDIR/$GETIPFILE $TARGETCRONDIR/$RNGETIPFILE #rename original file
echo $PW | sudo -S cp $THISDIR/$GETIPFILE $TARGETCRONDIR/$GETIPFILE #copy new version in
echo $PW | sudo -S chmod +x $TARGETCRONDIR/$GETIPFILE #
echo $PW | sudo -S crontab -l > mycron #write out current crontab
echo $PW | sudo -S echo $CRONPERIOD $TARGETCRONDIR >> mycron #echo new cron into cron file
echo $PW | sudo -S crontab mycron #install new cron file
rm mycron
$PW="" #clear password variable once it's no longer required
else # Don't replace, exit
exit
fi
else # Nothing to replace. Just copy it in
echo $PW | sudo -S "cp $THISDIR/$GETIPFILE $TARGETCRONDIR/$GETIPFILE" #copy new version in
echo $PW | sudo -S chmod +x $TARGETCRONDIR/$GETIPFILE # make sure it's executable
echo $PW | sudo -S crontab -l > mycron #write out current crontab
echo $PW | sudo -S echo $CRONPERIOD $TARGETCRONDIR >> mycron #echo new cron into cron file
echo $PW | sudo -S crontab mycron #install new cron file
rm mycron
$PW="" #clear password variable once it's no longer required
fi
fi
exit 0
#========================================================╛
One option is to ask for the password directly in the sudo command via an external GUI. From the sudo manpage:
-A, --askpass
Normally, if sudo requires a password, it will read it from the user's terminal. If the -A (askpass) option is specified, a (possibly graphical)
helper program is executed to read the user's password and output the password to the standard output. If the SUDO_ASKPASS environment variable is
set, it specifies the path to the helper program. Otherwise, if sudo.conf(5) contains a line specifying the askpass program, that value will be
used. For example:
# Path to askpass helper program
Path askpass /usr/X11R6/bin/ssh-askpass
If no askpass program is available, sudo will exit with an error.
if sudo is caching credentials, it will only ask for this password once. One way I use to get this cached would be, having no side effects other than caching the password:
export SUDO_ASKPASS=/usr/bin/ssh-askpass
sudo --askpass true
Depending on what distro you're running, ssh-askpass may be somewhere else. There is an example on StackExchange on how you might use kdialog to get the password for the sudo askpass. For reference, here is the script:
$ cat myaskpass.sh
#!/bin/bash
kdialog --password "Please enter your password: "
exit 0
And how you would use it:
export SUDO_ASKPASS=/path/to/myaskpass.sh
sudo --askpass true

Read response and if then else loop

I have this piece of rudementary code in my .bash_profile that loads on login, but I can't get it working. Probably some easy fix, but I', staring my self blind on it right now.
The code:
# Simple backup when editing files with nano
function bu() {
read -p "Backup >>"`basename $1`"<< b4 edit [Y/n]?" response
echo $response
response=$response${response,,} # tolower
if [[ $response =~ ^(yes|y| ) ]]; then
mkdir -p ~/.backup
#cp -v "$1" ~/.backup/`basename $1`-`date +%Y%m%d%H%M`.backup
cp "$1" ~/.backup/`basename $1`-`date +%Y%m%d%H%M`.backup
echo ~/.backup/`basename $1`-`date +%Y%m%d%H%M`.backup >> ~/.backup/bu_log.txt
nano "$1"
else
nano "$1"
fi
}
And it has an alias nano="bu"
so, when i write nano, it should ask me if i want to backup the file first (on yes) or just open it in nano straight away.
The only thing that happens now is that it keeps asking the question and looping, newer goes to nano.
CentOS is the linux
Since nano is an alias to bu, typing nano runs your function, which calls nano, which is an alias to bu, which calls nano, ...
In order to run the actual nano editor, you need to disable alias expansion for that call. Use the command built-in:
command nano "$1"
You are calling nano recursivly, since you aliased nano=bu
so try to change the line in the script
nano "$1"
to the full path of
/usr/bin/nano "$1"
(or where nano is installed on your system)
I think you want:
response=${response,,}
You have
response=$response${response,,}
which gets you response=Yy. That won't match your regular expression.
You could also just do shopt -s nocasematch.
Aliases are usually trouble. The rule is "if in doubt, use a function."
nano() {
bu "$#"
}
bu() ( # Use a subshell to avoid having to reset shell options
shopt -s nocasematch
local base=${1##*/}
read -p "Backup >>${base}<< b4 edit [Y/n]?" response
case $response in
y*)
mkdir -p ~/.backup
local backup=~/.backup/"${base}-$(date +%Y%m%d%H%M`).backup"
cp "$1" "$backup"
echo "$backup" >> ~/.backup/bu_log.txt
;;
esac
command nano "$#" # Use "$#" to allow you to pass more than one argument to nano
)

Resources