diff --git a/.github/workflows/agent-binary.yml b/.github/workflows/agent-binary.yml new file mode 100644 index 0000000..cd6693c --- /dev/null +++ b/.github/workflows/agent-binary.yml @@ -0,0 +1,51 @@ +name: GoDoxy agent binary + +on: + push: + tags: + - v* + paths: + - "agent/**" + +jobs: + build: + strategy: + matrix: + include: + - runner: ubuntu-latest + platform: linux/amd64 + binary_name: godoxy-agent-linux-amd64 + - runner: ubuntu-24.04-arm + platform: linux/arm64 + binary_name: godoxy-agent-linux-arm64 + name: Build ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Verify dependencies + run: go mod verify + - name: Build + run: | + make agent=1 NAME=${{ matrix.binary_name }} build + - name: Check binary + run: | + file bin/${{ matrix.binary_name }} + - name: Test + run: | + go test -v ./agent/... + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.binary_name }} + path: bin/${{ matrix.binary_name }} + - name: Upload to release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: bin/${{ matrix.binary_name }} diff --git a/agent/pkg/agent/bare_metal.go b/agent/pkg/agent/bare_metal.go new file mode 100644 index 0000000..8cc170c --- /dev/null +++ b/agent/pkg/agent/bare_metal.go @@ -0,0 +1,24 @@ +package agent + +import ( + "bytes" + "strings" + "text/template" +) + +var ( + installScript = `AGENT_NAME={{.Name}} \ + AGENT_PORT={{.Port}} \ + AGENT_CA_CERT={{.CACert}} \ + AGENT_SSL_CERT={{.SSLCert}} \ + bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/go-proxy/main/scripts/install-agent.sh)"` + installScriptTemplate = template.Must(template.New("install.sh").Parse(installScript)) +) + +func (c *AgentEnvConfig) Generate() (string, error) { + buf := bytes.NewBuffer(make([]byte, 0, 4096)) + if err := installScriptTemplate.Execute(buf, c); err != nil { + return "", err + } + return strings.ReplaceAll(buf.String(), ";", "\\;"), nil +} diff --git a/agent/pkg/agent/docker_compose.go b/agent/pkg/agent/docker_compose.go index 841fcb4..63b4669 100644 --- a/agent/pkg/agent/docker_compose.go +++ b/agent/pkg/agent/docker_compose.go @@ -7,9 +7,11 @@ import ( _ "embed" ) -//go:embed agent.compose.yml -var agentComposeYAML string -var agentComposeYAMLTemplate = template.Must(template.New("agent.compose.yml").Parse(agentComposeYAML)) +var ( + //go:embed templates/agent.compose.yml + agentComposeYAML string + agentComposeYAMLTemplate = template.Must(template.New("agent.compose.yml").Parse(agentComposeYAML)) +) const ( DockerImageProduction = "ghcr.io/yusing/godoxy-agent:latest" diff --git a/agent/pkg/agent/env.go b/agent/pkg/agent/env.go new file mode 100644 index 0000000..68bc2da --- /dev/null +++ b/agent/pkg/agent/env.go @@ -0,0 +1,17 @@ +package agent + +type ( + AgentEnvConfig struct { + Name string + Port int + CACert string + SSLCert string + } + AgentComposeConfig struct { + Image string + *AgentEnvConfig + } + Generator interface { + Generate() (string, error) + } +) diff --git a/agent/pkg/agent/agent.compose.yml b/agent/pkg/agent/templates/agent.compose.yml similarity index 100% rename from agent/pkg/agent/agent.compose.yml rename to agent/pkg/agent/templates/agent.compose.yml diff --git a/internal/api/v1/new_agent.go b/internal/api/v1/new_agent.go index ecb6daa..01ad1c9 100644 --- a/internal/api/v1/new_agent.go +++ b/internal/api/v1/new_agent.go @@ -13,7 +13,6 @@ import ( "github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/certs" config "github.com/yusing/go-proxy/internal/config/types" - "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/utils/strutils" ) @@ -47,11 +46,8 @@ func NewAgent(w http.ResponseWriter, r *http.Request) { } t := q.Get("type") switch t { - case "docker": + case "docker", "system": break - case "system": - gphttp.ClientError(w, gperr.Errorf("system agent is not supported yet"), http.StatusNotImplemented) - return case "": gphttp.ClientError(w, gphttp.ErrMissingKey("type")) return @@ -74,14 +70,18 @@ func NewAgent(w http.ResponseWriter, r *http.Request) { return } - cfg := agent.AgentComposeConfig{ - Image: image, + var cfg agent.Generator = &agent.AgentEnvConfig{ Name: name, Port: port, CACert: ca.String(), SSLCert: srv.String(), } - + if t == "docker" { + cfg = &agent.AgentComposeConfig{ + Image: image, + AgentEnvConfig: cfg.(*agent.AgentEnvConfig), + } + } template, err := cfg.Generate() if err != nil { gphttp.ServerError(w, r, err) diff --git a/scripts/install-agent.sh b/scripts/install-agent.sh new file mode 100644 index 0000000..e652f5c --- /dev/null +++ b/scripts/install-agent.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +set -e + +check_pkg() { + if ! command -v $1 &>/dev/null; then + echo "$1 could not be found, please install it first" + exit 1 + fi +} + +# check if curl and jq are installed +check_pkg curl +check_pkg jq + +# check if running user is root +if [ "$EUID" -ne 0 ]; then + echo "Please run the script as root" + exit 1 +fi + +# check if system is using systemd +if [ -d "/etc/systemd/system" ]; then + echo "System is using systemd" +else + echo "Unsupported init system, currently only systemd is supported" + exit 1 +fi + +# check variables +if [ -z "$AGENT_NAME" ]; then + echo "AGENT_NAME is not set" + exit 1 +fi +if [ -z "$AGENT_PORT" ]; then + echo "AGENT_PORT is not set" + exit 1 +fi +if [ -z "$AGENT_CA_CERT" ]; then + echo "AGENT_CA_CERT is not set" + exit 1 +fi +if [ -z "$AGENT_SSL_CERT" ]; then + echo "AGENT_SSL_CERT is not set" + exit 1 +fi + +# init variables +arch=$(uname -m) +if [ "$arch" = "x86_64" ]; then + filename="godoxy-agent-linux-amd64" +elif [ "$arch" = "aarch64" ]; then + filename="godoxy-agent-linux-arm64" +else + echo "Unsupported architecture: $arch, expect x86_64 or aarch64" + exit 1 +fi +repo="yusing/go-proxy" +install_path="/usr/local/bin" +name="godoxy-agent" +bin_path="${install_path}/${name}" +env_file="/etc/${name}.env" +service_path="/etc/systemd/system/${name}.service" +log_path="/var/log/${name}.log" +data_path="/var/lib/${name}" + +# check if install path is writable +if [ ! -w "$install_path" ]; then + echo "Install path is not writable, please check the permissions" + exit 1 +fi + +# check if service path is writable +if [ ! -w "$service_path" ]; then + echo "Service path is not writable, please check the permissions" + exit 1 +fi + +# check if env file is writable +if [ ! -w "$env_file" ]; then + echo "Env file is not writable, please check the permissions" + exit 1 +fi + +# check if command is uninstall +if [ "$1" = "uninstall" ]; then + echo "Uninstalling the agent" + systemctl disable --now $name + rm -f $bin_path + rm -f $env_file + rm -f $service_path + rm -rf $data_path + systemctl daemon-reload + echo "Agent uninstalled successfully" + exit 0 +fi + +echo "Finding the latest agent binary" +bin_url=$(curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$repo/releases/latest | jq -r '.assets[] | select(.name | contains("'$filename'")) | .browser_download_url') + +echo "Downloading the agent binary" +curl -L "$bin_url" -o $bin_path + +echo "Making the agent binary executable" +chmod +x $bin_path + +echo "Creating the environment file" +cat <$env_file +AGENT_NAME="${AGENT_NAME}" +AGENT_PORT="${AGENT_PORT}" +AGENT_CA_CERT="${AGENT_CA_CERT}" +AGENT_SSL_CERT="${AGENT_SSL_CERT}" +EOF +chmod 600 $env_file + +echo "Creating the data directory" +mkdir -p $data_path + +echo "Registering the agent as a service" +cat <$service_path +[Unit] +Description=GoDoxy Agent +After=docker.socket + +[Service]] +Type=simple +ExecStart=${bin_path} +EnvironmentFile=${env_file} +WorkingDirectory=${data_path} +Restart=always +RestartSec=10 +StandardOutput=append:${log_path} +StandardError=append:${log_path} + +# Security settings +ProtectSystem=full +ProtectHome=true +NoNewPrivileges=true + +# User and group +User=root +Group=root + +[Install] +WantedBy=multi-user.target +EOF +systemctl daemon-reload +systemctl enable --now $name +echo "Agent installed successfully"