Skip to content

Instantly share code, notes, and snippets.

@Yunuuuu
Last active July 14, 2026 04:46
Show Gist options
  • Select an option

  • Save Yunuuuu/9cc8de7705c5e8fd9cb5909440705260 to your computer and use it in GitHub Desktop.

Select an option

Save Yunuuuu/9cc8de7705c5e8fd9cb5909440705260 to your computer and use it in GitHub Desktop.
Install or update FRP from the SourceForge mirror.
# Install or update FRP from the SourceForge mirror.
#
# Usage:
# _install_frp [OPTIONS]
# _install_frp -h
# unset -f _install_frp
#
# Default mode: --user
#
# Default user layout:
# ${XDG_DATA_HOME:-$HOME/.local/share}/frp
# ${XDG_CONFIG_HOME:-$HOME/.config}/frp/frpc.toml
# ${XDG_CONFIG_HOME:-$HOME/.config}/frp/frps.toml
# $HOME/.local/bin/frpc
# $HOME/.local/bin/frps
#
# Default system layout:
# /usr/local/share/frp
# /usr/local/etc/frp/frpc.toml
# /usr/local/etc/frp/frps.toml
# /usr/local/bin/frpc
# /usr/local/bin/frps
#
# Uninstall the default user installation:
# systemctl --user disable --now frpc 2>/dev/null || true
# systemctl --user disable --now frps 2>/dev/null || true
# rm -f -- "$HOME/.local/bin/frpc" "$HOME/.local/bin/frps"
# rm -rf -- "${XDG_DATA_HOME:-$HOME/.local/share}/frp"
#
# Also remove the user configuration:
# rm -rf -- "${XDG_CONFIG_HOME:-$HOME/.config}/frp"
#
# Uninstall the default system installation:
# sudo systemctl disable --now frpc 2>/dev/null || true
# sudo systemctl disable --now frps 2>/dev/null || true
# sudo rm -f -- /usr/local/bin/frpc /usr/local/bin/frps
# sudo rm -rf -- /usr/local/share/frp
#
# Also remove the system configuration:
# sudo rm -rf -- /usr/local/etc/frp
_install_frp() {
local scope="user"
local scope_set=0
local data_home=""
local data_home_set=0
local bin_dir=""
local bin_dir_set=0
local config_home=""
local frp_home=""
local binary_mode=""
local data_dir_mode=""
local config_dir_mode=""
local bin_dir_mode=""
local machine=""
local archive_arch=""
local tmp=""
local source_dir=""
local latest_dir=""
local download_path=""
local new_frpc=""
local new_frps=""
local file=""
local name=""
local rc=1
local base="https://sourceforge.net/projects/frp.mirror/files"
while (($# > 0)); do
case "$1" in
--user)
if ((scope_set)) && [[ $scope != user ]]; then
echo "Cannot use --user and --system together" >&2
return 2
fi
scope="user"
scope_set=1
shift
;;
--system)
if ((scope_set)) && [[ $scope != system ]]; then
echo "Cannot use --user and --system together" >&2
return 2
fi
scope="system"
scope_set=1
shift
;;
--data-home)
if (($# < 2)); then
echo "--data-home requires a value" >&2
return 2
fi
data_home="$2"
data_home_set=1
shift 2
;;
--data-home=*)
data_home="${1#*=}"
data_home_set=1
shift
;;
--bin-dir)
if (($# < 2)); then
echo "--bin-dir requires a value" >&2
return 2
fi
bin_dir="$2"
bin_dir_set=1
shift 2
;;
--bin-dir=*)
bin_dir="${1#*=}"
bin_dir_set=1
shift
;;
-h | --help)
cat <<'HELP'
Usage:
_install_frp [OPTIONS]
Options may appear in any order.
Options:
--user
Install for the current user. This is the default mode.
--system
Install system-wide. Root privileges are required.
--data-home PATH
--data-home=PATH
Set the parent directory of the FRP data directory.
Release data files are installed into:
PATH/frp
Defaults:
user: ${XDG_DATA_HOME:-$HOME/.local/share}
system: /usr/local/share
--bin-dir PATH
--bin-dir=PATH
Install frpc and frps directly into PATH.
Resulting executable paths:
PATH/frpc
PATH/frps
Defaults:
user: $HOME/.local/bin
system: /usr/local/bin
PATH cannot be empty.
-h, --help
Show this help.
Supported Linux architectures:
x86_64, amd64
aarch64, arm64
Default user layout:
${XDG_DATA_HOME:-$HOME/.local/share}/frp
${XDG_CONFIG_HOME:-$HOME/.config}/frp/frpc.toml
${XDG_CONFIG_HOME:-$HOME/.config}/frp/frps.toml
$HOME/.local/bin/frpc
$HOME/.local/bin/frps
Default system layout:
/usr/local/share/frp
/usr/local/etc/frp/frpc.toml
/usr/local/etc/frp/frps.toml
/usr/local/bin/frpc
/usr/local/bin/frps
Examples:
_install_frp
_install_frp --user
_install_frp --system
_install_frp \
--data-home "$HOME/.local/share" \
--bin-dir "$HOME/.local/bin"
_install_frp \
--system \
--data-home /usr/local/share \
--bin-dir /usr/local/bin
Run the default user installation:
frpc -c "${XDG_CONFIG_HOME:-$HOME/.config}/frp/frpc.toml"
frps -c "${XDG_CONFIG_HOME:-$HOME/.config}/frp/frps.toml"
Run the default system installation:
frpc -c /usr/local/etc/frp/frpc.toml
frps -c /usr/local/etc/frp/frps.toml
Uninstall the default user installation:
systemctl --user disable --now frpc 2>/dev/null || true
systemctl --user disable --now frps 2>/dev/null || true
rm -f -- "$HOME/.local/bin/frpc" "$HOME/.local/bin/frps"
rm -rf -- "${XDG_DATA_HOME:-$HOME/.local/share}/frp"
Also remove the user configuration:
rm -rf -- "${XDG_CONFIG_HOME:-$HOME/.config}/frp"
Uninstall the default system installation:
sudo systemctl disable --now frpc 2>/dev/null || true
sudo systemctl disable --now frps 2>/dev/null || true
sudo rm -f -- /usr/local/bin/frpc /usr/local/bin/frps
sudo rm -rf -- /usr/local/share/frp
Also remove the system configuration:
sudo rm -rf -- /usr/local/etc/frp
HELP
return 0
;;
--)
shift
if (($# > 0)); then
echo "Unexpected positional argument: $1" >&2
return 2
fi
;;
-*)
echo "Unknown option: $1" >&2
return 2
;;
*)
echo "Unexpected positional argument: $1" >&2
echo "Use --data-home or --bin-dir to specify paths" >&2
return 2
;;
esac
done
case "$scope" in
user)
if ((data_home_set == 0)); then
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
fi
if ((bin_dir_set == 0)); then
bin_dir="$HOME/.local/bin"
fi
config_home="${XDG_CONFIG_HOME:-$HOME/.config}/frp"
binary_mode=700
data_dir_mode=700
config_dir_mode=700
bin_dir_mode=700
;;
system)
if ((EUID != 0)); then
echo "System installation requires root privileges" >&2
return 1
fi
if ((data_home_set == 0)); then
data_home="/usr/local/share"
fi
if ((bin_dir_set == 0)); then
bin_dir="/usr/local/bin"
fi
config_home="/usr/local/etc/frp"
binary_mode=755
data_dir_mode=755
config_dir_mode=755
bin_dir_mode=755
;;
esac
if [[ -z $data_home ]]; then
echo "Data home cannot be empty" >&2
return 2
fi
if [[ -z $bin_dir ]]; then
echo "Binary directory cannot be empty" >&2
return 2
fi
data_home="${data_home%/}"
bin_dir="${bin_dir%/}"
frp_home="$data_home/frp"
machine="$(uname -m)"
case "$machine" in
x86_64 | amd64)
archive_arch="amd64"
;;
aarch64 | arm64)
archive_arch="arm64"
;;
*)
echo "Unsupported architecture: $machine" >&2
return 1
;;
esac
tmp="$(mktemp -d)" || {
echo "Failed to create a temporary directory" >&2
return 1
}
new_frpc="$bin_dir/.frpc.new.$$"
new_frps="$bin_dir/.frps.new.$$"
latest_dir="$(
curl -fsSL "$base/" \
| grep -oE \
'/projects/frp\.mirror/files/v[0-9]+\.[0-9]+\.[0-9]+/' \
| sort -Vu \
| tail -n 1
)"
if [[ -z $latest_dir ]]; then
echo "Failed to find the latest FRP release" >&2
else
download_path="$(
curl -fsSL "https://sourceforge.net$latest_dir" \
| grep -oE \
"/projects/frp\.mirror/files/v[0-9]+\.[0-9]+\.[0-9]+/frp_[0-9]+\.[0-9]+\.[0-9]+_linux_${archive_arch}\.tar\.gz/download" \
| head -n 1
)"
if [[ -z $download_path ]]; then
echo "Failed to find the Linux $archive_arch package" >&2
elif ! curl \
-fL \
"https://sourceforge.net$download_path" \
-o "$tmp/frp.tar.gz"
then
echo "Failed to download FRP" >&2
elif ! tar -xzf "$tmp/frp.tar.gz" -C "$tmp"; then
echo "Failed to extract the FRP archive" >&2
else
source_dir="$(
find "$tmp" \
-mindepth 1 \
-maxdepth 1 \
-type d \
-name "frp_*_linux_${archive_arch}" \
-print \
-quit
)"
if [[ -z $source_dir ]]; then
echo "Failed to locate the extracted FRP directory" >&2
elif [[ ! -f $source_dir/frpc ]]; then
echo "The FRP archive does not contain frpc" >&2
elif [[ ! -f $source_dir/frps ]]; then
echo "The FRP archive does not contain frps" >&2
elif ! install -d -m "$data_dir_mode" "$frp_home"; then
echo "Failed to create the FRP data directory" >&2
elif ! install -d -m "$config_dir_mode" "$config_home"; then
echo "Failed to create the FRP configuration directory" >&2
elif ! install -d -m "$bin_dir_mode" "$bin_dir"; then
echo "Failed to create the executable directory" >&2
elif ! install \
-m "$binary_mode" \
"$source_dir/frpc" \
"$new_frpc"
then
echo "Failed to stage frpc" >&2
elif ! install \
-m "$binary_mode" \
"$source_dir/frps" \
"$new_frps"
then
echo "Failed to stage frps" >&2
else
rc=0
while IFS= read -r -d '' file; do
name="${file##*/}"
case "$name" in
frpc | frps)
continue
;;
frpc.toml | frps.toml)
if [[ ! -e $config_home/$name && ! -L $config_home/$name ]]; then
install -m 600 "$file" "$config_home/$name" || rc=1
fi
;;
*)
rm -rf -- "$frp_home/$name" \
&& cp -a -- "$file" "$frp_home/$name" \
|| rc=1
;;
esac
done < <(
find "$source_dir" \
-mindepth 1 \
-maxdepth 1 \
-print0
)
if ((rc == 0)); then
mv -fT -- "$new_frpc" "$bin_dir/frpc" \
&& mv -fT -- "$new_frps" "$bin_dir/frps" \
|| rc=1
fi
fi
fi
fi
rm -rf -- "$tmp"
rm -f -- "$new_frpc" "$new_frps"
return "$rc"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment