implement system mode agent

This commit is contained in:
yusing 2025-02-23 11:26:38 +08:00
parent 5e8e4fa4a1
commit 2c21387ad9
7 changed files with 254 additions and 11 deletions

51
.github/workflows/agent-binary.yml vendored Normal file
View file

@ -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 }}

View file

@ -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
}

View file

@ -7,9 +7,11 @@ import (
_ "embed" _ "embed"
) )
//go:embed agent.compose.yml var (
var agentComposeYAML string //go:embed templates/agent.compose.yml
var agentComposeYAMLTemplate = template.Must(template.New("agent.compose.yml").Parse(agentComposeYAML)) agentComposeYAML string
agentComposeYAMLTemplate = template.Must(template.New("agent.compose.yml").Parse(agentComposeYAML))
)
const ( const (
DockerImageProduction = "ghcr.io/yusing/godoxy-agent:latest" DockerImageProduction = "ghcr.io/yusing/godoxy-agent:latest"

17
agent/pkg/agent/env.go Normal file
View file

@ -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)
}
)

View file

@ -13,7 +13,6 @@ import (
"github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/agent/pkg/certs" "github.com/yusing/go-proxy/agent/pkg/certs"
config "github.com/yusing/go-proxy/internal/config/types" 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/net/gphttp"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
) )
@ -47,11 +46,8 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
} }
t := q.Get("type") t := q.Get("type")
switch t { switch t {
case "docker": case "docker", "system":
break break
case "system":
gphttp.ClientError(w, gperr.Errorf("system agent is not supported yet"), http.StatusNotImplemented)
return
case "": case "":
gphttp.ClientError(w, gphttp.ErrMissingKey("type")) gphttp.ClientError(w, gphttp.ErrMissingKey("type"))
return return
@ -74,14 +70,18 @@ func NewAgent(w http.ResponseWriter, r *http.Request) {
return return
} }
cfg := agent.AgentComposeConfig{ var cfg agent.Generator = &agent.AgentEnvConfig{
Image: image,
Name: name, Name: name,
Port: port, Port: port,
CACert: ca.String(), CACert: ca.String(),
SSLCert: srv.String(), SSLCert: srv.String(),
} }
if t == "docker" {
cfg = &agent.AgentComposeConfig{
Image: image,
AgentEnvConfig: cfg.(*agent.AgentEnvConfig),
}
}
template, err := cfg.Generate() template, err := cfg.Generate()
if err != nil { if err != nil {
gphttp.ServerError(w, r, err) gphttp.ServerError(w, r, err)

149
scripts/install-agent.sh Normal file
View file

@ -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 <<EOF >$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 <<EOF >$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"