Services >> Terminal: Guest Account and Auto-Login, Set up sudo

{ EDIT: Updated to create the guest account in persistent storage. /var (used previously) is a symlink to /tmp, which is wiped on reboot. }

Having to log-in to the Services >> Terminal each time you start it is a pain. But bypassing that password authentication opens a major security hole.

By default, using /bin/login -f root in luci-app-ttyd (under Services >> Terminal >> Config >> Command field) creates a major local security hole: anyone on your LAN can bypass LuCI entirely, point their browser to http://<router_ip>:7681, and drop straight into an unauthenticated root shell.

Let’s fix both of those problems.

Create a persistent directory and initialize the login profile file:

mkdir -p /home/guest
touch /home/guest/.profile

Secure the folder so the guest account cannot delete or alter its login profile

chown -R root:root /home/guest
chown root:root /home/guest/.profile
chmod 755 /home/guest
chmod 644 /home/guest/.profile

To ensure the /home/guest/.profile file survives a firmware update, go to System >> Backup / Flash Firmware >> Configuration tab, enter:

/home/guest/
/home/guest/.profile

… into the list, then click the Save button at bottom-right.

Create a guest user:

echo "guest:x:1001:1001:guest:/home/guest:/bin/ash" >> /etc/passwd

Register the User Group:

echo "guest:x:1001:" >> /etc/group

Set the guest account password:

passwd guest

Follow the prompts to enter the password for the guest account.

Under System >> Software, install sudo.

Grant sudo permissions (with password use) to the guest user:

echo "guest ALL=(ALL) ALL" > /etc/sudoers.d/guest

Secure the Permissions File:

chmod 0440 /etc/sudoers.d/guest

Set up auto-login to the guest account: Under Services >> Terminal >> Config >> Command field … enter:

/bin/login -f guest

Make the changes survive firmware updates:

echo "/home/guest/" >> /etc/sysupgrade.conf

Now, when you start Terminal, it will auto-login to the guest account.

If you try to do anything that requires sudo, such as:

sudo uci show network

… it will prompt you for the guest account password. Sudo remembers the password for a period of time, so you can then perform further sudo actions without having to re-enter the guest password.

If you need to do sudo work, you can type:

sudo -i

… enter the guest account password, and when you’re done, type:

exit

… which will drop you back to the guest account.

How It Works in Practice

Auto-Login: When you open the LuCI Terminal page, it will drop you straight into a command prompt (under the guest account) instantly without prompting for a username or password. Running whoami (if you’ve got that package installed) will show you are guest.

Safe LAN Exposure: If anyone snoops on port 7681 across your local network, they are entirely locked inside a restricted user environment with no rights to alter firewall rules, Wi-Fi configs, or packages.

Elevating Privileges: When you need to run an admin command, prefix it with sudo or drop to a root shell with sudo -i.

DropBear SSH and LuCI Terminal: The guest account (and associated guest password) work for both.

{ EDIT: updated to make the changes persist across reboots

/var is a symlink directly into the router’s volatile RAM (/tmp)

If you implemented the previous script, you don’t have to worry about cleaning up the old script… it’s flushed on router reboot. }

{ EDIT 2: updated script to include CPU Load monitoring }

{ EDIT 3: updated script to print out to 3 decimal places }

{ EDIT 4: updated script to lower CPU utilization, and to also display temperature in Kelvin }

{ EDIT 5: implemented a guest account sleep timeout when loading, to allow the guest account to attach to the shell }

{ EDIT 6: Replaced the guest account sleep timeout with a function that checks whether the terminal is ready to receive commands before proceeding }

{ EDIT 7: Implemented an alternative script for /home/guest/.profile, and colored text }

{ EDIT 8: Updated the alternative script for /home/guest/.profile to make it more secure. Now the guest account cannot run background processes, and foreground processes other than sudo are killed }

{ EDIT 9: Updated /etc/motd-temp.sh script to include the 2.4GHz and 5GHz chip temperatures }

In Services >> Terminal, drop into a sudo shell (enter sudo -i, enter guest password), then paste this script in and press Enter:

cat << 'EOF' > /etc/motd-temp.sh
#!/bin/sh
clear

# TRAP: Restore cursor and clean exit on Ctrl+C
trap 'printf "\033[?25h\n\n"; clear; trap - INT; return 2> /dev/null || exit' INT

# Helper function to get CPU ticks entirely in native shell memory
get_cpu_ticks() {
    read -r _ user nice sys idle iowait irq softirq steal guest guest_nice < /proc/stat

    # Native shell math
    total=$((user + nice + sys + idle + iowait + irq + softirq + steal + guest + guest_nice))
    non_idle=$((user + nice + sys + irq + softirq + steal))

    echo "$total $non_idle"
}

# Dynamic mapping helper to locate hwmon paths
get_wifi_temp_path() {
    # Searches for temp1_input under the specified phy path
    for path in /sys/class/ieee80211/$1/hwmon*/temp1_input; do
        if [ -f "$path" ]; then
            echo "$path"
            return
        fi
    done
}

# Resolve Wi-Fi thermal paths once at startup
PHY0_PATH=$(get_wifi_temp_path "phy0")
PHY1_PATH=$(get_wifi_temp_path "phy1")

# Initial snapshot
PREV_STATS=$(get_cpu_ticks)
PREV_TOTAL=${PREV_STATS% *}
PREV_NON_IDLE=${PREV_STATS#* }

while true; do
    # Read core CPU temperature directly into a shell variable
    read -r TEMP_RAW < /sys/class/thermal/thermal_zone0/temp

    # Read Wi-Fi temperatures, default to 0 if paths fail
    TEMP_WIFI0_RAW=0; [ -n "$PHY0_PATH" ] && read -r TEMP_WIFI0_RAW < "$PHY0_PATH"
    TEMP_WIFI1_RAW=0; [ -n "$PHY1_PATH" ] && read -r TEMP_WIFI1_RAW < "$PHY1_PATH"

    # Format Core CPU Celsius fixed-point decimals
    TEMP_C_INT=$((TEMP_RAW / 1000))
    TEMP_C_DEC=$((TEMP_RAW % 1000))
    TEMP_C=$(printf "%d.%03d" "$TEMP_C_INT" "$TEMP_C_DEC")

    # Format Wi-Fi Celsius fixed-point decimals
    WIFI0_C_INT=$((TEMP_WIFI0_RAW / 1000))
    WIFI0_C_DEC=$((TEMP_WIFI0_RAW % 1000))
    WIFI0_C=$(printf "%d.%01d" "$WIFI0_C_INT" $((WIFI0_C_DEC / 10))) # Clean 1-decimal layout

    WIFI1_C_INT=$((TEMP_WIFI1_RAW / 1000))
    WIFI1_C_DEC=$((TEMP_WIFI1_RAW % 1000))
    WIFI1_C=$(printf "%d.%01d" "$WIFI1_C_INT" $((WIFI1_C_DEC / 10))) # Clean 1-decimal layout

    # Calculate Fahrenheit using fixed-point math: (C * 1.8) + 32
    TEMP_F_RAW=$(( (TEMP_RAW * 18 / 10) + 32000 ))
    TEMP_F_INT=$((TEMP_F_RAW / 1000))
    TEMP_F_DEC=$((TEMP_F_RAW % 1000))
    TEMP_F=$(printf "%d.%03d" "$TEMP_F_INT" "$TEMP_F_DEC")

    # Calculate Kelvin using fixed-point math: C + 273.15
    TEMP_K_RAW=$(( TEMP_RAW + 273150 ))
    TEMP_K_INT=$(( TEMP_K_RAW / 1000 ))
    TEMP_K_DEC=$(( TEMP_K_RAW % 1000 ))
    TEMP_K=$(printf "%d.%03d" "$TEMP_K_INT" "$TEMP_K_DEC")

    # CPU load calculations
    CURR_STATS=$(get_cpu_ticks)
    CURR_TOTAL=${CURR_STATS% *}
    CURR_NON_IDLE=${CURR_STATS#* }

    DIFF_TOTAL=$((CURR_TOTAL - PREV_TOTAL))
    DIFF_NON_IDLE=$((CURR_NON_IDLE - PREV_NON_IDLE))

    if [ "$DIFF_TOTAL" -gt 0 ]; then
        # Scale by 100000 to extract a 3-decimal percentage
        CPU_SCALE=$(( (100000 * DIFF_NON_IDLE) / DIFF_TOTAL ))
        CPU_INT=$((CPU_SCALE / 1000))
        CPU_DEC=$((CPU_SCALE % 1000))
        CPU_USAGE=$(printf "%d.%03d" "$CPU_INT" "$CPU_DEC")
     else
        CPU_USAGE="0.000"
    fi

    # Define ANSI Color Escape Codes
    COLOR_GREEN="\033[0;32m"
    COLOR_YELLOW="\033[0;33m"
    COLOR_RED="\033[0;31m"
    COLOR_RESET="\033[0m"

    # Extract the integer value for color determination
    INT_TEMP="${TEMP_C%%.*}"

    # Determine the correct color threshold using the whole number
    if [ "$INT_TEMP" -lt 50 ]; then
        TEMP_COLOR="$COLOR_GREEN"
    elif [ "$INT_TEMP" -le 70 ]; then
        TEMP_COLOR="$COLOR_YELLOW"
    else
        TEMP_COLOR="$COLOR_RED"
    fi

    # Print to terminal screen
    printf "\r\033[?25l\033[KCore: ${TEMP_COLOR}%s°C / %s°F / %sK${COLOR_RESET} | ${TEMP_COLOR}2.4GHz WiFi: %s°C${COLOR_RESET} | ${TEMP_COLOR}5GHz WiFi: %s°C${COLOR_RESET} | CPU Load: %s%%" "$TEMP_C" "$TEMP_F" "$TEMP_K" "$WIFI0_C" "$WIFI1_C" "$CPU_USAGE"

    PREV_TOTAL=$CURR_TOTAL
    PREV_NON_IDLE=$CURR_NON_IDLE

    # Pause for 5 seconds or exit on Enter keypress
    read -t 5 input && break
done

# Cleanup
printf "\033[?25h\n\n"
clear
trap - INT
EOF

You’ll change the trigger temperatures for the color change of the text in this section of the above code:

    # Determine the correct color threshold using the whole number
    if [ "$INT_TEMP" -lt 50 ]; then
        TEMP_COLOR="$COLOR_GREEN"
    elif [ "$INT_TEMP" -le 70 ]; then
        TEMP_COLOR="$COLOR_YELLOW"
    else
        TEMP_COLOR="$COLOR_RED"
    fi

I’ve got it set to show green text below 50 °C, yellow text between 50 °C and 70 °C, and red text above 70 °C.

Ensure the guest account has the ability to run the script:

chmod 755 /etc/motd-temp.sh

Check where the guest user’s account is located:

grep guest /etc/passwd

Which should return something like:

guest:$5$PESZuFcajaWH0Xf4$kpUjgAyv6bwrhFrQTtFj99DPYFlXszpGUdcjXO1BpiB:1001:1001:guest:/home/guest:/bin/ash

Write the execution command and suppress history files to prevent write errors:

cat << 'EOF' > /home/guest/.profile
# Wait for the terminal file descriptor to be fully ready before proceeding
while [ ! -t 1 ]; do sleep 0.1; done
# Clear terminal splash screen
printf "\033[2J\033[H"
# Prevent writing command history
export HISTFILE=/dev/null
# Run temperature / CPU load monitoring script (Enter or Ctrl-C to exit to prompt)
/etc/motd-temp.sh
EOF

If you want a more secure setup which ensures orphaned terminal sessions are killed before a new terminal session starts (to prevent a guest account terminal session from accidentally connecting to an abandoned root terminal session), instead of writing the immediately-above code to /home/guest/.profile, write the below code to /home/guest/.profile instead.

cat << 'EOF' > /home/guest/.profile
# Initialize environment parameters
while [ ! -t 1 ]; do sleep 0.1; done
printf "\033[2J\033[H"
export HISTFILE=/dev/null

# Trap Ctrl+C globally so it refreshes instead of breaking the login shell loop
trap 'echo' INT

# ==========================================================
# Kill lingering orphans on startup
# If an old sudo session somehow managed to survive a browser close,
# kill it immediately when this new session logs in. This prevents
# the new browser terminal session from hijacking the old.
# ==========================================================
if [ -n "$SSH_TTY" ]; then
    pkill -9 -u root -t "$(basename "$SSH_TTY")" 2>/dev/null
fi

while true; do
    # Start the temperature / CPU load monitoring script
    /etc/motd-temp.sh

    while true; do
        printf "guest@OpenWrt:\$ "

        if ! read -t 60 cmd; then
            # Kill background forks spawned by guest account
            kill -9 $(jobs -p) 2>/dev/null
            cmd=""
            sleep 2
            break
        fi

        case "$cmd" in
            exit)
                exit 0
                ;;
            clear)
                printf "\033[2J\033[H"
                continue
                ;;
            "")
                continue
                ;;
        esac

        if [ -n "$cmd" ]; then
            case "$cmd" in
                sudo*)
                    # Allow administrative commands to run natively in the foreground
                    eval "$cmd"
                    ;;
                *)
                    # Force all other guest commands into the background with an ampersand (&)
                    # and silence their output so they don't break the prompt layout.
                    eval "$cmd" >/dev/null 2>&1 &
                    ;;
            esac
        fi
    done

    # Clear the screen before reloading the terminal
    printf "\033[2J\033[H"
done
EOF

The above script times out the guest account after 60 seconds of inactivity, then reloads the temperature / CPU load monitoring script.

The above code assumes you’ve named your guest account ‘guest’. Change the line:

        printf "guest@OpenWrt:\$ "

… in the code above to whatever account name you’ve set for your guest account.

It’s also a good idea to go to Services >> Terminal >> Config >> Max Clients and set it to 1 (if you only plan to use a single terminal instance) or 2 (if you plan to use one terminal instance for temperature / CPU load monitoring, and a second terminal instance for entering commands).

Secure the folder so the guest account cannot delete or alter its login profile:

chown -R root:root /home/guest
chmod 755 /home/guest
chmod 644 /home/guest/.profile

Now, when you go to Services >> Terminal, it will immediately start monitoring temperature (under the guest account). You can press either Enter or Ctrl-C to drop back to the command prompt (under the guest accout).

So now I can do something like open my browser and enter:

http://192.168.1.1:7681

… which allows me to monitor OpenWRT One temperature (updating every 5 seconds). I can press Enter or Ctrl-C to drop to a command prompt (under the guest account).

If I type sudo -i and enter the guest password, I’m at a root prompt. I can type exit to drop back to a guest prompt. I can type exit again, then hit Enter to reload the temperature monitoring.

Now it’s a simple matter of setting up a Favorites in your browser pointing to that URL. You could even set it so that each new tab starts up that OpenWRT One temperature monitoring page.

If you want to get rid of the splashscreen in terminal, you can do:

> /etc/banner
> /etc/profile.d/apk-cheatsheet.sh

That basically makes those files empty… that leaves only the BusyBox line, which is hard-coded in BusyBox.

For reference, here’s the contents of those files (so you can write the contents of those files back if you want the splashscreen back):

/etc/banner:

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
-----------------------------------------------------
 OpenWrt 25.12.5, r33051-f5dae5ece4 Dave's Guitar
-----------------------------------------------------

/etc/profile.d/apk-cheatsheet.sh:

if [ -x /usr/bin/apk ] ; then
cat << EOF

 OpenWrt recently switched to the "apk" package manager!

 OPKG Command           APK Equivalent      Description
 ------------------------------------------------------------------
 opkg install <pkg>     apk add <pkg>       Install a package
 opkg remove <pkg>      apk del <pkg>       Remove a package
 opkg upgrade           apk upgrade         Upgrade all packages
 opkg files <pkg>       apk info -L <pkg>   List package contents
 opkg list-installed    apk info            List installed packages
 opkg update            apk update          Update package lists
 opkg search <pkg>      apk search <pkg>    Search for packages
 ------------------------------------------------------------------

For more information visit:
https://openwrt.org/docs/guide-user/additional-software/opkg-to-apk-cheatsheet

EOF
fi

You can also null those files (as above), then write your own data, to wit:

cat << 'EOF' > /etc/banner
This router property of:
Herman Munster
1313 Mockingbird Lane
Mockingbird Heights
EOF