| #!/bin/sh |
| |
| # Copyright 2015-2024 Rivoreo |
| |
| # Permission is hereby granted, free of charge, to any person obtaining a copy |
| # of this software and associated documentation files (the "Software"), to |
| # deal in the Software without restriction, including without limitation the |
| # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| # sell copies of the Software, and to permit persons to whom the Software is |
| # furnished to do so, subject to the following conditions: |
| |
| # The above copyright notice and this permission notice shall be included in |
| # all copies or substantial portions of the Software. |
| |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR |
| # IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| # SOFTWARE. |
| |
| |
| PROGRAM_DIR="${0%/*}" |
| [ "$PROGRAM_DIR" = "$0" ] && PROGRAM_DIR="$PWD" |
| RNCN_STATE_FILE="$PROGRAM_DIR/.nodename" |
| RNCN_UNPRIVILEGED_USER=rncn-noaccess |
| GEOIP_MMDB_URL=https://cdn.libz.so/gh/JohnnySun/geoip/release/Country.mmdb |
| DEFAULT_FALLBACK_NAME_SERVER=8.8.4.4 |
| |
| # Override Debian default PATH |
| PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin |
| [ -h /bin ] || PATH="$PATH:/bin" |
| [ -h /sbin ] || PATH="$PATH:/sbin" |
| export PATH="$PATH:/usr/games" |
| |
| [ -z "$CC" ] && CC=gcc |
| |
| check_super_user() { |
| [ "`id -u`" = 0 ] && return |
| echo "This action must be run with super user" 1>&2 |
| exit 1 |
| } |
| |
| parse_key_value() { |
| case "$1" in |
| *=*) |
| key="${1%%=*}" |
| key="${key%%[ ]*}" |
| value="${1#*=}" |
| local trim_value |
| while trim_value="${value#[ ]}" && [ "$trim_value" != "$value" ]; do value="$trim_value"; done |
| ;; |
| *\ *|*\ *) |
| key="${1%%[ ]*}" |
| value="${1#*[ ]}" |
| ;; |
| *) |
| false |
| ;; |
| esac |
| } |
| |
| parse_boolean() { |
| local value |
| eval "value=\"\$$1\"" |
| case "$value" in |
| [Oo][Ff][Ff]|[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|0|"") |
| eval "$1=" |
| ;; |
| [Oo][Nn]|[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) |
| eval "$1=1" |
| ;; |
| *) |
| false |
| ;; |
| esac |
| } |
| |
| print_peer_in_tunnel_address() { |
| local ORIG_IFS="$IFS" |
| IFS=. |
| set -- $1 |
| IFS="$ORIG_IFS" |
| [ $# = 4 ] || return |
| printf "%s.%s.%s." $1 $2 $3 |
| echo $(($4+($4%2?-1:1))) |
| } |
| |
| print_peer_in_tunnel_inet6_address() { |
| local last_number="${1##*:}" |
| if [ -n "$last_number" ]; then |
| last_number="0x$last_number" |
| else |
| last_number=0 |
| fi |
| printf '%s:%x\n' "${1%:*}" $((last_number+(last_number%2?-1:1))) |
| } |
| |
| # Usage: load_config <config-dir> |
| load_config() { |
| node_address= |
| set_hostname= |
| ipv4_default_router= |
| ipv6_default_router= |
| run_dns_server= |
| replace_sysctl_conf= |
| replace_resolv_conf= |
| add_login_logo=1 |
| run_sniproxy= |
| fallback_name_server= |
| time_zone= |
| dns_config_update_url= |
| mon_initial_ping_duration=60 |
| mon_ping_duration=3600 |
| mon_minimal_ping_wait=3600 |
| wg_private_key= |
| wg_use_peer_resolver= |
| wg_periodic_rebind_port= |
| peers= |
| ra_list= |
| ipv4_ra_exclusion= |
| ipv6_ra_exclusion= |
| local_wan_ipv4_route_targets= |
| local_wan_ipv6_route_targets= |
| local section= |
| local current_peer= current_peer_ip6addr_index |
| local key value line nlines=0 |
| while read -r line; do |
| nlines=$((nlines+1)) |
| case "$line" in |
| ""|\;*|\#*) |
| continue |
| ;; |
| \[peer\ *\]*) |
| section=peer |
| peer_name="${line#\[peer }" |
| peer_name="${peer_name%%\]*}" |
| current_peer="`printf %s \"$peer_name\" | sed -E 's/[.-]/_/g'`" |
| peers="$peers $current_peer" |
| eval "peer_${current_peer}_name=\"\$peer_name\"" |
| eval "peer_${current_peer}_public_key=" |
| eval "peer_${current_peer}_bind_port=" |
| eval "peer_${current_peer}_remote=" |
| eval "peer_${current_peer}_mtu=" |
| eval "peer_${current_peer}_ip4_addr=" |
| eval "peer_${current_peer}_ip6_addr_0=" |
| eval "peer_${current_peer}_metric_offset=0" |
| current_peer_ip6addr_index=0 |
| continue |
| ;; |
| \[*\]*) |
| section="${line%%\]*}" |
| section="${section#\[}" |
| current_peer= |
| continue |
| ;; |
| esac |
| case "$section" in |
| ""|wireguard|peer) |
| if ! parse_key_value "$line"; then |
| printf "Parsing error at line %s, missing value?\\n" $nlines 1>&2 |
| return 1 |
| fi |
| case "$section.$key" in |
| .node_address|.ipv4_default_router|.ipv6_default_router|.fallback_name_server|.time_zone|.dns_config_update_url) |
| eval "$key=\"\$value\"" |
| ;; |
| .set_hostname|.run_dns_server|.replace_sysctl_conf|.replace_resolv_conf|.add_login_logo) |
| eval "$key=\"\$value\"" |
| if ! parse_boolean "$key"; then |
| printf "Expect a boolean value for %s at line %s, got unrecognizable '%s'\\n" "$key" $nlines "$value" 1>&2 |
| return 1 |
| fi |
| ;; |
| monitor.initial_ping_duration|monitor.ping_duration|monitor.minimal_ping_wait) |
| eval "mon_$key=\"\$value\"" |
| ;; |
| wireguard.private_key|wireguard.use_peer_resolver|wireguard.periodic_rebind_port) |
| eval "wg_$key=\"\$value\"" |
| ;; |
| peer.public_key|peer.bind_port|peer.remote|peer.mtu|peer.metric_offset) |
| eval "peer_${current_peer}_$key=\"\$value\"" |
| ;; |
| peer.ip4_addr) |
| eval "peer_${current_peer}_$key=\"\$value\"" |
| if ! value="`print_peer_in_tunnel_address \"$value\"`"; then |
| printf "Invalid IPv4 address at line %s\\n" $nlines 1>&2 |
| return 1 |
| fi |
| eval "peer_${current_peer}_remote_ip4_addr=\"\$value\"" |
| ;; |
| peer.ip6_addr) |
| eval "peer_${current_peer}_ip6_addr_$current_peer_ip6addr_index=\"\$value\"" |
| current_peer_ip6addr_index=$((current_peer_ip6addr_index+1)) |
| ;; |
| esac |
| ;; |
| ra-list) |
| ra_list="$ra_list$line |
| " |
| ;; |
| ipv4-ra-exclusion) |
| ipv4_ra_exclusion="$ipv4_ra_exclusion$line |
| " |
| ;; |
| ipv6-ra-exclusion) |
| ipv6_ra_exclusion="$ipv6_ra_exclusion$line |
| " |
| ;; |
| wan-ipv4-route-targets) |
| local_wan_ipv4_route_targets="$local_wan_ipv4_route_targets$line |
| " |
| ;; |
| wan-ipv6-route-targets) |
| local_wan_ipv6_route_targets="$local_wan_ipv6_route_targets$line |
| " |
| ;; |
| esac |
| done < "$1/main.cfg" |
| for key in node_address ipv4_default_router wg_private_key; do |
| eval "[ -n \"\$$key\" ]" && continue |
| printf "Missing %s\\n" "$key" 1>&2 |
| return 1 |
| done |
| [ -z "$peers" ] && echo "Warning: no tunnel peer defined" 1>&2 |
| if [ -n "$ra_list" ]; then |
| bird_conf_include_ipv4_ra=' include "ipv4-ra";' |
| bird_conf_include_ipv6_ra=' include "ipv6-ra";' |
| else |
| bird_conf_include_ipv4_ra= |
| bird_conf_include_ipv6_ra= |
| fi |
| for f in "$1/clips"/*; do |
| [ -f "$f" ] || continue |
| eval "clip_${f##*/}=\"\`cat \\\"\$f\\\"\`\"" |
| done |
| } |
| |
| get_default_route_interface() { |
| local line |
| # Skip header |
| read -r line |
| while read -r line; do |
| set -- $line |
| if [ "$2" = 00000000 ] && [ "$7" = 0 ]; then |
| default_route_interface="$1" |
| return |
| fi |
| done |
| false |
| } < /proc/net/route |
| |
| make_sure_nonempty_file_ends_with_new_line() { |
| local len |
| len=`wc -c < "$1"` || return |
| [ "$len" -lt 1 ] && return |
| [ "`dd bs=1 \"if=$1\" skip=$((len-1)) 2> /dev/null | wc -l`" -eq 1 ] && return |
| echo >> "$1" |
| } |
| |
| append_unique_line() { |
| grep --fixed-strings --line-regexp --quiet -- "$1" "$2" && return |
| printf %s\\n "$1" >> "$2" |
| } |
| |
| modify_etc_profile() { |
| if [ -h /etc/profile ]; then |
| echo "/etc/profile is a symbolic link, this is unexpected" 1>&2 |
| return 1 |
| fi |
| if [ ! -f /etc/profile ]; then |
| echo "/etc/profile didn't exist as a regular file, this is unexpected" 1>&2 |
| return 1 |
| fi |
| grep --fixed-strings --line-regexp --quiet -- "export SYSTEMD_PAGER=" /etc/profile && return |
| make_sure_nonempty_file_ends_with_new_line /etc/profile |
| local new_profile IFS= line if_statement= |
| new_profile="`mktemp`" || return |
| while read -r line; do case "$line" in |
| 'if [ "${PS1-}" ]; then'|"if [ "*" -eq 0 ]; then") |
| if_statement="$line" |
| ;; |
| " PATH="*) |
| if [ -z "$if_statement" ]; then |
| echo "Warning: Unexpected format in /etc/profile" |
| rm -f "$new_profile" |
| append_unique_line "export PATH=$PATH" /etc/profile |
| append_unique_line "export SYSTEMD_PAGER=" /etc/profile |
| append_unique_line "export SYSTEMD_URLIFY=off" /etc/profile |
| return |
| fi |
| ;; |
| else) |
| [ -z "$if_statement" ] && echo else |
| ;; |
| fi) |
| case "$if_statement" in |
| 'if [ "${PS1-}" ]; then') |
| cat << EOT |
| if [ -n "\${PS1-}" ]; then |
| # Interactive shell |
| [ -n "\${BASH-}" ] && [ "\$BASH" != "/bin/sh" ] && [ -f /etc/bash.bashrc ] && . /etc/bash.bashrc |
| fi |
| EOT |
| ;; |
| "") |
| echo fi |
| ;; |
| *) |
| cat << EOT |
| export PATH=$PATH |
| export SYSTEMD_PAGER= |
| export SYSTEMD_URLIFY=off |
| |
| if [ "\${LANG%%.*}" = zh_CN ]; then |
| # Workaround for bug 939445 |
| export LANG="zh_TW\${LANG#zh_CN}" |
| [ -z "\$LANGUAGE" ] && export LANGUAGE=zh_CN:zh_TW:en_US:en_GB |
| elif [ -z "\$LANG" ]; then case "\$TERM" in |
| linux) |
| export LANG=C.UTF-8 |
| ;; |
| *) |
| #export LANG=zh_TW.UTF-8 |
| export LANG=en_GB.UTF-8 |
| ;; |
| esac fi |
| EOT |
| esac |
| if_statement= |
| ;; |
| "export PATH") |
| ;; |
| *) |
| case "$if_statement" in |
| 'if [ "${PS1-}" ]; then') |
| ;; |
| "") |
| printf %s\\n "$line" |
| ;; |
| *) |
| printf '%s\n%s\n' "$if_statement" "$line" |
| ;; |
| esac |
| ;; |
| esac done < /etc/profile > "$new_profile" |
| cat "$new_profile" > /etc/profile |
| rm -f "$new_profile" |
| } |
| |
| configure_zsh() { |
| [ -d /etc/zsh ] || return 0 |
| make_sure_nonempty_file_ends_with_new_line /etc/zsh/zlogin |
| append_unique_line "source /etc/profile" /etc/zsh/zlogin |
| } |
| |
| create_rncn_unprivileged_user() { |
| adduser --system --gecos "Unprivileged user for running RNCN tasks" --no-create-home "$RNCN_UNPRIVILEGED_USER" && return |
| adduser --system --comment "Unprivileged user for running RNCN tasks" --no-create-home "$RNCN_UNPRIVILEGED_USER" |
| } |
| |
| configure_sshd() { |
| make_sure_nonempty_file_ends_with_new_line /etc/ssh/sshd_config |
| local sshd_config_sed_flags= |
| grep -Eq "^[[:space:]]*#?UseDNS[[:space:]]+yes" /etc/ssh/sshd_config && sshd_config_sed_flags="-e 's/^([[:space:]]*)#?(UseDNS[[:space:]]+).+/\\1\\2no/'" || true |
| grep -Eq "^[[:space:]]*#?PasswordAuthentication[[:space:]]+no" /etc/ssh/sshd_config && sshd_config_sed_flags="-e 's/^([[:space:]]*)#?(PasswordAuthentication[[:space:]]+).+/\\1\\2yes/'" || true |
| if grep -Eq "^[[:space:]]*#?PrintMotd[[:space:]]+" /etc/ssh/sshd_config; then |
| sshd_config_sed_flags="-e 's/^([[:space:]]*)#?(PrintMotd[[:space:]]+).+/\\1\\2yes/'" |
| else |
| append_unique_line "PrintMotd yes" /etc/ssh/sshd_config |
| fi |
| [ -z "$sshd_config_sed_flags" ] && return |
| rm -f /etc/ssh/sshd_config.new || return |
| eval "sed -E $sshd_config_sed_flags /etc/ssh/sshd_config" > /etc/ssh/sshd_config.new || return |
| mv -f /etc/ssh/sshd_config.new /etc/ssh/sshd_config |
| } |
| |
| configure_pam_sshd() { |
| [ -f /etc/pam.d/sshd ] || return 0 |
| rm -f /etc/pam.d/sshd.new || return |
| sed -E '/[[:space:]]pam_(motd|limits)\.so/d' /etc/pam.d/sshd > /etc/pam.d/sshd.new || return |
| if cmp -s /etc/pam.d/sshd.new /etc/pam.d/sshd; then |
| rm -f /etc/pam.d/sshd.new |
| else |
| mv -f /etc/pam.d/sshd.new /etc/pam.d/sshd |
| fi |
| } |
| |
| # Silence log messages from pam_unix.so for sudo(1) to $RNCN_UNPRIVILEGED_USER |
| configure_sudo_pam() { |
| [ -f /etc/pam.d/sudo ] || return 0 |
| local IFS= |
| local pass= |
| rm -f /etc/pam.d/sudo.new |
| while read -r line; do |
| case "$line" in |
| @include*) |
| [ -z "$pass" ] && printf 'session [success=done default=ignore] pam_succeed_if.so quiet ruser = root user = %s\n' "$RNCN_UNPRIVILEGED_USER" |
| pass=1 |
| ;; |
| */limits.conf*) |
| continue |
| ;; |
| session*pam_limits.so*) |
| continue |
| ;; |
| session*pam_env.so*envfile=/etc/default/locale*) |
| continue |
| ;; |
| esac |
| printf %s\\n "$line" |
| done < /etc/pam.d/sudo > /etc/pam.d/sudo.new |
| mv -f /etc/pam.d/sudo.new /etc/pam.d/sudo |
| } |
| |
| configure_sudoers() { |
| local sudoers=/etc/sudoers new_sudoers |
| append_or_set_words() { |
| if [ -n "$new_value" ]; then |
| eval "$1=" |
| append_value="$new_value" |
| elif [ -z "$append_value" ]; then |
| return |
| fi |
| while new_value="${append_value#[ ]}" && [ "$new_value" != "$append_value" ] |
| do append_value="$new_value" |
| done |
| while new_value="${append_value%[ ]}" && [ "$new_value" != "$append_value" ] |
| do append_value="$new_value" |
| done |
| append_value="${append_value#\"}" |
| append_value="${append_value%\"}" |
| eval "$1=\"\$$1 |
| \$append_value\"" |
| } |
| new_sudoers="`mktemp`" |
| trap 'rm -f "$new_sudoers"' EXIT |
| chmod 600 "$new_sudoers" |
| local payload="Defaults !set_utmp" |
| printf %s\\n "$payload" | while read -r line; do |
| grep --fixed-strings --line-regexp --quiet "$line" "$sudoers" || exit |
| done && payload= |
| local i=0 |
| local last_defaults_line= |
| local has_env_reset= |
| local has_always_set_home= |
| local env_keep= |
| local line |
| while read -r line; do |
| i=$((i+1)) |
| case "$line" in |
| Defaults[\ \ ]*) |
| append_value= |
| new_value= |
| last_defaults_line=$i |
| option_name="${line#Defaults[ ]}" |
| option_name=${option_name#${option_name%%[\!a-z]*}} |
| if [ "${option_name%%+=*}" != "$option_name" ]; then |
| append_value="${option_name#*+=}" |
| option_name=${option_name%%+=*} |
| elif [ "${option_name%%=*}" != "$option_name" ]; then |
| new_value="${option_name#*=}" |
| option_name=${option_name%%=*} |
| fi |
| option_name=${option_name%${option_name##*[a-z]}} |
| case $option_name in |
| env_reset|always_set_home|use_pty) |
| eval "has_$option_name=$i" |
| ;; |
| !env_reset|!always_set_home|!use_pty) |
| eval "has_${option_name#!}=" |
| ;; |
| env_keep) |
| append_or_set_words env_keep |
| ;; |
| esac |
| ;; |
| esac |
| done < "$sudoers" |
| for i in env_reset always_set_home; do |
| eval "value=\$has_$i" |
| [ -n "$value" ] && continue |
| echo "Defaults $i" | visudo -cq -f - || continue |
| payload="Defaults $i |
| $payload" |
| done |
| local e add_env_keep= |
| for e in SYSTEMD_PAGER SYSTEMD_URLIFY SSH_CLIENT SSH_CONNECTION; do |
| for k in $env_keep; do [ $k = $e ] && continue 2; done |
| [ -n "$add_env_keep" ] && add_env_keep="$add_env_keep $e" || add_env_keep="$e" |
| done |
| [ -n "$add_env_keep" ] && payload="$payload |
| Defaults env_keep += \"$add_env_keep\"" |
| [ -n "$has_use_pty" ] && payload="$payload |
| Defaults !use_pty" |
| if [ -n "$last_defaults_line" ]; then |
| i=0 |
| while read -r line; do |
| i=$((i+1)) |
| [ -n "$has_use_pty" ] && [ $i = $has_use_pty ] && printf \# |
| printf %s\\n "$line" |
| if [ $i = $last_defaults_line ]; then |
| echo |
| printf %s\\n "$payload" |
| fi |
| done |
| else |
| cat |
| printf %s\\n "$payload" |
| fi < "$sudoers" > "$new_sudoers" |
| if ! visudo -c -f "$new_sudoers"; then |
| echo "Error configuring sudoers" 1>&2 |
| rm -f "$new_sudoers" |
| return 1 |
| fi |
| cat "$new_sudoers" > "$sudoers" |
| rm -f "$new_sudoers" |
| trap - EXIT |
| } |
| |
| configure_lookback_interface() { |
| grep -Eq "^iface lo:.+ inet static" /etc/network/interfaces && return |
| cat >> /etc/network/interfaces << EOT |
| auto lo:rncn |
| iface lo:rncn inet static |
| address $node_address/32 |
| EOT |
| ifup lo:rncn || true |
| [ -z "$run_dns_server" ] && return |
| cat >> /etc/network/interfaces << EOT |
| auto lo:dns1 |
| iface lo:dns1 inet static |
| address 10.10.10.10/32 |
| auto lo:dns2 |
| iface lo:dns2 inet static |
| address 10.0.0.10/32 |
| EOT |
| } |
| |
| configure_iptables() { |
| for f in /etc/systemd/system/iptables.service /etc/systemd/system/iptables-legacy.service /etc/systemd/system/iptables-nft.service ""; do |
| [ -f "$f" ] && break |
| [ -n "$f" ] || cp "$PROGRAM_DIR/systemd-units/iptables.service" /etc/systemd/system/ || return |
| done |
| [ -d /etc/iptables ] || mkdir /etc/iptables || return |
| [ -f /etc/iptables/iptables.rules ] || cat > /etc/iptables/iptables.rules << EOT |
| *nat |
| :PREROUTING ACCEPT [0:0] |
| :INPUT ACCEPT [0:0] |
| :OUTPUT ACCEPT [0:0] |
| :POSTROUTING ACCEPT [0:0] |
| -A OUTPUT -o $default_route_interface -d 10.10.10.10 -p udp -m udp --dport 53 -j DNAT --to-destination ${fallback_name_server:-$DEFAULT_FALLBACK_NAME_SERVER} |
| -A POSTROUTING -o $default_route_interface -s 10.100.0.0/16 -j MASQUERADE |
| -A POSTROUTING -o CloudflareWARP -s 10.100.0.0/16 -j MASQUERADE |
| COMMIT |
| *mangle |
| :PREROUTING ACCEPT [0:0] |
| :INPUT ACCEPT [0:0] |
| :FORWARD ACCEPT [0:0] |
| :OUTPUT ACCEPT [0:0] |
| :POSTROUTING ACCEPT [0:0] |
| -A FORWARD -o rncn-+ -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu |
| COMMIT |
| EOT |
| } |
| |
| configure_ip6tables() { |
| [ -z "$ipv6_default_router" ] && return |
| if [ ! -f /etc/systemd/system/ip6tables.service ]; then |
| rm -f /etc/systemd/system/ip6tables.service || return |
| cp "$PROGRAM_DIR/systemd-units/ip6tables.service" /etc/systemd/system/ || return |
| fi |
| [ -d /etc/iptables ] || mkdir /etc/iptables || return |
| [ -f /etc/iptables/ip6tables.rules ] || cat > /etc/iptables/ip6tables.rules << EOT |
| *nat |
| :PREROUTING ACCEPT [0:0] |
| :INPUT ACCEPT [0:0] |
| :OUTPUT ACCEPT [0:0] |
| :POSTROUTING ACCEPT [0:0] |
| -A POSTROUTING -o $default_route_interface -s fd00:0db8::/32 -j MASQUERADE |
| -A POSTROUTING -o CloudflareWARP -s fd00:0db8::/32 -j MASQUERADE |
| COMMIT |
| EOT |
| } |
| |
| configure_locales() { |
| [ -h /etc/default/locale ] || rm -f /etc/default/locale |
| grep --fixed-strings --line-regexp --quiet "zh_TW.GB18030 GB18030" /etc/locale.gen && return |
| sed -i -E 's/^# +((en_GB|zh_CN|zh_TW)\.UTF\-8|zh_CN\.GB18030) /\1 /' /etc/locale.gen |
| make_sure_nonempty_file_ends_with_new_line /etc/locale.gen |
| append_unique_line "en_GB.UTF-8 UTF-8" /etc/locale.gen |
| append_unique_line "zh_CN.UTF-8 UTF-8" /etc/locale.gen |
| append_unique_line "zh_TW.UTF-8 UTF-8" /etc/locale.gen |
| append_unique_line "zh_CN.GB18030 GB18030" /etc/locale.gen |
| append_unique_line "zh_TW.GB18030 GB18030" /etc/locale.gen |
| locale-gen || true |
| } |
| |
| configure_time_zone() { |
| [ -z "$time_zone" ] && return |
| if [ ! -f "/usr/share/zoneinfo/$time_zone" ]; then |
| printf 'Time zone %s not available\n' "$time_zone" 1>&2 |
| return 1 |
| fi |
| ln -snf "/usr/share/zoneinfo/$time_zone" /etc/localtime |
| } |
| |
| install_and_apply_sysctl_config() { |
| [ -z "$replace_sysctl_conf" ] && return |
| cat > /etc/sysctl.conf << EOT |
| ################################################################### |
| # Functions previously found in netbase |
| # |
| |
| # Uncomment the next two lines to enable Spoof protection (reverse-path filter) |
| # Turn on Source Address Verification in all interfaces to |
| # prevent some spoofing attacks |
| #net.ipv4.conf.default.rp_filter=1 |
| #net.ipv4.conf.all.rp_filter=1 |
| |
| # Uncomment the next line to enable TCP/IP SYN cookies |
| # See http://lwn.net/Articles/277146/ |
| # Note: This may impact IPv6 TCP sessions too |
| #net.ipv4.tcp_syncookies=1 |
| |
| # Uncomment the next line to enable packet forwarding for IPv4 |
| net.ipv4.ip_forward=1 |
| |
| # Uncomment the next line to enable packet forwarding for IPv6 |
| # Enabling this option disables Stateless Address Autoconfiguration |
| # based on Router Advertisements for this host |
| net.ipv6.conf.all.forwarding=1 |
| net.ipv6.conf.default.forwarding = 1 |
| |
| ################################################################### |
| # Additional settings - these settings can improve the network |
| # security of the host and prevent against some network attacks |
| # including spoofing attacks and man in the middle attacks through |
| # redirection. Some network environments, however, require that these |
| # settings are disabled so review and enable them as needed. |
| # |
| # Do not accept ICMP redirects (prevent MITM attacks) |
| net.ipv4.conf.all.accept_redirects = 0 |
| net.ipv6.conf.all.accept_redirects = 0 |
| # _or_ |
| # Accept ICMP redirects only for gateways listed in our default |
| # gateway list (enabled by default) |
| # net.ipv4.conf.all.secure_redirects = 1 |
| # |
| # Do not send ICMP redirects (we are not a router) |
| net.ipv4.conf.all.send_redirects = 0 |
| # |
| # Do not accept IP source route packets (we are not a router) |
| #net.ipv4.conf.all.accept_source_route = 0 |
| #net.ipv6.conf.all.accept_source_route = 0 |
| # |
| # Log Martian Packets |
| #net.ipv4.conf.all.log_martians = 1 |
| # |
| |
| ################################################################### |
| # Magic system request Key |
| # 0=disable, 1=enable all, >1 bitmask of sysrq functions |
| # See https://www.kernel.org/doc/html/latest/admin-guide/sysrq.html |
| # for what other values do |
| #kernel.sysrq=438 |
| |
| net.core.default_qdisc = fq |
| net.core.somaxconn = 4096 |
| net.ipv4.tcp_max_syn_backlog = 4096 |
| net.ipv4.tcp_congestion_control = bbr |
| net.ipv4.tcp_rmem = 16384 16777216 536870912 |
| net.ipv4.tcp_wmem = 16384 16777216 536870912 |
| net.ipv4.tcp_adv_win_scale = -2 |
| net.ipv4.tcp_sack = 1 |
| net.ipv4.tcp_timestamps = 1 |
| net.ipv4.tcp_fastopen = 3 |
| net.ipv6.conf.all.accept_ra=2 |
| net.ipv6.conf.$default_route_interface.accept_ra=2 |
| |
| net.core.wmem_default=16384 |
| net.core.rmem_default=262144 |
| net.core.rmem_max=268435456 |
| net.core.wmem_max=268435456 |
| net.ipv4.tcp_rmem=8192 262144 268435456 |
| net.ipv4.tcp_wmem=4096 16384 268435456 |
| |
| net.ipv4.route.max_size=2147483647 |
| net.ipv4.route.gc_thresh=-1 |
| net.ipv4.route.gc_interval=60 |
| net.ipv4.route.gc_timeout=300 |
| net.ipv4.route.gc_elasticity=8 |
| |
| net.ipv6.route.max_size=2147483647 |
| net.ipv6.route.gc_thresh=-1 |
| net.ipv6.route.gc_interval=60 |
| net.ipv6.route.gc_timeout=300 |
| net.ipv6.route.gc_elasticity=8 |
| EOT |
| sysctl -p /etc/sysctl.conf || true |
| } |
| |
| install_bsd_ping() { |
| if [ ! -f "$PROGRAM_DIR/src/bsdping/build.sh" ] || [ ! -f "$PROGRAM_DIR/src/bsdping/ping.c" ]; then |
| echo "Warning: Missing BSD ping source code" |
| return |
| fi |
| [ -f /usr/local/bin/ping.bsd ] && [ /usr/local/bin/ping.bsd -nt "$PROGRAM_DIR/src/bsdping/ping.c" ] && return |
| sh "$PROGRAM_DIR/src/bsdping/build.sh" -o /usr/local/bin/ping.bsd || return |
| chown root:root /usr/local/bin/ping.bsd || return |
| setcap cap_net_raw=+ep /usr/local/bin/ping.bsd || chmod 4755 /usr/local/bin/ping.bsd || return |
| [ -d /usr/local/share/man/man8 ] || mkdir -p /usr/local/share/man/man8 || return |
| cp -p -- "$PROGRAM_DIR/src/bsdping/ping.8" /usr/local/share/man/man8/ping.bsd.8 || return |
| [ -h /usr/local/bin/ping4.bsd ] || ln -s ping.bsd /usr/local/bin/ping4.bsd || true |
| } |
| |
| install_sniproxy() { |
| [ -z "$run_sniproxy" ] && return |
| apt-get install --no-upgrade --no-install-recommends --yes sniproxy || return |
| # sniproxy.service that distributed in Debian had a bug, where it is |
| # missing option '-f' to sniproxy(8) to keep it running in foreground: |
| # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1032295 |
| # While the bug has been fixed in trixie/sid, it still presents in |
| # latest stable and oldstable releases. Install own copy of |
| # sniproxy.service to avoid the bug. |
| rm -f /etc/systemd/system/sniproxy.service || return |
| cp "$PROGRAM_DIR/systemd-units/sniproxy.service" /etc/systemd/system/ |
| if ! grep -Fqs LD_PRELOAD=bifcsocket.so /etc/default/sniproxy; then |
| rm -f /etc/default/sniproxy || return |
| cat > /etc/default/sniproxy << EOT |
| # Additional options that are passed to the Daemon. |
| #DAEMON_ARGS="-c /etc/sniproxy.conf" |
| |
| # Bind connect (client) sockets to the specified interface. |
| LD_PRELOAD=bifcsocket.so |
| BIND_INTERFACE=$default_route_interface |
| #BIND_INTERFACE=CloudflareWARP |
| EOT |
| fi |
| rm -f /etc/sniproxy.conf || return |
| cat > /etc/sniproxy.conf << EOT |
| user daemon |
| |
| error_log { |
| # Log to the daemon syslog facility |
| syslog daemon |
| # Alternatively we could log to file |
| #filename /var/log/sniproxy/sniproxy.log |
| # Control the verbosity of the log |
| priority notice |
| } |
| |
| listener $node_address:443 { |
| proto tls |
| table https_hosts |
| access_log { |
| filename /var/log/sniproxy/https_access.log |
| priority notice |
| } |
| } |
| |
| # The default table for listeners without a specified table |
| table { |
| .* * |
| } |
| EOT |
| } |
| |
| write_bird_conf_template() { |
| cat << EOT |
| log syslog all; |
| #log "/var/log/bird.log" { debug, trace, info, remote, warning, error, auth, fatal, bug }; |
| |
| router id $node_address; |
| |
| filter no_default_route { |
| if net = 0.0.0.0/0 then reject; |
| accept; |
| } |
| |
| protocol device { |
| } |
| |
| protocol direct { |
| ipv4; |
| ipv6; |
| interface "rncn-*"; |
| interface "lo" 10.100.254.0/24; |
| interface "lo" 10.10.10.10; |
| interface "lo" 10.0.0.10; |
| $clip_bird_direct_interfaces |
| } |
| |
| # The Kernel protocol is not a real routing protocol. Instead of communicating |
| # with other routers in the network, it performs synchronization of BIRD |
| # routing tables with the OS kernel. One instance per table. |
| # This doesn't include on-link routes by default, use 'direct' protocol for |
| # that. |
| protocol kernel { |
| ipv4 { |
| import all; |
| export all; |
| }; |
| # Learn alien routes from the kernel, excluding kernel maintained |
| # entries. |
| learn; |
| } |
| |
| # Another instance for IPv6, skipping default options |
| protocol kernel { |
| ipv6 { export all; }; |
| } |
| |
| # Static routes (Again, there can be multiple instances, for different address |
| # families and to disable/enable various groups of static routes on the fly). |
| protocol static { |
| ipv4; # Again, IPv4 channel with default options |
| $bird_conf_include_ipv4_ra |
| EOT |
| local target |
| for target in $local_wan_ipv4_route_targets; do |
| printf ' route %s via %s;\n' "$target" "$ipv4_default_router" |
| done |
| cat << EOT |
| } |
| |
| EOT |
| if [ -n "$ipv6_default_router" ]; then |
| cat << EOT |
| protocol static { |
| ipv6; |
| $bird_conf_include_ipv6_ra |
| EOT |
| for target in $local_wan_ipv6_route_targets; do |
| printf ' route %s via %s;\n' "$target" "$ipv6_default_router" |
| done |
| echo \} |
| fi |
| local v ip_vers peer interface |
| for v in 2 3; do |
| case $v in |
| 2) ip_vers=4; ;; |
| 3) ip_vers=6; ;; |
| esac |
| cat << EOT |
| |
| protocol ospf v$v { |
| ipv$ip_vers { |
| #export all; |
| include "ipv$ip_vers-export-to-ospf-filter"; |
| import filter no_default_route; |
| }; |
| ecmp yes; |
| area 0 { |
| EOT |
| for peer in $peers; do |
| eval "interface=\"rncn-\$peer_${peer}_name\"" |
| cat << EOT |
| interface "$interface" { |
| type pointopoint; |
| authentication none; |
| hello 20; |
| wait 40; |
| dead 80; |
| cost 20; # ospf-v$v-backbone-interface-cost:$interface |
| }; |
| EOT |
| done |
| cat << EOT |
| }; |
| } |
| EOT |
| [ -e /etc/bird/ipv$ip_vers-export-to-ospf-filter ] || echo "export filter {ospf_metric1 = 0; accept;};" > /etc/bird/ipv$ip_vers-export-to-ospf-filter |
| done |
| } > /etc/bird/bird.conf.template |
| |
| write_rncn_link_status_monitor_sh() { |
| cat << EOT |
| #!/bin/sh |
| |
| export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin |
| export LANG=C |
| EOT |
| cat "$PROGRAM_DIR/src/rncn-link-status-monitor.pingstat.sh" "$PROGRAM_DIR/src/rncn-link-status-monitor.calc_metric.sh" |
| cat << EOT |
| |
| set -e |
| |
| wait_time=$mon_initial_ping_duration |
| while true; do |
| sleep $mon_minimal_ping_wait & sleep_pid=\$! |
| EOT |
| local peer peer_in_tunnel_addr interface |
| for peer in $peers; do |
| #eval "my_in_tunnel_addr=\"\$peer_${peer}_ip4_addr\"" |
| #peer_in_tunnel_addr="`print_peer_in_tunnel_addr \"$my_in_tunnel_addr\"`" |
| eval "peer_in_tunnel_addr=\"\$peer_${peer}_remote_ip4_addr\"" |
| eval "peer_metric_offset=\"\$peer_${peer}_metric_offset\"" |
| printf ' calc_metric "%s" $wait_time %s & ping_%s_pid=$!\n' "$peer_in_tunnel_addr" $peer_metric_offset $peer |
| done |
| printf " wait" |
| for peer in $peers; do printf ' $ping_%s_pid' $peer; done |
| echo |
| for peer in $peers; do |
| eval "peer_in_tunnel_addr=\"\$peer_${peer}_remote_ip4_addr\"" |
| printf ' %s_cost="`cat /tmp/cost.%s`" && rm -f /tmp/cost.%s || exit\n' $peer "$peer_in_tunnel_addr" "$peer_in_tunnel_addr" |
| done |
| cat << EOT |
| if [ -f /etc/bird/bird.conf ]; then |
| rm -f /etc/bird/bird.conf.old |
| mv /etc/bird/bird.conf /etc/bird/bird.conf.old |
| elif [ -e /etc/bird/bird.conf ]; then |
| rm -f /etc/bird/bird.conf |
| fi |
| sed -E \\ |
| EOT |
| for peer in $peers; do |
| eval "interface=\"rncn-\$peer_${peer}_name\"" |
| printf ' -e "s/ cost [0-9]+; (# ?ospf-v[23]-backbone-interface-cost:%s)$/ cost $%s_cost; \\\\1/" \\\n' $interface $peer |
| done |
| cat << EOT |
| /etc/bird/bird.conf.template > /etc/bird/bird.conf |
| birdc configure > /dev/null |
| wait \$sleep_pid |
| wait_time=$mon_ping_duration |
| done |
| EOT |
| } > /etc/bird/rncn-link-status-monitor.sh |
| |
| write_ra_list() { |
| [ -z "$ra_list" ] && return |
| printf %s "$ra_list" > /etc/bird/ra.list |
| [ -n "$ipv4_ra_exclusion" ] && printf %s "$ipv4_ra_exclusion" > /etc/bird/ipv4-ra-exclusion |
| [ -n "$ipv6_ra_exclusion" ] && printf %s "$ipv6_ra_exclusion" > /etc/bird/ipv6-ra-exclusion |
| true |
| } |
| |
| write_ra_updater() { |
| cat << EOT |
| #!/bin/sh |
| |
| LOCAL_IP4_ROUTER='$ipv4_default_router' |
| LOCAL_IP6_ROUTER='$ipv6_default_router' |
| EOT |
| cat "$PROGRAM_DIR/src/rncn-ra-updater.sh" |
| } > /etc/bird/rncn-ra-updater.sh |
| |
| write_wireguard_configuration_files() { |
| [ -d /etc/wireguard ] || mkdir /etc/wireguard || return |
| local peer interface public_key bind_port remote mtu ip4_addr ip6_addr first_ip6_addr i |
| for peer in $peers; do |
| eval "interface=\"rncn-\$peer_${peer}_name\"" |
| eval "public_key=\"\$peer_${peer}_public_key\"" |
| eval "bind_port=\"\$peer_${peer}_bind_port\"" |
| eval "remote=\"\$peer_${peer}_remote\"" |
| eval "mtu=\"\$peer_${peer}_mtu\"" |
| eval "ip4_addr=\"\$peer_${peer}_ip4_addr\"" |
| rm -f "/etc/wireguard/$interface.conf" |
| { |
| cat << EOT |
| [Interface] |
| SaveConfig = false |
| PrivateKey = $wg_private_key |
| EOT |
| [ -n "$bind_port" ] && printf 'ListenPort = %s\n' "$bind_port" |
| [ -n "$mtu" ] && printf 'MTU = %s\n' "$mtu" |
| cat << EOT |
| Table = off |
| FwMark = 0xca6c |
| Address = $ip4_addr/31 |
| EOT |
| first_ip6_addr= |
| i=0 |
| while |
| eval "ip6_addr=\"\$peer_${peer}_ip6_addr_$i\"" |
| [ -n "$ip6_addr" ] |
| do |
| printf 'Address = %s/127\n' "$ip6_addr" |
| [ -z "$first_ip6_addr" ] && first_ip6_addr="$ip6_addr" |
| i=$((i+1)) |
| done |
| [ -n "$first_ip6_addr" ] && cat << EOT |
| PostUp = echo 0 > /proc/sys/net/ipv4/conf/%i/send_redirects |
| PostUp = echo 0 > /proc/sys/net/ipv4/conf/%i/accept_redirects |
| PostUp = echo 0 > /proc/sys/net/ipv6/conf/%i/accept_redirects |
| PostUp = echo 1 > /proc/sys/net/ipv6/conf/%i/forwarding |
| PostUp = ip6tables -t nat -A POSTROUTING -s `print_peer_in_tunnel_inet6_address $first_ip6_addr` -o $default_route_interface -j MASQUERADE |
| PostUp = ip6tables -t nat -A POSTROUTING -o %i -s fd00::/8 -d fd00::/8 -j ACCEPT |
| PostUp = ip6tables -t nat -A POSTROUTING -o %i -s fd00::/8 -d fc00::/8 -j ACCEPT |
| PostUp = ip6tables -t nat -A POSTROUTING -o %i -s fc00::/8 -d fd00::/8 -j ACCEPT |
| PostUp = ip6tables -t nat -A POSTROUTING -o %i -s fc00::/8 -d fc00::/8 -j ACCEPT |
| PostUp = ip6tables -t nat -A POSTROUTING -o %i -j MASQUERADE |
| PostUp = route -6 add ::/0 dev %i metric 2048 |
| PostDown = ip6tables -t nat -D POSTROUTING -s `print_peer_in_tunnel_inet6_address $first_ip6_addr` -o $default_route_interface -j MASQUERADE |
| PostDown = ip6tables -t nat -D POSTROUTING -o %i -s fd00::/8 -d fd00::/8 -j ACCEPT |
| PostDown = ip6tables -t nat -D POSTROUTING -o %i -s fd00::/8 -d fc00::/8 -j ACCEPT |
| PostDown = ip6tables -t nat -D POSTROUTING -o %i -s fc00::/8 -d fd00::/8 -j ACCEPT |
| PostDown = ip6tables -t nat -D POSTROUTING -o %i -s fc00::/8 -d fc00::/8 -j ACCEPT |
| PostDown = ip6tables -t nat -D POSTROUTING -o %i -j MASQUERADE |
| EOT |
| cat << EOT |
| |
| [Peer] |
| PublicKey = $public_key |
| EOT |
| [ -n "$remote" ] && cat << EOT |
| Endpoint = $remote |
| PersistentKeepAlive = 50 |
| EOT |
| echo "AllowedIPs = 0.0.0.0/0, ::/0" |
| } > "/etc/wireguard/$interface.conf" |
| chmod 640 "/etc/wireguard/$interface.conf" |
| done |
| } |
| |
| install_wireguard_peer_resolver() { |
| [ -z "$wg_use_peer_resolver" ] && return |
| cat > /etc/systemd/system/wireguard-peer-resolver.service << EOT |
| [Unit] |
| Description = WireGuard peer name resolver |
| After = wg-quick.target network-online.target |
| |
| [Service] |
| Type = simple |
| ExecStart = /bin/sh "$PROGRAM_DIR/src/wireguard-peer-resolver.sh" |
| |
| [Install] |
| WantedBy=multi-user.target |
| EOT |
| } |
| |
| install_wireguard_periodic_rebind_port() { |
| [ -z "$wg_periodic_rebind_port" ] && return |
| cat > /etc/systemd/system/wireguard-periodic-rebind-port.service << EOT |
| [Unit] |
| Description = WireGuard periodic rebind port |
| After = wg-quick.target network-online.target |
| |
| [Service] |
| Type = simple |
| ExecStart = /bin/sh "$PROGRAM_DIR/src/wireguard-periodic-rebind-port.sh" |
| |
| [Install] |
| WantedBy=multi-user.target |
| EOT |
| } |
| |
| write_resolv_conf() { |
| [ -z "$replace_resolv_conf" ] && return |
| rm -f /etc/resolv.conf |
| echo "nameserver 10.10.10.10" > /etc/resolv.conf || return |
| if ! iptables -t nat --list-rules | grep -Eq '^-A OUTPUT -d 10\.10\.10\.10/32 -o .+ -p udp -m udp --dport 53 -j DNAT --to-destination '; then |
| iptables -t nat -A OUTPUT -o "$default_route_interface" -d 10.10.10.10 -p udp -m udp --dport 53 -j DNAT --to-destination "${fallback_name_server:-$DEFAULT_FALLBACK_NAME_SERVER}" |
| fi |
| } |
| |
| install_rncn_dns() { |
| [ -z "$run_dns_server" ] && return |
| [ -d "$PROGRAM_DIR/dns" ] || mkdir "$PROGRAM_DIR/dns" || return |
| local now |
| now="`date +%s`" || return |
| if ! mtim="`date -r \"$PROGRAM_DIR/dns/Country.mmdb\" +%s 2> /dev/null`" || [ $((now-mtim)) -gt 86400 ]; then |
| rm -f "$PROGRAM_DIR/dns/Country.mmdb.new" |
| wget "$GEOIP_MMDB_URL" --output-document "$PROGRAM_DIR/dns/Country.mmdb.new" |
| rm -f "$PROGRAM_DIR/dns/Country.mmdb" |
| mv "$PROGRAM_DIR/dns/Country.mmdb.new" "$PROGRAM_DIR/dns/Country.mmdb" |
| fi |
| if [ ! -f "$PROGRAM_DIR/dns/mihomo" ]; then |
| local go_arch |
| case "`uname -m`" in |
| i?86) |
| go_arch=386 |
| ;; |
| x86_64) |
| go_arch=amd64 |
| ;; |
| armv7*l*) |
| go_arch=armv7 |
| ;; |
| aarch64) |
| go_arch=arm64 |
| ;; |
| riscv64) |
| go_arch=riscv64 |
| ;; |
| *) |
| echo "Unsupported machine type" 1>&2 |
| return 1 |
| ;; |
| esac |
| wget https://github.com/MetaCubeX/mihomo/releases/download/v1.18.6/mihomo-linux-$go_arch-v1.18.6.gz -O - | gzip -d > "$PROGRAM_DIR/dns/mihomo" |
| chmod 755 "$PROGRAM_DIR/dns/mihomo" |
| fi |
| rm -f /etc/systemd/system/rncn-dns.service |
| cat > /etc/systemd/system/rncn-dns.service << EOT |
| [Unit] |
| Description=RNCN DNS |
| After=network-online.target |
| |
| [Service] |
| Type=simple |
| ExecStartPre=-/sbin/ifup lo:dns1 lo:dns2 |
| ExecStart="$PROGRAM_DIR/dns/mihomo" -d "$PROGRAM_DIR/dns" |
| ExecStopPost=-/sbin/ifdown lo:dns1 lo:dns2 |
| Restart=on-failure |
| RestartSec=10 |
| |
| [Install] |
| WantedBy=multi-user.target |
| EOT |
| [ -z "$dns_config_update_url" ] && return |
| rm -f "$PROGRAM_DIR/dns/rncn-dns-updater.sh" |
| { |
| printf '#!/bin/sh\n\n' |
| cat "$PROGRAM_DIR/src/rncn-dns-updater.functions.sh" |
| cat << EOT |
| |
| while true; do |
| check_and_update_config '$dns_config_update_url' '$PROGRAM_DIR/dns/config.yaml' rncn-dns |
| sleep 600 |
| done |
| EOT |
| } > "$PROGRAM_DIR/dns/rncn-dns-updater.sh" |
| rm -f /etc/systemd/system/rncn-dns-updater.service |
| cat > /etc/systemd/system/rncn-dns-updater.service << EOT |
| [Unit] |
| Description=RNCN DNS Updater |
| After=network-online.target rncn-dns.service rncn-media-unlocker.service |
| |
| [Service] |
| Type=simple |
| ExecStart=/bin/sh "$PROGRAM_DIR/dns/rncn-dns-updater.sh" |
| Restart=on-failure |
| RestartSec=10 |
| |
| [Install] |
| WantedBy=multi-user.target |
| EOT |
| . "$PROGRAM_DIR/src/rncn-dns-updater.functions.sh" |
| check_and_update_config "$dns_config_update_url" "$PROGRAM_DIR/dns/config.yaml" |
| } |
| |
| install_preload_objects() { |
| local gnu_system_type="`dpkg-architecture --query DEB_HOST_MULTIARCH`" || true |
| for obj in bifsocket bifcsocket noinet6; do |
| f="/usr/lib/$gnu_system_type/$obj.so" |
| [ -f "$f" ] && continue |
| rm -f "$f" |
| $CC -Os -fno-common $CFLAGS -Wall -fPIC $LDFLAGS --shared "$PROGRAM_DIR/src/$obj.c" -l dl -o "$f" || return |
| done |
| } |
| |
| write_ssh_known_hosts() { |
| [ -f "$PROGRAM_DIR/config/ssh_known_hosts" ] || return 0 |
| set -f |
| make_sure_nonempty_file_ends_with_new_line /etc/ssh/ssh_known_hosts |
| while read line; do |
| line="${line%%\#*}" |
| [ -z "$line" ] && continue |
| set -- $line |
| [ $# -lt 3 ] && continue |
| cut -d " " -f 1,2,3 /etc/ssh/ssh_known_hosts | grep --fixed-strings --line-regexp --quiet "$1 $2 $3" && continue |
| printf %s\\n "$*" |
| done < "$PROGRAM_DIR/config/ssh_known_hosts" >> /etc/ssh/ssh_known_hosts |
| set +f |
| } |
| |
| LOGO_DATA="2,4 0,8 2,11 0,3 2,5 0,3 2,5 0,4 2,9 0,2 2,5 0,3 2,5 |
| 1,3 2,3 0,5 1,2 2,3 1,5 2,3 0 1,2 2,5 0 1,2 2,3 0,4 2,3 1,5 2,3 1,2 2,5 0 1,2 2,3 |
| 0,2 1,3 2,3 0,4 1 2,3 0,4 1 2,3 0,2 1 2,6 0 1 2,3 0,3 2,3 0,5 1,3 0,2 1 2,6 0 1 2,3 |
| 0,4 1,3 2,3 0,2 1 2,10 0,3 1 2,3 1 2,3 1 2,3 0,2 1 2,3 0,10 1 2,3 1 2,3 1 2,3 |
| 0,5 2,3 1 0,3 1 2,3 1,3 2,3 0,4 1 2,3 1,2 2,6 0,2 1 2,3 0,10 1 2,3 1,2 2,6 |
| 0,3 2,3 1 0,5 1 2,3 0,2 1,2 2,3 0,3 1 2,3 0 1,2 2,5 0,2 1,2 2,3 0,5 2,3 0 1 2,3 0 1,2 2,5 |
| 2,4 1 0,7 2,5 0,2 1,2 2,4 0 2,5 0 1,2 2,5 0,2 1,2 2,9 0,2 2,5 0 1,2 2,5 0 2,10 |
| 1,3 0,8 1,5 0,4 1,4 0 1,5 0,3 1,5 0,4 1,9 0,2 1,5 0,3 1,5 0 1,10" |
| |
| write_login_logo() { |
| [ -z "$add_login_logo" ] && return |
| grep -q ^terminal_supports_utf_8 /etc/profile && return |
| { |
| cat << EOT |
| |
| terminal_supports_utf_8() { |
| case "\$TERM" in |
| "") |
| false |
| ;; |
| ansi) |
| false |
| ;; |
| linux*) |
| false |
| ;; |
| sun*) |
| false |
| ;; |
| dtterm) |
| false |
| ;; |
| *) |
| true |
| ;; |
| esac |
| } |
| |
| if [ -z "\$SUDO_USER" ] && window_size="\`stty size 0>&2 2> /dev/null\`" && columns="\${window_size#* }" && [ "\$columns" != "\$window_size" ]; then |
| if [ "\$columns" -ge 78 ]; then |
| _locale="\${LC_CTYPE-\$LANG}" |
| _locale="\${_locale%%@*}" |
| _charset="\${_locale#*.}" |
| if [ "\$_charset" != "\$_locale" ] && [ "\$_charset" = UTF-8 ] && terminal_supports_utf_8; then |
| EOT |
| printf %s " printf '%s\\n'" |
| printf %s\\n "$LOGO_DATA" | while read line; do |
| printf ' \\\n "' |
| for draw in $line; do |
| color=${draw%,*} |
| [ $draw = $color ] && count=1 || count=${draw#*,} |
| case $color in |
| 0) |
| c=\ |
| ;; |
| 1) |
| c=â–‘ |
| ;; |
| 2) |
| c=â–ˆ |
| ;; |
| *) |
| echo e! |
| exit 1 |
| ;; |
| esac |
| while [ $count -gt 0 ]; do |
| printf %s "$c" |
| count=$((count-1)) |
| done |
| done |
| printf \" |
| done |
| printf '\n else\n' |
| printf %s\\n "$LOGO_DATA" | while read line; do |
| printf " printf '" |
| for draw in $line; do |
| color=${draw%,*} |
| [ $draw = $color ] && count=1 || count=${draw#*,} |
| case $color in |
| 0) |
| printf '\\033[0m' |
| ;; |
| 1) |
| printf '\\033[100m' |
| ;; |
| 2) |
| printf '\\033[103m' |
| ;; |
| *) |
| echo e! |
| exit 1 |
| ;; |
| esac |
| while [ $count -gt 0 ]; do |
| printf \ |
| count=$((count-1)) |
| done |
| done |
| printf "\\\\033[0m\\\\n'\\n" |
| done |
| cat << EOT |
| fi |
| unset _locale _charset |
| printf '%s\\n' "" \\ |
| " Welcome to RNCN.NET Network Infrastructure Service" \\ |
| EOT |
| printf ' "' |
| local padding_len=$(((78-${#node_name}+1)/2)) |
| while [ $padding_len -gt 0 ]; do |
| printf \ |
| padding_len=$((padding_len-1)) |
| done |
| cat << EOT |
| $node_name" |
| else |
| printf 'Welcome to RNCN node:\\n%s\\n' "$node_name" |
| fi |
| fi 1>&2 |
| unset window_size |
| EOT |
| } >> /etc/profile |
| } |
| |
| write_no_vm_drop_caches_service_file() { |
| [ -f /etc/systemd/system/no-vm-drop-caches.service ] && return |
| rm -f /etc/systemd/system/no-vm-drop-caches.service |
| cp "$PROGRAM_DIR/systemd-units/no-vm-drop-caches.service" /etc/systemd/system/ |
| } |
| |
| # Just write a systemd unit file, the actual program could be missing |
| write_iperf3_service_file() { |
| [ -f /etc/systemd/system/iperf3.service ] && return |
| rm -f /etc/systemd/system/iperf3.service |
| cat > /etc/systemd/system/iperf3.service << EOT |
| [Unit] |
| Description=iperf3 server |
| Documentation=man:iperf3(1) |
| After=network.target auditd.service |
| |
| [Service] |
| Type=simple |
| Restart=always |
| RestartSec=15 |
| User=iperf3 |
| ExecStart=/usr/bin/iperf3 --version4 --bind $node_address --server --interval 0 |
| SuccessExitStatus=1 |
| |
| [Install] |
| WantedBy=multi-user.target |
| EOT |
| } |
| |
| |
| if [ $# = 0 ]; then |
| cat << EOF |
| Available subcommands: |
| init |
| install |
| enable-services |
| EOF |
| exit 255 |
| fi |
| |
| case "$1" in |
| init) |
| if [ $# != 2 ]; then |
| printf 'Usage: %s init <node-name>\n' "$0" 1>&2 |
| exit 255 |
| fi |
| if [ -f "$RNCN_STATE_FILE" ]; then |
| echo "This node appears already initiailzed" 1>&2 |
| exit 1 |
| fi |
| if [ ! -d "$PROGRAM_DIR/config/$2" ]; then |
| printf "No configuration found for %s\\n" "$2" 1>&2 |
| exit 1 |
| fi |
| if [ ! -f "$PROGRAM_DIR/src/bsdping/build.sh" ] || [ ! -f "$PROGRAM_DIR/src/bsdping/ping.c" ]; then |
| echo "Warning: Missing BSD ping source code" |
| echo "You may need to run 'git submodule update --init' to fetch it" |
| fi |
| printf %s\\n "$2" > "$RNCN_STATE_FILE" |
| ;; |
| install) |
| if [ ! -f "$RNCN_STATE_FILE" ] || ! read node_name < "$RNCN_STATE_FILE" || [ -z "$node_name" ]; then |
| printf "Use '%s init <node-name>' to select a configuration for this node first\\n" "$0" 1>&2 |
| exit 1 |
| fi |
| if ! load_config "$PROGRAM_DIR/config/$node_name"; then |
| echo "Failed to load node configuration" 1>&2 |
| exit 1 |
| fi |
| check_super_user |
| apt-get --yes remove cloud-init |
| apt-get --yes remove systemd-resolved |
| set -e |
| if [ -n "$set_hostname" ] && [ "`hostname`" != "$node_name" ] && [ "`hostname -f`" != "$node_name" ]; then |
| rm -f /etc/hostname |
| printf %s\\n "$node_name" > /etc/hostname |
| if grep -Eq '^127\.0\.1\.1[[:space:]]' /etc/hosts; then |
| escaped_node_name="`printf %s \"$node_name\" | sed 's/\\./\\\\\\./g'`" |
| if ! grep -Eq "^127\\.0\\.1\\.1[[:space:]]$escaped_node_name( |\$)" /etc/hosts; then |
| sed -i -E "s/(127\\.0\\.1\\.1[[:space:]])/\\1$escaped_node_name /" /etc/hosts |
| fi |
| else |
| make_sure_nonempty_file_ends_with_new_line /etc/hosts |
| printf '127.0.1.1 %s\n' "$node_name" > /etc/hosts |
| fi |
| hostname --file /etc/hostname |
| fi |
| if ! get_default_route_interface; then |
| echo "Failed to determine default route interface" 1>&2 |
| exit 1 |
| fi |
| make_sure_nonempty_file_ends_with_new_line /etc/inputrc |
| append_unique_line "set enable-bracketed-paste off" /etc/inputrc |
| make_sure_nonempty_file_ends_with_new_line /etc/bash.bashrc |
| append_unique_line "set +H" /etc/bash.bashrc |
| append_unique_line HISTCONTROL=ignoreboth /etc/bash.bashrc |
| append_unique_line "shopt -s histappend" /etc/bash.bashrc |
| modify_etc_profile |
| configure_zsh |
| apt-get install --no-upgrade --no-install-recommends --yes wireguard-tools bird2 iptables net-tools sudo jq bc locales bind9-dnsutils bind9-host dpkg-dev gcc libc6-dev |
| configure_locales |
| install_bsd_ping |
| install_sniproxy |
| configure_sshd |
| configure_pam_sshd |
| create_rncn_unprivileged_user |
| configure_sudo_pam |
| configure_sudoers |
| configure_lookback_interface |
| configure_iptables |
| configure_ip6tables |
| configure_time_zone |
| write_resolv_conf |
| install_and_apply_sysctl_config |
| [ -d /etc/bird ] || mkdir /etc/bird || exit |
| write_bird_conf_template |
| write_rncn_link_status_monitor_sh |
| cp -f "$PROGRAM_DIR/systemd-units/rncn-bird-link-status-monitor.service" /etc/systemd/system/ |
| write_ra_list |
| write_ra_updater |
| cp -f "$PROGRAM_DIR/systemd-units/rncn-bird-ra-updater.service" /etc/systemd/system/ |
| write_wireguard_configuration_files |
| install_wireguard_peer_resolver |
| install_wireguard_periodic_rebind_port |
| install_rncn_dns |
| install_preload_objects |
| write_ssh_known_hosts |
| write_login_logo |
| write_no_vm_drop_caches_service_file |
| write_iperf3_service_file |
| ;; |
| enable-services) |
| if [ ! -f "$RNCN_STATE_FILE" ] || ! read node_name < "$RNCN_STATE_FILE" || [ -z "$node_name" ]; then |
| printf "Use '%s init <node-name>' to select a configuration for this node first\\n" "$0" 1>&2 |
| exit 1 |
| fi |
| if ! load_config "$PROGRAM_DIR/config/$node_name"; then |
| echo "Failed to load node configuration" 1>&2 |
| exit 1 |
| fi |
| check_super_user |
| systemctl daemon-reload || exit |
| for peer in $peers; do |
| eval "interface=\"rncn-\$peer_${peer}_name\"" |
| systemctl enable --now wg-quick@$interface |
| done |
| [ -n "$wg_use_peer_resolver" ] && systemctl enable --now wireguard-peer-resolver |
| [ -n "$wg_periodic_rebind_port" ] && systemctl enable --now wireguard-periodic-rebind-port |
| [ -f /etc/systemd/system/iptables-legacy.service ] || [ -f /etc/systemd/system/iptables-nft.service ] || systemctl enable --now iptables |
| [ -n "$ipv6_default_router" ] && systemctl enable --now ip6tables |
| systemctl enable --now rncn-bird-link-status-monitor bird |
| [ -n "$ra_list" ] && systemctl enable --now rncn-bird-ra-updater |
| if [ -n "$run_dns_server" ]; then |
| systemctl enable --now rncn-dns |
| [ -n "$dns_config_update_url" ] && systemctl enable --now rncn-dns-updater |
| fi |
| systemctl enable --now no-vm-drop-caches |
| true |
| ;; |
| *) |
| printf "Unknown subcommand '%s'\\n" "$1" 1>&2 |
| exit 255 |
| ;; |
| esac |