diff --git a/README.md b/README.md index 81c2483..71d2eb8 100755 --- a/README.md +++ b/README.md @@ -70,23 +70,17 @@ Setup DNS Records point to machine which runs `GoDoxy`, e.g. **NOTE:** GoDoxy is designed to be (and only works when) running in `host` network mode, do not change it. To change listening ports, modify `.env`. -1. Pull the latest docker images +1. Prepare a new directory for docker compose and config files. + +2. Run setup script inside the directory, or [set up manually](#manual-setup) ```shell - docker pull ghcr.io/yusing/go-proxy:latest + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/go-proxy/v0.9/scripts/setup.sh)" ``` -2. Create new directory, `cd` into it, then run setup, or [set up manually](#manual-setup) +3. Start the container `docker compose up -d` and wait for it to be ready - ```shell - docker run --rm -v .:/setup ghcr.io/yusing/go-proxy /app/godoxy setup - ``` - -3. _(Optional)_ setup `docker-socket-proxy` other docker nodes (see [Multi docker nodes setup](https://github.com/yusing/go-proxy/wiki/Configurations#multi-docker-nodes-setup)) then add them inside `config.yml` - -4. Start the container `docker compose up -d` - -5. You may now do some extra configuration on WebUI `https://godoxy.domain.com` +4. You may now do some extra configuration on WebUI `https://godoxy.yourdomain.com` [๐Ÿ”ผBack to top](#table-of-content) @@ -118,6 +112,10 @@ Setup DNS Records point to machine which runs `GoDoxy`, e.g. โ”‚ โ”‚ โ”œโ”€โ”€ middleware2.yml โ”‚ โ”œโ”€โ”€ provider1.yml โ”‚ โ””โ”€โ”€ provider2.yml +โ”œโ”€โ”€ data +โ”‚ โ”œโ”€โ”€ metrics # metrics data +โ”‚ โ”‚ โ”œโ”€โ”€ uptime.json +โ”‚ โ”‚ โ””โ”€โ”€ system_info.json โ””โ”€โ”€ .env ``` diff --git a/README_CHT.md b/README_CHT.md index e260a8d..d997221 100644 --- a/README_CHT.md +++ b/README_CHT.md @@ -66,23 +66,19 @@ ## ๅฎ‰่ฃ -1. ๆ‹‰ๅ–ๆœ€ๆ–ฐ็š„ Docker ๆ˜ ๅƒ +**ๆณจๆ„๏ผš** GoDoxy ่จญ่จˆ็‚บ๏ผˆไธ”ๅƒ…ๅœจ๏ผ‰`host` ็ถฒ่ทฏๆจกๅผไธ‹้‹ไฝœ๏ผŒ่ซ‹ๅ‹ฟๆ›ดๆ”นใ€‚ๅฆ‚้œ€ๆ›ดๆ”น็›ฃ่ฝๅŸ ๏ผŒ่ซ‹ไฟฎๆ”น `.env`ใ€‚ + +1. ๆบ–ๅ‚™ไธ€ๅ€‹ๆ–ฐ็›ฎ้Œ„็”จๆ–ผ docker compose ๅ’Œ้…็ฝฎๆ–‡ไปถใ€‚ + +2. ๅœจ็›ฎ้Œ„ๅ…ง้‹่กŒๅฎ‰่ฃ่…ณๆœฌ๏ผŒๆˆ–[ๆ‰‹ๅ‹•ๅฎ‰่ฃ](#ๆ‰‹ๅ‹•ๅฎ‰่ฃ) ```shell - docker pull ghcr.io/yusing/go-proxy:latest + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/go-proxy/v0.9/scripts/setup.sh)" ``` -2. ๅปบ็ซ‹ๆ–ฐ็›ฎ้Œ„๏ผŒ`cd` ้€ฒๅ…ฅๅพŒ้‹่กŒๅฎ‰่ฃ๏ผŒๆˆ–[ๆ‰‹ๅ‹•ๅฎ‰่ฃ](#ๆ‰‹ๅ‹•ๅฎ‰่ฃ) +3. ๅ•Ÿๅ‹•ๅฎนๅ™จ `docker compose up -d` ไธฆ็ญ‰ๅพ…ๅฐฑ็ท’ - ```shell - docker run --rm -v .:/setup ghcr.io/yusing/go-proxy /app/godoxy setup - ``` - -3. _๏ผˆๅฏ้ธ๏ผ‰_ ่จญ็ฝฎๅ…ถไป– Docker ็ฏ€้ปž็š„ `docker-socket-proxy`๏ผˆๅƒ่ฆ‹ [ๅคš Docker ็ฏ€้ปž่จญ็ฝฎ](https://github.com/yusing/go-proxy/wiki/Configurations#multi-docker-nodes-setup)๏ผ‰๏ผŒ็„ถๅพŒๅœจ `config.yml` ไธญๆทปๅŠ ๅฎƒๅ€‘ - -4. ๅ•Ÿๅ‹•ๅฎนๅ™จ `docker compose up -d` - -5. ๅคงๅŠŸๅ‘Šๆˆ!ๅฏๅ‰ๅพ€WebUI `https://gp.domain.com` ้€ฒ่กŒ้กๅค–็š„้…็ฝฎ +4. ็พๅœจๅฏไปฅๅœจ WebUI `https://godoxy.yourdomain.com` ้€ฒ่กŒ้กๅค–้…็ฝฎ [๐Ÿ”ผๅ›žๅˆฐ้ ‚้ƒจ](#็›ฎ้Œ„) @@ -114,6 +110,10 @@ โ”‚ โ”‚ โ”œโ”€โ”€ middleware2.yml โ”‚ โ”œโ”€โ”€ provider1.yml โ”‚ โ””โ”€โ”€ provider2.yml +โ”œโ”€โ”€ data +โ”‚ โ”œโ”€โ”€ metrics # metrics data +โ”‚ โ”‚ โ”œโ”€โ”€ uptime.json +โ”‚ โ”‚ โ””โ”€โ”€ system_info.json โ””โ”€โ”€ .env ``` diff --git a/cmd/main.go b/cmd/main.go index 4352af6..c11a811 100755 --- a/cmd/main.go +++ b/cmd/main.go @@ -42,9 +42,6 @@ func main() { args := common.GetArgs() switch args.Command { - case common.CommandSetup: - internal.Setup() - return case common.CommandReload: if err := query.ReloadServer(); err != nil { E.LogFatal("server reload error", err) diff --git a/internal/common/args.go b/internal/common/args.go index e9f6f57..ccae94d 100644 --- a/internal/common/args.go +++ b/internal/common/args.go @@ -1,9 +1,7 @@ package common import ( - "flag" "fmt" - "log" ) type Args struct { @@ -12,7 +10,6 @@ type Args struct { const ( CommandStart = "" - CommandSetup = "setup" CommandValidate = "validate" CommandListConfigs = "ls-config" CommandListRoutes = "ls-routes" @@ -23,34 +20,20 @@ const ( CommandDebugListMTrace = "debug-ls-mtrace" ) -var ValidCommands = []string{ - CommandStart, - CommandSetup, - CommandValidate, - CommandListConfigs, - CommandListRoutes, - CommandListIcons, - CommandReload, - CommandDebugListEntries, - CommandDebugListProviders, - CommandDebugListMTrace, -} +type MainServerCommandValidator struct{} -func GetArgs() Args { - var args Args - flag.Parse() - args.Command = flag.Arg(0) - if err := validateArg(args.Command); err != nil { - log.Fatalf("invalid command: %s", err) - } - return args -} - -func validateArg(arg string) error { - for _, v := range ValidCommands { - if arg == v { - return nil - } +func (v MainServerCommandValidator) IsCommandValid(cmd string) bool { + switch cmd { + case CommandStart, + CommandValidate, + CommandListConfigs, + CommandListRoutes, + CommandListIcons, + CommandReload, + CommandDebugListEntries, + CommandDebugListProviders, + CommandDebugListMTrace: + return true } return fmt.Errorf("invalid command %q", arg) } diff --git a/internal/setup.go b/internal/setup.go deleted file mode 100644 index 60b3ec6..0000000 --- a/internal/setup.go +++ /dev/null @@ -1,127 +0,0 @@ -package internal - -import ( - "io" - "log" - "net/http" - "net/url" - "os" - "path" - - "github.com/yusing/go-proxy/internal/common" -) - -var ( - branch = common.GetEnvString("BRANCH", "v0.9") - baseURL = "https://github.com/yusing/go-proxy/raw/" + branch - requiredConfigs = []Config{ - {common.ConfigBasePath, true, false, ""}, - {common.DotEnvPath, false, true, common.DotEnvExamplePath}, - {common.ComposeFileName, false, true, common.ComposeExampleFileName}, - {path.Join(common.ConfigBasePath, common.ConfigFileName), false, true, common.ConfigExampleFileName}, - } -) - -type Config struct { - Pathname string - IsDir bool - NeedDownload bool - DownloadFileName string -} - -func Setup() { - log.Println("setting up go-proxy") - log.Println("branch:", branch) - - if err := os.Chdir("/setup"); err != nil { - log.Fatalf("failed: %s\n", err) - } - - for _, config := range requiredConfigs { - config.setup() - } - - log.Println("setup finished") -} - -func (c *Config) setup() { - if c.IsDir { - mkdir(c.Pathname) - return - } - if !c.NeedDownload { - touch(c.Pathname) - return - } - - fetch(c.DownloadFileName, c.Pathname) -} - -func hasFileOrDir(path string) bool { - _, err := os.Stat(path) - return err == nil -} - -func mkdir(pathname string) { - _, err := os.Stat(pathname) - if err != nil && os.IsNotExist(err) { - log.Printf("creating directory %q\n", pathname) - err := os.MkdirAll(pathname, 0o755) - if err != nil { - log.Fatalf("failed: %s\n", err) - } - return - } - if err != nil { - log.Fatalf("failed: %s\n", err) - } -} - -func touch(pathname string) { - if hasFileOrDir(pathname) { - return - } - log.Printf("creating file %q\n", pathname) - _, err := os.Create(pathname) - if err != nil { - log.Fatalf("failed: %s\n", err) - } -} - -func fetch(remoteFilename string, outFileName string) { - if hasFileOrDir(outFileName) { - if remoteFilename == outFileName { - log.Printf("%q already exists, not overwriting\n", outFileName) - return - } - log.Printf("%q already exists, downloading to %q\n", outFileName, remoteFilename) - outFileName = remoteFilename - } - log.Printf("downloading %q to %q\n", remoteFilename, outFileName) - - url, err := url.JoinPath(baseURL, remoteFilename) - if err != nil { - log.Fatalf("unexpected error: %s\n", err) - } - - resp, err := http.Get(url) - if err != nil { - log.Fatalf("http request failed: %s\n", err) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - resp.Body.Close() - log.Fatalf("error reading response body: %s\n", err) - } - - err = os.WriteFile(outFileName, body, 0o644) - if err != nil { - resp.Body.Close() - log.Fatalf("failed to write to file: %s\n", err) - } - - log.Print("done") - - resp.Body.Close() -} diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 0000000..d30b664 --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,219 @@ +#!/bin/bash + +set -e # Exit on error + +# Detect download tool +if command -v curl >/dev/null 2>&1; then + DOWNLOAD_TOOL="curl" + DOWNLOAD_CMD="curl -fsSL -o" +elif command -v wget >/dev/null 2>&1; then + DOWNLOAD_TOOL="wget" + DOWNLOAD_CMD="wget -qO" +else + read -p "Neither curl nor wget is installed, install curl? (y/n): " INSTALL + if [ "$INSTALL" == "y" ]; then + install_pkg "curl" + else + echo "Error: Neither curl nor wget is installed. Please install one of them and try again." + exit 1 + fi +fi + +echo "Using ${DOWNLOAD_TOOL} for downloads" + +get_default_branch() { + local repo="$1" # Format: owner/repo + local branch + + if [ "$DOWNLOAD_TOOL" = "curl" ]; then + branch=$(curl -sL "https://api.github.com/repos/${repo}" | grep -o '"default_branch": *"[^"]*"' | cut -d'"' -f4) + elif [ "$DOWNLOAD_TOOL" = "wget" ]; then + branch=$(wget -qO- "https://api.github.com/repos/${repo}" | grep -o '"default_branch": *"[^"]*"' | cut -d'"' -f4) + fi + + if [ -z "$branch" ]; then + echo "main" # Fallback to 'main' if detection fails + else + echo "$branch" + fi +} + +# Environment variables with defaults +REPO="yusing/go-proxy" +BRANCH=${BRANCH:-$(get_default_branch "$REPO")} +REPO_URL="https://github.com/$REPO" +WIKI_URL="${REPO_URL}/wiki" +BASE_URL="${REPO_URL}/raw/${BRANCH}" + +# Config paths +CONFIG_BASE_PATH="config" +DOT_ENV_PATH=".env" +DOT_ENV_EXAMPLE_PATH=".env.example" +COMPOSE_FILE_NAME="compose.yml" +COMPOSE_EXAMPLE_FILE_NAME="compose.example.yml" +CONFIG_FILE_NAME="config.yml" +CONFIG_EXAMPLE_FILE_NAME="config.example.yml" + +echo "Setting up GoDoxy" +echo "Branch: ${BRANCH}" + +install_pkg() { + # detect package manager + if command -v apt >/dev/null 2>&1; then + apt install -y "$1" + elif command -v yum >/dev/null 2>&1; then + yum install -y "$1" + elif command -v pacman >/dev/null 2>&1; then + pacman -S --noconfirm "$1" + else + echo "Error: No supported package manager found" + exit 1 + fi +} + +check_pkg() { + local cmd="$1" + local pkg="$2" + if ! command -v "$cmd" >/dev/null 2>&1; then + # check if user is root + if [ "$EUID" -ne 0 ]; then + echo "Error: $pkg is not installed and you are not running as root. Please install it and try again." + exit 1 + fi + read -p "$pkg is not installed, install it? (y/n): " INSTALL + if [ "$INSTALL" == "y" ]; then + install_pkg "$pkg" + else + echo "Error: $pkg is not installed. Please install it and try again." + exit 1 + fi + fi +} + +# Function to check if file/directory exists +has_file_or_dir() { + [ -e "$1" ] +} + +# Function to create directory +mkdir_if_not_exists() { + if [ ! -d "$1" ]; then + echo "Creating directory \"$1\"" + mkdir -p "$1" + fi +} + +# Function to create empty file +touch_if_not_exists() { + if [ ! -f "$1" ]; then + echo "Creating file \"$1\"" + touch "$1" + fi +} + +# Function to download file +fetch_file() { + local remote_file="$1" + local out_file="$2" + + if has_file_or_dir "$out_file"; then + if [ "$remote_file" = "$out_file" ]; then + echo "\"$out_file\" already exists, not overwriting" + return + fi + read -p "Do you want to overwrite \"$out_file\"? (y/n): " OVERWRITE + if [ "$OVERWRITE" != "y" ]; then + echo "Skipping \"$remote_file\"" + return + fi + fi + + echo "Downloading \"$remote_file\" to \"$out_file\"" + if ! $DOWNLOAD_CMD "$out_file" "${BASE_URL}/${remote_file}"; then + echo "Error: Failed to download ${remote_file}" + rm -f "$out_file" # Clean up partial download + exit 1 + fi + echo "Done" +} + +ask_while_empty() { + local prompt="$1" + local var_name="$2" + local value="" + while [ -z "$value" ]; do + read -p "$prompt" value + if [ -z "$value" ]; then + echo "Error: $var_name cannot be empty, please try again" + fi + done + eval "$var_name=\"$value\"" +} + +check_pkg "openssl" "openssl" +check_pkg "docker" "docker-ce" + +# Setup required configurations +# 1. Config base directory +mkdir_if_not_exists "$CONFIG_BASE_PATH" + +# 2. .env file +fetch_file "$DOT_ENV_EXAMPLE_PATH" "$DOT_ENV_PATH" +# set random JWT secret +JWT_SECRET=$(openssl rand -base64 32) +sed -i "s/GODOXY_API_JWT_SECRET=.*/GODOXY_API_JWT_SECRET=${JWT_SECRET}/" "$DOT_ENV_PATH" + +# 3. docker-compose.yml +fetch_file "$COMPOSE_EXAMPLE_FILE_NAME" "$COMPOSE_FILE_NAME" + +# 4. config.yml +fetch_file "$CONFIG_EXAMPLE_FILE_NAME" "${CONFIG_BASE_PATH}/${CONFIG_FILE_NAME}" + +# 5. setup authentication + +# ask for user and password +echo "Setting up login user" +ask_while_empty "Enter login username: " LOGIN_USERNAME +ask_while_empty "Enter login password: " LOGIN_PASSWORD +echo "Setting up login user \"$LOGIN_USERNAME\" with password \"$LOGIN_PASSWORD\"" +sed -i "s/GODOXY_API_USERNAME=.*/GODOXY_API_USERNAME=${LOGIN_USERNAME}/" "$DOT_ENV_PATH" +sed -i "s/GODOXY_API_PASSWORD=.*/GODOXY_API_PASSWORD=${LOGIN_PASSWORD}/" "$DOT_ENV_PATH" + +# 6. setup autocert + +# ask if want to enable autocert +echo "Setting up autocert for SSL certificate" +ask_while_empty "Do you want to enable autocert? (y/n): " ENABLE_AUTOCERT + +# quit if not using autocert +if [ "$ENABLE_AUTOCERT" == "y" ]; then + # ask for domain + echo "Setting up autocert" + ask_while_empty "Enter domain (e.g. example.com): " DOMAIN + + # ask for email + ask_while_empty "Enter email for Let's Encrypt: " EMAIL + + # ask if using cloudflare + ask_while_empty "Are you using cloudflare? (y/n): " USE_CLOUDFLARE + + # ask for cloudflare api key + if [ "$USE_CLOUDFLARE" = "y" ]; then + ask_while_empty "Enter cloudflare api key: " CLOUDFLARE_API_KEY + cat <>"$CONFIG_BASE_PATH/$CONFIG_FILE_NAME" +autocert: + provider: cloudflare + email: $EMAIL + domains: + - "*.${DOMAIN}" + - "${DOMAIN}" + options: + auth_token: "$CLOUDFLARE_API_KEY" +EOF + else + echo "Not using cloudflare, skipping autocert setup" + echo "Please refer to ${WIKI_URL}/Supported-DNS-01-Providers for more information" + fi +fi + +echo "Setup finished"