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:
- Spin up secure FRP tunnels that expose ports from the home server.
- Use Jenkins jobs like a dashboard - "Expose internal port 3000 as port 1000 externally on VPS", "Shut it down", etc.
- 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
- Go to your Jenkins instance in the browser (e.g. http://localhost:8080).
- 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"
After that you have:
- frps running on port 1000
- A dashboard on http://vps-ip:1500
- Config in /opt/frp/frps.ini
โ 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
๐ฆ 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))"
โ 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
๐ง 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)