DEV Community

Cover image for ๐Ÿ โ˜ ๏ธ Home Alone: Exposing My Home Server to the Internet (and Judgment) with FRP + Jenkins + Bash
Cyber
Cyber

Posted on

๐Ÿ โ˜ ๏ธ Home Alone: Exposing My Home Server to the Internet (and Judgment) with FRP + Jenkins + Bash

TL;DR:

I turned my home server into a publicly available chaos machine by automating FRP tunnels from Jenkinsโ€ฆ running on a remote VPSโ€ฆ like some kind of over-caffeinated Bond villain.

After all, who doesn't want to broadcast their cat cam to friends?

๐Ÿ’ก The Setup (a.k.a. the Overkill Stack)

  • ๐Ÿง  Home Server (Worker Jenkins Node) on private network - Raspberry Pi, old laptop, toaster with Linux - whatever you have.
  • ๐ŸŒ VPS (Main Jenkins Node) - Runs Jenkins jobs to control FRP clients.
  • ๐Ÿš‡ FRP (Fast Reverse Proxy) - An open-source alternative to Ngrok.
  • ๐Ÿค– Jenkins - Because we like suffering, but reproducible.

๐ŸŒŸ Goal

From Jenkins (already connected to the home server via a build agent), we want to:

  1. Spin up secure FRP tunnels that expose ports from the home server.
  2. Use Jenkins jobs like a dashboard - "Expose internal port 3000 as port 1000 externally on VPS", "Shut it down", etc.
  3. Avoid manual interaction unless something literally catches fire.

๐Ÿ› ๏ธ Use Cases (Excuses to Build This)

  • ๐Ÿ’ป Show off local dev apps to clients/friends
  • ๐Ÿ“ฆ Share self-hosted dashboards (Grafana, Prometheus, whatever)
  • ๐ŸŽฅ Let your dog cam stream to the world
  • โ›” Temporarily expose private tools during demos

How It Works

  • FRP server runs on your VPS
  • FRP client lives on your home server
  • Jenkins jobs (run on a connected build agent) generate config files + launch tunnels

๐Ÿ“ฆ FRP Server Setup on Jenkins VPS

Steps to Create a Freestyle Job in Jenkins

  • Open Jenkins Dashboard
  • Click on "New Item"
    • (Top-left of the dashboard)
  • Enter a Job Name
  • Select "Freestyle Project"
    • Then click OK.
  • Configure the Job
    • Copy and paste the script provided below.
  • Click "Save"
  • Run the Job
    • Click "Build Now" on the job page.

โœ… Here's a minimal script:

#!/bin/bash

# --- SETTINGS ---
FRP_VERSION=0.63.0
FRP_PLATFORM=linux_amd64
FRP_DIR="frp_${FRP_VERSION}_${FRP_PLATFORM}"
FRP_DOWNLOAD_URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_DIR}.tar.gz"
FRP_INSTALL_DIR="/opt/frp"
FRPS_BINARY="${FRP_INSTALL_DIR}/frps"

# --- ENSURE INSTALL DIR EXISTS ---
mkdir -p "$FRP_INSTALL_DIR"

# --- DOWNLOAD + INSTALL IF NEEDED ---
if [[ ! -f "$FRPS_BINARY" ]]; then
  echo "๐Ÿ“ฆ Downloading FRP server to $FRP_INSTALL_DIR..."
  wget "$FRP_DOWNLOAD_URL" -O frp.tar.gz || {
    echo "โŒ Download failed. Check URL or network."
    exit 1
  }

  tar -xzf frp.tar.gz
  mv "${FRP_DIR}/frps" "$FRPS_BINARY"
  chmod +x "$FRPS_BINARY"
  rm -rf "$FRP_DIR" frp.tar.gz
fi

# --- CREATE CONFIG IF NEEDED ---
cat > "$FRP_INSTALL_DIR/frps.ini" <<EOF
[common]
bind_port = 1000
dashboard_port = 1500
dashboard_user = admin
dashboard_pwd = admin
EOF

# --- START FRP SERVER ---
cd "$FRP_INSTALL_DIR"
export BUILD_ID=dontKillMe
nohup "$FRPS_BINARY" -c frps.ini > frps.log 2>&1 &

echo "โœ… FRP server started on port 1000, dashboard at :1500"

Enter fullscreen mode Exit fullscreen mode

After that you have:

โŒ How To Kill FTP Server

I set up a different freestyle job - just like in the thrilling saga above, that shuts down the server when I don't need it.

#!/bin/bash
FRP_PID=$(pgrep -f frps)

if [ -n "$FRP_PID" ]; then
  echo "Killing frps with PID $FRP_PID"
  kill "$FRP_PID"
else
  echo "No frps process found"
fi
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ FRP Client Setup on Home Server via Jenkins

The next step is to create a third freestyle job in Jenkins, which will set up the FRP client on the worker node of Jenkins running on my home server.

Keep in mind, you'll need to use the "Build with Parameters" option, where the parameters are $LOCAL_PORT (the port on your private server) and $REMOTE_PORT (the port on your public VPS server).

โœ… Here's another bash script:

#!/bin/bash

if [[ -z "$LOCAL_PORT" || -z "$REMOTE_PORT" ]]; then
  echo "โŒ Missing required parameters."
  echo "Please provide: LOCAL_PORT, REMOTE_PORT"
  exit 1
fi

FRP_SERVER=<VPS IP>
FRP_SERVER_PORT=1000
FRP_VERSION=0.63.0
FRP_PLATFORM=linux_amd64
FRP_DIR="frp_${FRP_VERSION}_${FRP_PLATFORM}"

WORKDIR="$WORKSPACE/frpc_clients"
mkdir -p "$WORKDIR"

FRPC_BINARY="$WORKDIR/frpc"
FRPC_CONFIG="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.ini"
FRPC_LOG="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.log"
FRPC_PID="$WORKDIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.pid"

# Download frpc if missing
if [[ ! -f "$FRPC_BINARY" ]]; then
  echo "๐Ÿ”ฝ Downloading frpc v${FRP_VERSION}..."
  wget "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_DIR}.tar.gz" -O /tmp/frp.tar.gz || {
    echo "โŒ Download failed. Please check FRP version or network."
    exit 1
  }

  tar -xzf /tmp/frp.tar.gz -C /tmp
  mv /tmp/${FRP_DIR}/frpc "$FRPC_BINARY"
  chmod +x "$FRPC_BINARY"
  rm -rf /tmp/${FRP_DIR} /tmp/frp.tar.gz
fi

echo "โš™๏ธ Writing frpc config for local:$LOCAL_PORT โ†’ remote:$REMOTE_PORT"
cat > "$FRPC_CONFIG" <<EOL
[common]
server_addr = $FRP_SERVER
server_port = $FRP_SERVER_PORT

[tunnel_${LOCAL_PORT}]
type = tcp
local_port = $LOCAL_PORT
remote_port = $REMOTE_PORT
EOL

# Check if already running
if [[ -f "$FRPC_PID" ]]; then
  PID=$(cat "$FRPC_PID")
  if kill -0 "$PID" >/dev/null 2>&1; then
    echo "โš ๏ธ Tunnel already running with PID $PID"
    exit 0
  else
    echo "โš ๏ธ Stale PID file found, removing"
    rm -f "$FRPC_PID"
  fi
fi

echo "๐Ÿš€ Starting FRP tunnel..."
export BUILD_ID=dontKillMe
nohup "$FRPC_BINARY" -c "$FRPC_CONFIG" > "$FRPC_LOG" 2>&1 &
echo $! > "$FRPC_PID"

echo "โœ… Tunnel running at $FRP_SERVER:$REMOTE_PORT (PID $(cat $FRPC_PID))"

Enter fullscreen mode Exit fullscreen mode

โœ… Your janky React app is now live at http://vps-ip:30001

โŒ How To Kill A Tunnel To FRP Client

Create another freestyle job using the Bash script below.

As mentioned earlier, make sure to use the "Build with Parameters" option, setting $LOCAL_PORT and $REMOTE_PORT appropriately.

#!/bin/bash

LOCAL_PORT="$LOCAL_PORT"
REMOTE_PORT="$REMOTE_PORT"
FRPC_DIR="$FRP_DIR/frpc_clients"
PID_FILE="$FRPC_DIR/frpc_${LOCAL_PORT}_${REMOTE_PORT}.pid"

if [ -f "$PID_FILE" ]; then
  PID=$(cat "$PID_FILE")
  echo "Stopping tunnel: local $LOCAL_PORT โ†’ remote $REMOTE_PORT (PID: $PID)"
  kill "$PID"
  rm -f "$PID_FILE"
  echo "Tunnel stopped successfully."
else
  echo "No PID file found at $PID_FILE"
  exit 1
fi

Enter fullscreen mode Exit fullscreen mode

๐Ÿง  Final Thoughts

They said, "Don't expose your local ports to the internet". They didn't say, "Don't automate it with Jenkins like a Bond villain".

Because even in a zero-trust, cloud-native worldโ€ฆsometimes you just want to YOLO a port and livestream your cat feeder from Jenkins.

You can find the full code for this madness on GitHub.

Top comments (0)