CategoryBash

Locally remount volumes from Docker to be used by local user using bindfs

#!/bin/bash
set -exou pipefail

# Location of the script (not the location from where it is executed from
THISDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

DOCKER_VOLUME_DIR=volumes  # This is the directory docker mounts to
LOCAL_DOCKER_VOLUME_DIR=localvolumes # This is the directory you want to locally mount to.
INSIDE_CONTAINER_USER=1000

# Bindfs is required
APP=bindfs; [ -x "`which ${APP}`" ] || sudo apt install ${APP}

# Create local directory to map volume to.
[ ! -d ${THISDIR}/${LOCAL_DOCKER_VOLUME_DIR} ] && mkdir -p ${THISDIR}${LOCAL_DOCKER_VOLUME_DIR}

# Unmount if already mounted
sudo umount ${THISDIR}/${LOCAL_DOCKER_VOLUME_DIR} || true

# Bet local users group
GROUP=`id -g -n $USER`

# Mount
sudo bindfs -u $USER -g "$GROUP" --create-for-user=${INSIDE_CONTAINER_USER} --create-for-group=${INSIDE_CONTAINER_USER} ${THISDIR}/${DOCKER_VOLUME_DIR} ${THISDIR}/${LOCAL_DOCKER_VOLUME_DIR}

Based on https://www.fullstaq.com/knowledge-hub/blogs/docker-and-the-host-filesystem-owner-matching-problem

Bash cheatsheet!

Just a collection of oneliners often use, but always forget 🙂 The following can be used from the terminal, or be used as a script.

#!/bin/bash
# ^^ Always start with a shebang!

# -e = immediately exit if any command has non-zero exit status
# -o pipefail = prevents masking of errors in a pipeline
# -u = error when using reference that has not been defined 
set -eou pipefail

# Check if directory is present before creating to avoid annoying warning.
[ ! -d /tmp/whatever ] && mkdir -p /tmp/whatever

# Check if file exists before performing operation on it.
[ -f file.txt ] && echo "blah" >> file.txt

# Check that script is NOT run as root
[[ $EUID -ne 0 ]] || {
    echo "[ERROR]     Do not run as root!"
    exit 1
}

# Check that script is run as root
[[ $EUID -ne 0 ]] && {
    echo "[ERROR]     This script must be run as root"
}

# Location of the script (not the location from where it is executed from
THISDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

# Grep, but do not match grep itself -> put first character of grep match in square brackets []. Suppose we want to see if blender is still running:
ps ax | grep [b]lender

# Kill application by name (when pkill is absent ;) ) Suppose we want to kill "my-awesome-app" 
ps ax | grep -i [m]y-awesome-app | awk '{ print $2 }'

# Install only if not installed.
APP=bindfs; [ -x "`which ${APP}`" ] || sudo apt install ${APP}

# Use default if first argument on command line is not set.
FIRST_ARG="${1:-some_default_var}"  

# Get the amount of CPUs on the device (-1, to use for parallel jobs)
cpus=$(($(nproc) -1))
# Or
cpus=$(($(grep -c "^processor" /proc/cpuinfo) - 1))

Replace variables from file using sed

Some configuration files are dependent on where the location of you location is. Therefore I mostly have variables in the configuration files, that I replace upon installation using sed.

As an example I have a systemd service file (example.service):

[Unit]
Description=My app

[Service]
WorkingDirectory={{WORKING_DIR}}
ExecStart=/usr/bin/python3 {{WORKING_DIR}}/app.sh
Restart=always

[Install]
WantedBy=multi-user.target

Then run the following on this file:

sed "s,{{WORKING_DIR}},/location/to/application/dir,g" example.service > /etc/systemd/system/example.service

Then the service file is installed and the variables are replaced with the path to your liking.

Useful one-liners

https://gist.github.com/johnnypea/b0cd77e5734d65691fa21d93274b305b

Mounting USB stick systemd

udev rules 99-usb-mount-rules

# Warning this mounts only the first partition to the defined place, assuming first partition is 1
KERNEL=="sd[a-z]*1", SUBSYSTEMS=="usb", ACTION=="add", RUN+="/bin/systemctl start automount@%k.service"
KERNEL=="sd[a-z]*1", SUBSYSTEMS=="usb", ACTION=="remove", RUN+="/bin/systemctl stop automount@%k.service"

Add systemd service: automount@.service

[Unit]
Description=Automount usb drive to %i

[Service]
Type=oneshot
RemainAfterExit=true

Environment="MOUNTPOINT=/media/user_data/EXTERNAL"

ExecStart=/usr/bin/mount-usb-drive.sh add %i
ExecStop=/usr/bin/mount-usb-drive.sh remove %i

Add script to filesystem that actually mounts the usb drive:

#!/bin/bash

# First parameter indicates to add or remove drive.
# last parameter indicates the device name.

WATCHDIR="/tmp/swuwatchdir"
mkdir -p ${WATCHDIR}

usage()
{
    echo "Usage: $0 {add|remove} device_name (e.g. sdb1)"
    exit 1
}

function do_mount()
{
    check_mount_state

    # Get formatting of the drive. TYPE
    # "blkid -o udev" option does not work for busybox version of blkid.
    eval $(/sbin/blkid ${DEVICE} | tr " " "\n" | tail -n 1)

    echo "Mounting ${DEVICE} of type ${TYPE} to ${MOUNTPOINT}"

    /bin/mkdir -p ${MOUNTPOINT}

    # Global mount options
    OPTS="rw,relatime,sync"

    # File system type specific mount options
    if [[ ${TYPE} == "vfat" ]]; then
        OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush"
    fi

    if ! /bin/mount -o ${OPTS} ${DEVICE} ${MOUNTPOINT}; then
        echo "Error mounting ${DEVICE} (status = $?)"
        /bin/rmdir ${MOUNTPOINT}
        exit 1
    fi

    # Place dummy file in MOUNTPOINT/../mountWatch to not
    # have to watch the entire user directory.
    touch ${WATCHDIR}/asdf
    echo "**** Mounted ${DEVICE} at ${MOUNTPOINT} ****"
}

function do_unmount()
{
    if [[ ! -d ${MOUNTPOINT} ]]; then
        echo "Warning: ${DEVICE} is not mounted"
    else
        /bin/umount -l ${DEVICE}
        echo "**** Unmounted ${DEVICE}"
    fi


    rm ${WATCHDIR}/asdf
    /bin/rmdir ${MOUNTPOINT}
}

function check_mount_state() {
    # See if this drive is already mounted, and if so where
    MOUNTED_TO=$(/bin/mount | /bin/grep ${DEVICE} | /usr/bin/awk '{ print $3 }')
    if [[ -n ${MOUNTED_TO} ]]; then
        echo "Warning: ${DEVICE} is already mounted at ${MOUNTED_TO}"
        echo "We only expect one USB drive to be inserted at a time."
        exit 1
    fi
}

function main() {

    if [[ $# -ne 2 ]]; then
        usage
    fi

    ACTION=$1
    DEVBASE=$2
    DEVICE="/dev/${DEVBASE}"

    # Var MOUNTPOINT should be set by the systemd script. Check it.
    if [[ -z ${MOUNTPOINT} ]]; then
        echo "MOUNTPOINT is not set by systemd script."
        exit 1
    fi

    case "${ACTION}" in
        add)
            do_mount
            ;;
        remove)
            do_unmount
            ;;
        *)
            usage
            ;;
    esac
}

main $@

Based on: https://serverfault.com/questions/766506/automount-usb-drives-with-systemd

Get status of git repositories in directory

I have a development directory (~/dev) which houses all of my git repositories.
Recently I bought another laptop, but to make sure I pushed all my local development I check the status of the git repositories with the following bash script:

#!/bin/bash

gitrepos=`find . -type d -name ".git" -prune -print | xargs -I {} dirname {}`

for repo in $gitrepos; do
  pushd $PWD > /dev/null
  cd $repo
  # echo $PWD
  if git diff-index --quiet HEAD --; then
    echo -e "UP TO DATE\t - $repo"
  else
    echo -e "CHANGES\t\t - $repo"
  fi
  popd > /dev/null
done

Place the code above in a file e.g. repostatus.sh and make it executable: chmod +x repostatus.sh.

Programatically switch tabs in a browser

So, if been looking to programatically switch tabs of a browser in kiosk mode. This is a quick way to do exactly that.

So first you have to install xdotool. I did this on a Ubuntu machine using apt:

$ sudo apt install xdotool

Then put the following in a file and make that executable (chmod +x yourscript.sh):

#!/bin/bash

BROWSER="Brave"
direction=$1

action="Ctrl+Tab"
if [[ "${direction}" == "left" ]]; then
  action="Ctrl+Shift+Tab"
fi

xdotool windowactivate --sync $(xdotool search --name ${BROWSER} | tail -n 1) && xdotool key ${action}

Execute it with an argument “left” or “right” (right = default). On execution it will locate your browser (“Brave”), then it emulates a shortcut to go to the next or previous tab.

The goals is to fire this script once a button has been pressed on a Kiosk such that the user can choose which tab to show.

Mount docker volume as same user as on host machine

docker containers often run as the root user (uid = 0, guid = 0). Files that the users generates are therefore also owned by the root user.

By creating an extra user on the docker system, and giving that user the same uid and guid as the user on you host system you will be able to modify your files without the need to be root on you host machine.

The following assumes you created an additional user in your docker container, and that it got 1000 for uid and guid.

#!/bin/bash

# Mr. R
# 06-2020

args=$@
cmd="builder.sh ${args}"

# current working directory is a volume mount to something on the host system.
# Therefore stat -c will provide the uid and gid of the host user.
# setting this uid and gid for the container user results in files written to the
# volume as host user.
usr=`id -nu 1000`
grp=`id -ng 1000`
groupmod -g $(stat -c "%g" .) $grp
usermod -u $(stat -c "%u" .) -g $(stat -c "%g" .) $usr

# Force all volume mounts to be of the appuser!
chown -R ${usr}:${grp} ${WORKDIR}

# If nothing given, start a shell, else run the builder script with arguments.
if [ "x${args}x" == "xx" ]; then
  cmd="/bin/bash"
fi

su -m -c "PATH=${PATH}; ${cmd}" ${usr}

Ping addresses on a subnet (no nmap)

If you are on a remote host, and you have not the rights to install any other software, then this might come in handy.

Normally I would use “nmap” to scan for other devices on the network, but if that is not available and “ping” is, then this can also discover online devices on the network, assuming they are pingable (ICMP enabled).

#!/bin/bash
# Ping devices on a subnet

SUBNET=${1:-"192.168.2"}

function main()
{
  for i in $SUBNET.{1..254}
  do
    check_alive $i &
  done

  # Sleep to not put shell inbetween
  sleep 1
  exit 0
}

function check_alive()
{
  ping -c 1 $1 > /dev/null
  [ $? -eq 0 ] && echo -e "$i\t UP"
}

main $@

The output will look like the following:

⇒  ./ping-it.sh 192.168.2
192.168.2.69	 UP
192.168.2.62	 UP
192.168.2.254	 UP
192.168.2.14	 UP
192.168.2.65	 UP
192.168.2.182	 UP
192.168.2.25	 UP
192.168.2.200	 UP
192.168.2.210	 UP
192.168.2.190	 UP

Meaning of “man” numbers (e.g. mkfifo(3)

I always forget what the number after the man pages means.
For example mkfifo(3) (https://linux.die.net/man/3/mkfifo).

This post helps me (and you) to not forget 🙂

MANUAL SECTIONS
    The standard sections of the manual include:

    1      User Commands
    2      System Calls
    3      C Library Functions
    4      Devices and Special Files
    5      File Formats and Conventions
    6      Games et. al.
    7      Miscellanea
    8      System Administration tools and Daemons

    Distributions customize the manual section to their specifics,
    which often include additional sections.

Note: I found out that “man” has a man page of itself. So this is not really necessary anymore. I keep it here for others to find though.

But remember:

$ man man

© 2025 Roholt

Thema door Anders NorénOmhoog ↑