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)