diff --git a/Dockerfile b/Dockerfile index d58640a..5ade1c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -63,38 +63,30 @@ # For more information, please refer to the project repository: # https://github.com/alexzhangs/shadowsocks-libev-v2ray # -FROM shadowsocks/shadowsocks-libev:edge -# Set work directory -WORKDIR /shadowsocks-libev-v2ray +# To enable proxy at build time, use: +# docker build --build-arg https_proxy=http://host.docker.internal:$PROXY_HTTP_PORT_ON_HOST ... +ARG http_proxy https_proxy all_proxy -# Copy the current directory contents at local into the container -COPY . . - -RUN chmod +x docker-entrypoint.sh +### First stage: build environment +FROM alpine as builder -# Instal file, git, curl, and openssl -RUN apk add file git curl openssl - -# Install acme.sh -RUN curl -sL https://get.acme.sh | sh - -# Set the PATH for acme.sh -ENV PATH=$PATH:/root/.acme.sh +# Instal file, git, curl +RUN apk add file git curl -# Verify that acme.sh is installed -RUN acme.sh --version +# v2ray-plugin requires Go 1.16 +ENV GO_VERSION=1.16.10 -# Install Go 1.16 (v2ray-plugin requires Go 1.16) +# Install Go RUN <&2 + (( ret++ )) + fi + done + return $ret +} + +function main () { + declare proxy_flag=0 \ + mgt_port=6001 ss_ports=8381-8385 encrypt=aes-256-cfb domain dns dns_env \ + OPTIND OPTARG opt + + while getopts Pm:p:e:d:N:E:h opt; do + case $opt in + P) + proxy_flag=1 + ;; + m) + mgt_port=$OPTARG + ;; + p) + ss_ports=$OPTARG + ;; + e) + encrypt=$OPTARG + ;; + d) + domain=$OPTARG + ;; + N) + dns=$OPTARG + ;; + E) + dns_env=$OPTARG + ;; + *) + usage + return 255 + ;; + esac + done + + check-vars domain mgt_port ss_ports encrypt + + declare build_opts=() \ + run_opts=(--restart=always) \ + run_env_opts=(-e V2RAY=1 -e DOMAIN="$domain" -e DNS="$dns" -e DNS_ENV="$dns_env") \ + run_port_opts=(-p "$mgt_port:$mgt_port/UDP" -p "$ss_ports:$ss_ports" -p "$ss_ports:$ss_ports/UDP") \ + run_cmd_opts=( ss-manager + --manager-address "0.0.0.0:$mgt_port" + --executable /usr/local/bin/ss-server -m "$encrypt" -s 0.0.0.0 -u + --plugin v2ray-plugin --plugin-opts "server;tls;host=$domain" + ) + + if [[ $proxy_flag -eq 1 ]]; then + check-vars DOCKER_BUILD_ARGS_PROXY + # do not quote it + # shellcheck disable=SC2206 + build_opts+=($DOCKER_BUILD_ARGS_PROXY) + fi + + declare image_name=alexzhangs/shadowsocks-libev-v2ray + + declare script_dir + script_dir=$(dirname "$0") + + declare -a BUILD_OPTS RUN_OPTS + + function __build_and_run__ () { + # build the image + echo "" + echo "INFO: docker build ${BUILD_OPTS[*]}" + docker build "${BUILD_OPTS[@]}" + + # run the container + echo "" + echo "INFO: docker run ${RUN_OPTS[*]}" + docker run "${RUN_OPTS[@]}" || : + } + + declare image_tag image container + image_tag=dev-$(date +%Y%m%d-%H%M) + image="$image_name:$image_tag" + container="ss-libev-$image_tag" + BUILD_OPTS=( "${build_opts[@]}" -t "$image" -f "$script_dir/Dockerfile" "$script_dir" ) + RUN_OPTS=( "${run_opts[@]}" "${run_env_opts[@]}" "${run_port_opts[@]}" --name "$container" "$image" "${run_cmd_opts[@]}" ) + + __build_and_run__ +} + +main "$@" + +exit diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index b94602c..97ce459 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -32,12 +32,12 @@ #? The domain name for the V2Ray service. #? Please replace `v2ray.ss.yourdomain.com` with your actual domain name. #? And make sure the domain name matches the value used for `--plugin-opts` option. -#? +#? #? - DNS= #? #? Optional, used if env `V2RAY` is set, default is unset. #? Specify the for your domain to automate domain owner verification. -#? The won't be verified until the domain owner verification is triggered. +#? The Shell DNS library `acme.sh` is leveraged to achieve this. #? For the list of supported , please refer to: #? * https://github.com/acmesh-official/acme.sh/wiki/dnsapi #? @@ -49,22 +49,21 @@ #? Specify the environment variables required by the , usually for the username and token. #? The and are separated by `=`, and multiple pairs are separated by `,`. #? The and are case-sensitive. -#? The and won't be verified until the domain owner verification is triggered. #? For the required and , please refer to: #? * https://github.com/acmesh-official/acme.sh/wiki/dnsapi #? #? File: #? The following files are created by this script: #? -#? - ~/.acme-account-done +#? - ~/.acme-account-done-{DOMAIN} #? #? This file is created after the account registration with acme.sh. -#? The account registration is skipped if this file exists. +#? The account registration will be skipped if this file exists. #? -#? - ~/.acme-cert-done +#? - ~/.acme-cert-done-{DOMAIN} #? #? This file is created after the certificate is issued for the domain with acme.sh. -#? The certificate issuance is skipped if this file exists. +#? The certificate issuance will be skipped if this file exists. #? # exit on any error @@ -76,17 +75,20 @@ function usage () { } function issue-tls-cert () { - # Check if the required environment variables are set - if [[ -z $DOMAIN ]]; then - echo "FATAL: environment variable DOMAIN is not set." >&2 - exit 255 - fi + #? Description: + #? Issue a TLS certificate with acme.sh. + #? + #? Usage: + #? issue-tls-cert DOMAIN DNS DNS_ENV [--renew-hook "COMMAND"] + #? - declare acme_account_done_file=~/.acme-account-done - declare acme_cert_done_file=~/.acme-cert-done + declare domain=${1:?} dns=$2 dns_env=$3 renew_opts=("${@:4}") + + declare acme_account_done_file=~/.acme-account-done-${domain} + declare acme_cert_done_file=~/.acme-cert-done-${domain} if [[ -f $acme_cert_done_file ]]; then - echo "INFO: TLS certificate has been issued for the domain $DOMAIN." + echo "INFO: TLS certificate has been issued for the domain $domain." return fi @@ -94,32 +96,29 @@ function issue-tls-cert () { # Register an account with acme.sh if not done if [[ ! -f $acme_account_done_file ]]; then - acme.sh --register-account -m "acme@$DOMAIN" + acme.sh --register-account -m "acme@$domain" touch "$acme_account_done_file" fi - declare -a acme_common_opts=(--force-color --domain "$DOMAIN") - declare -a acme_issue_opts=("${acme_common_opts[@]}" --renew-hook reboot --dns) + declare -a acme_common_opts=(--force-color --domain "$domain") + declare -a acme_issue_opts=("${acme_common_opts[@]}" "${renew_opts[@]}" --dns) - # Setup DNS hook if DNS is set - if [[ -n $DNS ]]; then - # Check if the required environment variables are set - if [[ -z $DNS_ENV ]]; then - echo "WARNING: environment variable DNS_ENV is not set." >&2 + # Setup DNS hook if DNS_ENV is set + if [[ -n $dns ]]; then + # Check if the dns_env is set + if [[ -z $dns_env ]]; then + echo "WARNING: dns_env is not set." >&2 fi - declare -a DNS_ENVS + declare -a dns_envs # Read the DNS_ENV into an array - IFS=',' read -r -a DNS_ENVS <<< "$DNS_ENV" + IFS=',' read -r -a dns_envs <<< "$dns_env" - declare expr - # Export the DNS_ENVS - for expr in "${DNS_ENVS[@]}"; do - export "${expr:?}" - done + # Export the dns_envs + export "${dns_envs[@]}" # Issue a certificate for the domain with acme.sh, using DNS hook - acme.sh --issue "${acme_issue_opts[@]}" "$DNS" + acme.sh --issue "${acme_issue_opts[@]}" "$dns" else # Issue a certificate for the domain with acme.sh, using manual mode, ignoring the non-zero exit code acme.sh --issue "${acme_issue_opts[@]}" --yes-I-know-dns-manual-mode-enough-go-ahead-please || : @@ -136,7 +135,7 @@ function issue-tls-cert () { fi # Create a symbolic link for the certificate directory, v2ray-plugin seaches only the path without the _ecc suffix - ln -s "${DOMAIN}_ecc" "/root/.acme.sh/${DOMAIN}" + ln -s "${domain}_ecc" "/root/.acme.sh/${domain}" # Create the cert done file touch "$acme_cert_done_file" @@ -151,7 +150,8 @@ function main () { if [[ $V2RAY -eq 1 ]]; then # Issue a TLS certificate - issue-tls-cert + # shellcheck disable=SC2153 + issue-tls-cert "$DOMAIN" "$DNS" "$DNS_ENV" --renew-hook reboot fi exec "$@"