op-packages/luci-app-shadowrt/root/usr/libexec/istorec/shadowrt.sh
github-actions[bot] 32600acc60 🌴 Sync 2026-03-05 23:51:42
2026-03-05 23:51:42 +08:00

268 lines
7.1 KiB
Bash
Executable File

#!/bin/sh
# Author jjm2473@gmail.com
ACTION="${1}"
shift 1
do_install() {
local id="`uci get shadowrt.@instance[0].id 2>/dev/null`"
local data="`uci get shadowrt.@instance[0].data 2>/dev/null`"
local mnt="`uci get shadowrt.@instance[0].mnt 2>/dev/null`"
local proto=`uci get shadowrt.@instance[0].proto 2>/dev/null`
local address=`uci get shadowrt.@instance[0].address 2>/dev/null`
local gateway=`uci get shadowrt.@instance[0].gateway 2>/dev/null`
local dns="`uci get shadowrt.@instance[0].dns 2>/dev/null`"
local dhcp_server=`uci get shadowrt.@instance[0].dhcp_server 2>/dev/null`
local ports="`uci get shadowrt.@instance[0].ports 2>/dev/null`"
if [ -z "$data" ]; then
echo "data path is empty!" >&2
exit 1
fi
[ -s /rom/etc/openwrt_release ] || {
echo "/rom is not a openwrt rootfs!" >&2
exit 1
}
if [ "$proto" = "static" ]; then
if [ -z "$address" ]; then
echo "static ip requires address!" >&2
exit 1
fi
fi
/etc/init.d/docker-lan start || {
echo "create docker-lan bridge failed!" >&2
exit 1
}
local alpine_image="alpine:3.22.2"
if ! docker image inspect -f '{}' "$alpine_image" >/dev/null 2>&1; then
echo "pulling alpine image $alpine_image ..."
docker pull "$alpine_image" || exit 1
fi
if [ -d "$data/$id" ]; then
echo "WARNING: $data/$id already exists, may use old data." >&2
fi
local config="{\"id\":\"$id\",\"data\":\"$data\",\"mnt\":\"$mnt\",\"proto\":\"$proto\",\"address\":\"$address\",\"gateway\":\"$gateway\",\"dns\":\"$dns\",\"dhcp_server\":\"$dhcp_server\",\"ports\":\"$ports\"}"
local cmd="docker run --restart=unless-stopped -d \
--stop-signal SIGINT \
--stop-timeout 30 \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--cap-add=SYS_ADMIN \
--cap-add=SYS_CHROOT \
--cap-drop=MKNOD \
--cap-add=LEASE \
--cap-add=SETGID \
--cap-add=SETUID \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--cap-add=NET_BIND_SERVICE \
--network docker-lan \
-v /usr/share/shadowrt/container:/shadowrt:ro \
--entrypoint /shadowrt/entrypoint.sh \
-v /dev/net:/dev/net \
--device /dev/fuse:/dev/fuse \
-v /rom:/rom:ro \
--label creator=shadowrt \
--name '$id' \
--hostname '$id' \
--label 'com.shadowrt.config=$config' \
-v '$data/$id/overlay:/overlay:rw' "
[ -n "$proto" ] && cmd="$cmd -e IP_PROTO=$proto"
if [ "$proto" = "static" ]; then
[ -n "$address" ] && cmd="$cmd -e IP_ADDRESS=$address"
[ -n "$gateway" ] && cmd="$cmd -e IP_GATEWAY=$gateway"
[ -n "$dns" ] && cmd="$cmd -e 'IP_DNS=$dns'"
fi
[ "$dhcp_server" = "1" -o "$dhcp_server" = "on" ] && cmd="$cmd -e DHCP_SERVER=on"
if [ -n "$ports" ]; then
for p in $ports; do
cmd="$cmd -p $p:$p"
done
fi
if [ -n "$dns" ]; then
for d in $dns; do
cmd="$cmd --dns $d"
done
fi
local tz="`uci get system.@system[0].zonename | sed 's/ /_/g'`"
[ -z "$tz" ] || cmd="$cmd -e TZ=$tz"
if [ "$mnt" = "1" -o "$mnt" = "on" ]; then
cmd="$cmd -v /mnt:/mnt"
mountpoint -q /mnt && cmd="$cmd:rshared"
fi
cmd="$cmd $alpine_image"
echo "stopping existing container..."
docker stop "$id" >/dev/null 2>&1
docker rm -f "$id"
echo "starting shadowrt instance $id..."
echo "$cmd"
eval "$cmd"
}
do_ls() {
local name state ip
echo "["
docker ps -a -f 'label=creator=shadowrt' --format '{{.Names}} {{.State}}' | sort -n | while read name state; do
ip=
if [ "$state" = "running" ]; then
ip=`docker exec "$name" ip addr show dev br-lan | grep -m1 'inet ' | head -1 | sed -nE 's#.*inet ([0-9\.]*)/([0-9]*) .*#\1#p'`
if [ -z "$ip" ]; then
docker exec "$name" test -e /etc/openwrt_release -a ! -e /rom/note || state="starting"
fi
fi
echo '{"name":"'"$name"'","status":"'"$state"'","ip":"'"$ip"'"},'
#docker container inspect -f '{"name":"'"$name"'","status":"'"$state"'","config":{{index .Config.Labels "com.shadowrt.config"}},"ip":"'"$ip"'"},' "$name"
done | head -c -2
echo ""
echo "]"
}
do_clone() {
local config_json="`docker container inspect -f '{{index .Config.Labels "com.shadowrt.config"}}' "$1"`"
[ -z "$config_json" ] && {
echo "container $1 not found!" >&2
return 1
}
{
echo "delete shadowrt.@instance[0]"
echo "add shadowrt instance"
echo "$config_json" | jsonfilter -e 'id=$.id' \
-e 'data=$.data' \
-e 'mnt=$.mnt' \
-e 'proto=$.proto' \
-e 'address=$.address' \
-e 'gateway=$.gateway' \
-e 'dns=$.dns' \
-e 'dhcp_server=$.dhcp_server' \
-e 'ports=$.ports' | sed -e 's/; /\n/g' | sed -e 's/^export /set shadowrt.@instance[0]./g'
echo "commit shadowrt"
} | uci batch
return 0
}
do_reset_network() {
local name="$1"
local running=false
local shell
docker exec "$name" test -e /etc/openwrt_release -a ! -e /rom/note >/dev/null 2>&1 && running=true
if $running; then
shell="exec docker exec -i -w / '$name' /bin/sh"
else
docker stop "$name" >/dev/null 2>&1
local data="`docker container inspect -f '{{index .Config.Labels "com.shadowrt.config"}}' "$name" | jsonfilter -e '$.data'`"
local dir="$data/$name/overlay/upper"
[ -d "$dir" ] || return 0
shell="cd '$dir' && exec /bin/sh"
fi
{
cat <<-EOF
for f in etc/config/network etc/board.json; do
rm -f "\$f"
done
EOF
if $running; then
cat <<-EOF
/bin/board_detect
/bin/config_generate
/bin/sh -c ". /rom/etc/uci-defaults/zzz-dockerenv"
/bin/sh -c '. /rom/etc/uci-defaults/12_network-generate-ula'
/etc/init.d/network restart
sleep 2
EOF
else
echo 'rm -f etc/uci-defaults/zzz-dockerenv etc/uci-defaults/12_network-generate-ula'
fi
} | sh -c "$shell"
}
check_all_ready() {
local names="$1"
local name
local ret=0
for name in $names; do
if ! docker exec "$name" test -e /etc/openwrt_release -a ! -e /rom/note; then
ret=1
break
fi
done
return $ret
}
usage() {
echo "usage: $0 sub-command"
echo "where sub-command is one of:"
echo " install Install/Replace a instance"
echo " ls List all instances"
echo " rm/start/stop/restart {ID} Remove/Start/Stop/Restart the instance"
echo " clone {ID} Clone an existing instance to uci"
echo " rmd {ID} Remove an existing instance and its data"
echo " reset_network {ID} Reset network configuration inside the instance"
echo " status Dummy status for taskd"
}
case "${ACTION}" in
"install")
do_install
;;
"rm" | "rmd")
if [ -n "$1" ]; then
docker stop "$1" >/dev/null 2>&1
if [ "$ACTION" = "rmd" ]; then
data="`docker container inspect -f '{{index .Config.Labels "com.shadowrt.config"}}' "$1" | jsonfilter -e '$.data'`"
[ -n "$data" ] && rm -rf "$data/$1"
fi
docker rm -f "$1"
fi
;;
"reset_network")
if [ -n "$1" ]; then
do_reset_network "$1"
fi
;;
"start" | "stop" | "restart")
if [ -n "$1" ]; then
docker "${ACTION}" $1
if [ "$ACTION" = "start" -o "$ACTION" = "restart" ]; then
sleep 2
for i in $(seq 1 5); do
check_all_ready "$1" && break
sleep 1
done
fi
fi
;;
"clone")
if [ -n "$1" ]; then
do_clone "$1" || exit 1
fi
;;
"status")
# hack, return empty string, so lib-taskd thinks container is not installed
exit 0
;;
"ls")
do_ls
;;
*)
usage
exit 1
;;
esac