4
\$\begingroup\$

I wrote a bash script for the Raspberry Pi 3 (Raspbian) which has the main task of setting up a LAN to WLAN router. In addition it makes some things nicer for the intended users who have Windows background only. It will be used on a Pi 3 with built-in WLAN and the official 7" display only.

Preconditions to run the script are (typically done on Windows, so my script can't help here):

  • download latest Raspbian
  • flash to SD card
  • copy 3 files to the /boot partition: the script, a file asplash and splash.png
  • insert in Pi and run Raspbian
  • configure an Internet connection
  • run the script (/boot/script.sh)

The script shall

  • update the sources (apt-get update)
  • upgrade the installation (apt-get upgrade)
  • install required packages to make all of the following work
  • set keyboard to German
  • set timezone to Berlin
  • forward IPv4 from LAN (eth0) to WLAN (wlan0)
  • configure LAN (eth0) to 192.168.63.1 so it can act as a gateway on the 192.168.63.0/24 network
  • replace the Raspberry boot output by a nice logo so that the user knows it'll be the right thing on startup
  • set the Desktop background by the same nice logo

The order of those things is not really important, but it seems that the Ethernet configuration should only be done when no Internet access is needed any more, since the script might disable Internet access if the original connection was made over LAN.

Yeah, that's it. My tests seem ok, but since this is my first larger bash script, I'd like to get some feedback on how to improve things in the future.

#!/bin/sh
echo "Setting up this device as a LAN to WLAN gateway..."
[ -f /boot/splash.png ] && echo "Splash screen found" || { echo "Please copy splash.png"; exit 1; }
[ -f /boot/asplash ] && echo "Splash screen script found" || { echo "Please copy a file called asplash"; exit 1; }
ping 8.8.8.8 -c 1 -q -w 1 > /dev/null && echo "Internet connection detected" || { echo "Make sure you have a valid Internet connection." ; exit 1; }
echo ""

# Localization --------------------------------------------------------
# Configure timezone to MESZ
# Adjust keyboard to typical German layout
echo "Setting timzone to Europe/Berlin..."
sudo timedatectl set-timezone Europe/Berlin

echo "Setting keyboard to German..."
sudo echo "XKBMODEL=\"pc105\"" | sudo tee /etc/default/keyboard > /dev/null
sudo echo "XKBLAYOUT=\"de\"" | sudo tee --append /etc/default/keyboard > /dev/null
sudo echo "XKBVARIANT=\"deadacute\"" | sudo tee --append /etc/default/keyboard > /dev/null
sudo echo "XKBOPTIONS=\"lv3:ralt_switch,terminate:ctrl_alt_bksp\"" | sudo tee --append /etc/default/keyboard > /dev/null
sudo echo "BACKSPACE=\"guess\"" | sudo tee --append /etc/default/keyboard > /dev/null

# Update to latest versions ------------------------------------------
echo "Updating packages..."
sudo apt-get update
sudo apt-get --yes upgrade

# Install necessary and useful packages -------------------------------
# DNS utilities for Internet diagnosis
sudo apt-get install dnsutils
# Frame buffer viewer For startup splashscreen
sudo apt-get install fbi

# Set up IP Forwarding ------------------------------------------------
# Find net.ipv4.ip_forward and (s)ubstitute it
echo "Setting up IP forwarding..."
sudo sed -i -e '/net.ipv4.ip_forward/ s/.*/net.ipv4.ip_forward=1/' /etc/sysctl.conf
# Set up NAT to route out on WLAN
sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
# Make those settings persistent for a restart
mkdir /home/pi/router
sudo iptables-save > /home/pi/router/iptables.tbl
sudo echo "#!/bin/sh" | sudo tee /etc/network/if-pre-up.d/iptables > /dev/null
sudo echo "iptables-restore < /home/pi/router/iptables.tbl" | sudo tee --append /etc/network/if-pre-up.d/iptables > /dev/null
sudo echo "exit 0" | sudo tee --append /etc/network/if-pre-up.d/iptables > /dev/null
sudo chown root:root /etc/network/if-pre-up.d/iptables
sudo chmod +x        /etc/network/if-pre-up.d/iptables
sudo chmod 755       /etc/network/if-pre-up.d/iptables

# Set up local network (test net) -------------------------------------
# The LAN interface (eth0) gets a static IP.
# Metric is important, since usually the LAN has lower metric and
# therefore gets a higher precedence. This is exactly what we don't 
# want here: traffic must go out on WLAN
echo "Configuring LAN test net to 192.168.63.x ..."
IP=192.168.63.1
sudo echo "interface eth0"                 | sudo tee --append /etc/dhcpcd.conf > /dev/null
sudo echo "metric 300"                     | sudo tee --append /etc/dhcpcd.conf > /dev/null
sudo echo "static ip_address=$IP/24"       | sudo tee --append /etc/dhcpcd.conf > /dev/null
sudo echo "static routers=$IP"             | sudo tee --append /etc/dhcpcd.conf > /dev/null
sudo echo "static domain_name_servers=$IP" | sudo tee --append /etc/dhcpcd.conf > /dev/null
sudo echo ""                 | sudo tee --append /etc/dhcpcd.conf > /dev/null
sudo echo "interface wlan0"                | sudo tee --append /etc/dhcpcd.conf > /dev/null
sudo echo "metric 200"                     | sudo tee --append /etc/dhcpcd.conf > /dev/null
sudo systemctl enable dhcpcd
sudo service dhcpcd start
echo "Configuring hostname to 'gateway'..."
sudo echo "gateway" | sudo tee /etc/hostname > /dev/null

# Make booting a bit nicer -------------------------------------------
# Remove the Raspberry logos and the text
# First create a backup copy if it does not exist yet
echo "Removing noise when booting..."
[ -f /boot/cmdline.txt.orig ] || sudo cp /boot/cmdline.txt /boot/cmdline.txt.orig
sudo sed -e '/rootwait/ s/\(.* rootwait\).*/\1 console=tty3 logo.nologo loglevel=3 vt.global_cursor_default=0/' /boot/cmdline.txt.orig | sudo tee /boot/cmdline.txt > /dev/null
# Copy splash screen and script
sudo cp /boot/splash.png /etc/
sudo chmod 644 /etc/splash.png
sudo cp /boot/asplash /etc/init.d/asplash
sudo chmod a+x /etc/init.d/asplash
sudo insserv /etc/init.d/asplash

# Change Desktop background --------------------
echo "Setting Desktop background ..."
sudo cp /boot/splash.png /home/pi/router/
sudo chmod 644 /home/pi/router/splash.png
sudo sed -i -e '/wallpaper=/ s/wallpaper=.*/wallpaper=\/home\/pi\/router\/splash.png/' /home/pi/.config/pcmanfm/LXDE-pi/desktop-items-0.conf
\$\endgroup\$
  • \$\begingroup\$ Maybe I was too optimistic saying it works. Might not work on a really clean system where apt-get upgrade was not run before. Running the script, rebooting and running again fixes that. grml \$\endgroup\$ – Thomas Weller Aug 30 '16 at 22:12
  • \$\begingroup\$ Please note that StackExchange already keeps an edit history for every post. It's not necessary to keep a separate edit protocol in the post. Such meta information is unnecessary and I've removed it :) \$\endgroup\$ – Vogel612 Aug 30 '16 at 22:25
  • \$\begingroup\$ @Vogel612: I wanted to make it more visible for the one who might be working on it (who starred it) so that he does not report a bug where there's none any more. \$\endgroup\$ – Thomas Weller Aug 30 '16 at 22:26
  • 1
    \$\begingroup\$ An alternative approach for the whole thing: PiBakery not entirely sure it fits all your needs, but it might \$\endgroup\$ – Arsenal Sep 7 '16 at 8:17
  • 1
    \$\begingroup\$ @Mawg: I wasn't very familiar with Python at that time. And I'm still not when it comes to modifying system settings and installing packages. Which Python package can do that? How can that be cross platform? Debian uses apt-get, Suse uses yast, right? While possible, I can hardly believe that Python can do it on both. \$\endgroup\$ – Thomas Weller Aug 28 '18 at 7:48
4
\$\begingroup\$

Creating files

For the record, the single biggest issue in this script is what @Sirac already mentioned: use here-documents instead of multiple echo statements to create a file. It's simpler, easier to read, and more efficient.

sudo everywhere

This script has no use as a normal user. All the operations it provides only make sense with root access. As such, instead of littering it with sudo everywhere, it would be better to drop all the sudo, and instead run this script itself with sudo.

Don't use sudo willy-nilly

Using sudo when it's not needed creates the bad habit of using it more than necessary, which defeats the purpose, and can lead to disaster. For example here:

sudo echo "XKBMODEL=\"pc105\"" | sudo tee /etc/default/keyboard > /dev/null

No need to echo text as root. Only the tee needs to be run as root.

Avoid too long lines

On this line it's important that the script will exit if the condition fails, but I have to scroll to the right to see that:

[ -f /boot/splash.png ] && echo "Splash screen found" || { echo "Please copy splash.png"; exit 1; }

It would be better to break the line using \.

But in this particular example, since you have multiple similar statements, I'd introduce a helper function:

fatal() {
    echo "$*"
    exit 1
}

[ -f /boot/splash.png ] && echo "Splash screen found" || fatal "Please copy splash.png"

When written this way, even though I don't see the end of the line without scrolling, I see "fatal", from which I can guess that there's not much interesting to see to scroll to the right, and I can simply read on to the next line.

Simplifying sed

Some of the sed commands can be written simpler:

sudo sed -i -e '/net.ipv4.ip_forward/ s/.*/net.ipv4.ip_forward=1/' /etc/sysctl.conf

This is equivalent to:

sudo sed -i -e 's/.*net.ipv4.ip_forward.*/net.ipv4.ip_forward=1/' /etc/sysctl.conf

You can avoid the duplication by using capture groups and a back reference:

sudo sed -i -e 's/.*\(net.ipv4.ip_forward\).*/\1=1/' /etc/sysctl.conf

Redundant commands

The first chmod here is unnecessary:

sudo chmod +x        /etc/network/if-pre-up.d/iptables
sudo chmod 755       /etc/network/if-pre-up.d/iptables

The second one will set the executable flag for all, so you can simply delete the first line.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the great feedback. "No need to echo text as root" - that's a relict from sudo echo > when I didn't use tee yet. Nice finding. "net.ipv4.ip_forward (not commented out)" - by default it is already set to 1 but commented out. In that case, removing the # is the intended thing to do, otherwise the setting will not be applied. \$\endgroup\$ – Thomas Weller Aug 31 '16 at 20:17
  • \$\begingroup\$ @ThomasWeller I see your point about replacing the net.ipv4.ip_forward, I dropped one of my objections accordingly \$\endgroup\$ – janos Aug 31 '16 at 21:16
2
\$\begingroup\$

I will only comment on one thing, that is writing multiple lines to a file. You can easily do this:

sudo tee yourfilehere >/dev/null <<EOF
line1
line2
line3
line4
EOF

You can use any other word instead of EOF. Also, if you only want to append, use the -a flag. Note that lines between the EOF's don't need "s or 's.

\$\endgroup\$
  • 1
    \$\begingroup\$ sudo will only affect cat, not the redirection, so you need tee. Does it work with tee as well? \$\endgroup\$ – Thomas Weller Aug 30 '16 at 20:59
  • \$\begingroup\$ I'm now only 99% sure, so I will test it. I think where the process can write is up to the process which got root-privileges. \$\endgroup\$ – Sirac Aug 30 '16 at 21:06
  • \$\begingroup\$ @ThomasWeller I just realized it is not possible, your tee approach just works fine, the EOF-way makes it just a bit more readable. As noted you can write the file content between the EOFs. Shell variables will be resolved unless you write <<'EOF' instead of <<EOF \$\endgroup\$ – Sirac Aug 30 '16 at 21:17
  • \$\begingroup\$ Thanks :) and have fun working with you raspberry pi. I got one of those too, so I know how time consuming the can be. Btw, have you considered to store the files you need online or deliver them with your script instead of writing them in your bash file? \$\endgroup\$ – Sirac Aug 30 '16 at 21:26
  • \$\begingroup\$ Some next steps are planned: a) make a minimal script that downloads the real script and the files from Internet b) offer DHCP on the LAN c) Have a live overview of devices connected (probably in Python not bash) \$\endgroup\$ – Thomas Weller Aug 30 '16 at 21:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.