Spaces:
Paused
Paused
Upload 1793 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .air.toml +47 -0
- .dockerignore +19 -0
- .gitattributes +10 -0
- .golangci.yml +393 -0
- .goreleaser.yml +89 -0
- .mockery.yml +17 -0
- .pre-commit-config.yaml +22 -0
- AGENTS.md +3 -0
- CHANGELOG.md +20 -0
- CLAUDE.md +428 -0
- LICENSE +210 -0
- Makefile +123 -0
- cmd/axonhub/main.go +281 -0
- conf/conf.go +217 -0
- config.example.yml +142 -0
- deploy/axonhub.service +14 -0
- deploy/helm/.helmignore +23 -0
- deploy/helm/Chart.yaml +12 -0
- deploy/helm/README.md +210 -0
- deploy/helm/install.bat +38 -0
- deploy/helm/install.sh +38 -0
- deploy/helm/templates/NOTES.txt +43 -0
- deploy/helm/templates/_helpers.tpl +76 -0
- deploy/helm/templates/data-pvc.yaml +18 -0
- deploy/helm/templates/deployment.yaml +82 -0
- deploy/helm/templates/hpa.yaml +33 -0
- deploy/helm/templates/ingress.yaml +62 -0
- deploy/helm/templates/postgresql-service.yaml +19 -0
- deploy/helm/templates/postgresql-statefulset.yaml +79 -0
- deploy/helm/templates/service.yaml +17 -0
- deploy/helm/templates/serviceaccount.yaml +12 -0
- deploy/helm/templates/tests/test-connection.yaml +16 -0
- deploy/helm/values-production.yaml +171 -0
- deploy/helm/values.yaml +165 -0
- deploy/install.bat +15 -0
- deploy/install.ps1 +217 -0
- deploy/install.sh +550 -0
- deploy/nginx.conf +238 -0
- deploy/start.bat +14 -0
- deploy/start.ps1 +155 -0
- deploy/start.sh +200 -0
- deploy/stop.bat +14 -0
- deploy/stop.ps1 +163 -0
- deploy/stop.sh +226 -0
- docker-compose.yml +172 -0
- docs/axonhub-architecture-light.svg +156 -0
- docs/axonhub-architecture.svg +141 -0
- docs/en/api-reference/anthropic-api.md +203 -0
- docs/en/api-reference/embedding-api.md +176 -0
- docs/en/api-reference/gemini-api.md +214 -0
.air.toml
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format
|
| 2 |
+
|
| 3 |
+
# Working directory
|
| 4 |
+
# . or absolute path, please note that the directories following must be under root.
|
| 5 |
+
root = "."
|
| 6 |
+
tmp_dir = "tmp"
|
| 7 |
+
|
| 8 |
+
[build]
|
| 9 |
+
# Just plain old shell command. You could use `make` as well.
|
| 10 |
+
cmd = "go build -o ./tmp/axonhub cmd/axonhub/main.go"
|
| 11 |
+
# Binary file yields from `cmd`.
|
| 12 |
+
bin = "./tmp/axonhub"
|
| 13 |
+
# Customize binary.
|
| 14 |
+
full_bin = "APP_USER=air ./tmp/axonhub"
|
| 15 |
+
# Watch these filename extensions.
|
| 16 |
+
include_ext = ["go","yml"]
|
| 17 |
+
# Ignore these filename extensions or directories.
|
| 18 |
+
exclude_dir = ["assets", "tmp", "vendor", "frontend", "integration_test", "website", "scripts", "openspec", "llm/tools","tools" ,"examples"]
|
| 19 |
+
# Watch these directories if you specified.
|
| 20 |
+
include_dir = []
|
| 21 |
+
# Exclude files.
|
| 22 |
+
exclude_file = []
|
| 23 |
+
# This log file places in your tmp_dir.
|
| 24 |
+
log = "air.log"
|
| 25 |
+
# It's not necessary to trigger build each time file changes if it's too frequent.
|
| 26 |
+
delay = 10000 # ms
|
| 27 |
+
# Stop running old binary when build errors occur.
|
| 28 |
+
stop_on_error = true
|
| 29 |
+
# Send Interrupt signal before killing process (windows does not support this feature)
|
| 30 |
+
send_interrupt = false
|
| 31 |
+
# Delay after sending Interrupt signal
|
| 32 |
+
kill_delay = 1000 # ms
|
| 33 |
+
|
| 34 |
+
[log]
|
| 35 |
+
# Show log time
|
| 36 |
+
time = false
|
| 37 |
+
|
| 38 |
+
[color]
|
| 39 |
+
# Customize each part's color. If no color found, use the raw app log.
|
| 40 |
+
main = "magenta"
|
| 41 |
+
watcher = "cyan"
|
| 42 |
+
build = "yellow"
|
| 43 |
+
runner = "green"
|
| 44 |
+
|
| 45 |
+
[misc]
|
| 46 |
+
# Delete tmp directory on exit
|
| 47 |
+
clean_on_exit = true
|
.dockerignore
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.gitignore
|
| 3 |
+
.dockerignore
|
| 4 |
+
node_modules
|
| 5 |
+
frontend/node_modules
|
| 6 |
+
frontend/dist
|
| 7 |
+
.axonhub
|
| 8 |
+
axonhub
|
| 9 |
+
axonhub.db
|
| 10 |
+
.DS_Store
|
| 11 |
+
*.log
|
| 12 |
+
coverage
|
| 13 |
+
dist
|
| 14 |
+
build
|
| 15 |
+
.vscode
|
| 16 |
+
.idea
|
| 17 |
+
.env
|
| 18 |
+
.env.local
|
| 19 |
+
.env.*.local
|
.gitattributes
CHANGED
|
@@ -33,3 +33,13 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
docs/screenshots/axonhub-channels.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
docs/screenshots/axonhub-dashboard.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
docs/screenshots/axonhub-model-price.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
docs/screenshots/axonhub-models.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
docs/screenshots/axonhub-profiles.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
docs/screenshots/axonhub-requests.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
docs/screenshots/axonhub-system.png filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
docs/screenshots/axonhub-trace.png filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
docs/screenshots/axonhub-usage-logs.png filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
docs/screenshots/axonhub-users.png filter=lfs diff=lfs merge=lfs -text
|
.golangci.yml
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file contains all available configuration options
|
| 2 |
+
# with their default values.
|
| 3 |
+
|
| 4 |
+
version: "2"
|
| 5 |
+
# options for analysis running
|
| 6 |
+
run:
|
| 7 |
+
# default concurrency is a available CPU number
|
| 8 |
+
concurrency: 4
|
| 9 |
+
|
| 10 |
+
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
| 11 |
+
timeout: 10m
|
| 12 |
+
|
| 13 |
+
# exit code when at least one issue was found, default is 1
|
| 14 |
+
issues-exit-code: 1
|
| 15 |
+
|
| 16 |
+
# include test files or not, default is true
|
| 17 |
+
tests: true
|
| 18 |
+
|
| 19 |
+
# list of build tags, all linters use it. Default is empty list.
|
| 20 |
+
build-tags: []
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
linters:
|
| 24 |
+
default: all
|
| 25 |
+
disable:
|
| 26 |
+
- prealloc
|
| 27 |
+
- errchkjson
|
| 28 |
+
- contextcheck
|
| 29 |
+
- rowserrcheck
|
| 30 |
+
- sqlclosecheck
|
| 31 |
+
- wastedassign
|
| 32 |
+
- forbidigo
|
| 33 |
+
- gocritic
|
| 34 |
+
- revive
|
| 35 |
+
- nonamedreturns
|
| 36 |
+
- wrapcheck
|
| 37 |
+
- varnamelen
|
| 38 |
+
- paralleltest
|
| 39 |
+
- nlreturn
|
| 40 |
+
- testpackage
|
| 41 |
+
- gochecknoglobals
|
| 42 |
+
- wsl
|
| 43 |
+
- ireturn
|
| 44 |
+
- godox
|
| 45 |
+
- cyclop
|
| 46 |
+
- whitespace
|
| 47 |
+
- nolintlint
|
| 48 |
+
- unused
|
| 49 |
+
- depguard
|
| 50 |
+
- lll
|
| 51 |
+
- exhaustruct
|
| 52 |
+
- gochecknoinits
|
| 53 |
+
- dupl
|
| 54 |
+
- tagalign
|
| 55 |
+
- funlen
|
| 56 |
+
- gomoddirectives
|
| 57 |
+
- nestif
|
| 58 |
+
- testableexamples
|
| 59 |
+
- gocyclo
|
| 60 |
+
- gosmopolitan
|
| 61 |
+
- perfsprint
|
| 62 |
+
- inamedparam
|
| 63 |
+
- interfacebloat
|
| 64 |
+
- unparam
|
| 65 |
+
- mnd
|
| 66 |
+
- recvcheck
|
| 67 |
+
- tagliatelle
|
| 68 |
+
- goconst
|
| 69 |
+
- gocognit
|
| 70 |
+
- err113
|
| 71 |
+
- funcorder
|
| 72 |
+
- noinlineerr
|
| 73 |
+
- nilnil
|
| 74 |
+
- thelper
|
| 75 |
+
- dogsled
|
| 76 |
+
- unqueryvet
|
| 77 |
+
- predeclared
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
# all available settings of specific linters
|
| 81 |
+
settings:
|
| 82 |
+
dogsled:
|
| 83 |
+
# checks assignments with too many blank identifiers; default is 2
|
| 84 |
+
max-blank-identifiers: 2
|
| 85 |
+
dupl:
|
| 86 |
+
# tokens count to trigger issue, 150 by default
|
| 87 |
+
threshold: 100
|
| 88 |
+
errcheck:
|
| 89 |
+
# report about not checking of errors in type assertions: `a := b.(MyStruct)`;
|
| 90 |
+
# default is false: such cases aren't reported by default.
|
| 91 |
+
check-type-assertions: false
|
| 92 |
+
|
| 93 |
+
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
| 94 |
+
# default is false: such cases aren't reported by default.
|
| 95 |
+
check-blank: false
|
| 96 |
+
|
| 97 |
+
funlen:
|
| 98 |
+
lines: 60
|
| 99 |
+
statements: 40
|
| 100 |
+
gocognit:
|
| 101 |
+
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
| 102 |
+
min-complexity: 20
|
| 103 |
+
goconst:
|
| 104 |
+
# minimal length of string constant, 3 by default
|
| 105 |
+
min-len: 3
|
| 106 |
+
# minimal occurrences count to trigger, 3 by default
|
| 107 |
+
min-occurrences: 3
|
| 108 |
+
gocritic:
|
| 109 |
+
# Which checks should be enabled; can't be combined with 'disabled-checks';
|
| 110 |
+
# See https://go-critic.github.io/overview#checks-overview
|
| 111 |
+
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
|
| 112 |
+
# By default list of stable checks is used.
|
| 113 |
+
enabled-checks: []
|
| 114 |
+
|
| 115 |
+
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
| 116 |
+
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
| 117 |
+
enabled-tags:
|
| 118 |
+
- performance
|
| 119 |
+
|
| 120 |
+
settings: # settings passed to gocritic
|
| 121 |
+
captLocal: # must be valid enabled check name
|
| 122 |
+
paramsOnly: true
|
| 123 |
+
rangeValCopy:
|
| 124 |
+
sizeThreshold: 32
|
| 125 |
+
gocyclo:
|
| 126 |
+
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
| 127 |
+
min-complexity: 10
|
| 128 |
+
godox:
|
| 129 |
+
# report any comments starting with keywords, this is useful for TODO or FIXME comments that
|
| 130 |
+
# might be left in the code accidentally and should be resolved before merging
|
| 131 |
+
keywords: [] # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting
|
| 132 |
+
govet:
|
| 133 |
+
# settings per analyzer
|
| 134 |
+
settings:
|
| 135 |
+
printf: # analyzer name, run `go tool vet help` to see all analyzers
|
| 136 |
+
funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer
|
| 137 |
+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
|
| 138 |
+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
|
| 139 |
+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
|
| 140 |
+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
|
| 141 |
+
|
| 142 |
+
# enable or disable analyzers by name
|
| 143 |
+
enable:
|
| 144 |
+
- atomicalign
|
| 145 |
+
disable:
|
| 146 |
+
- shadow
|
| 147 |
+
disable-all: false
|
| 148 |
+
|
| 149 |
+
lll:
|
| 150 |
+
# max line length, lines longer will be reported. Default is 120.
|
| 151 |
+
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
| 152 |
+
line-length: 180
|
| 153 |
+
# tab width in spaces. Default to 1.
|
| 154 |
+
tab-width: 1
|
| 155 |
+
misspell:
|
| 156 |
+
# Correct spellings using locale preferences for US or UK.
|
| 157 |
+
# Default is to use a neutral variety of English.
|
| 158 |
+
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
| 159 |
+
locale: US
|
| 160 |
+
nakedret:
|
| 161 |
+
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
| 162 |
+
max-func-lines: 30
|
| 163 |
+
prealloc:
|
| 164 |
+
# XXX: we don't recommend using this linter before doing performance profiling.
|
| 165 |
+
# For most programs usage of prealloc will be a premature optimization.
|
| 166 |
+
|
| 167 |
+
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
| 168 |
+
# True by default.
|
| 169 |
+
simple: true
|
| 170 |
+
range-loops: true # Report preallocation suggestions on range loops, true by default
|
| 171 |
+
for-loops: false # Report preallocation suggestions on for loops, false by default
|
| 172 |
+
unparam:
|
| 173 |
+
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
| 174 |
+
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
| 175 |
+
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
| 176 |
+
# with golangci-lint call it on a directory with the changed file.
|
| 177 |
+
check-exported: false
|
| 178 |
+
whitespace:
|
| 179 |
+
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
| 180 |
+
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
| 181 |
+
wsl:
|
| 182 |
+
# If true append is only allowed to be cuddled if appending value is
|
| 183 |
+
# matching variables, fields or types on line above. Default is true.
|
| 184 |
+
strict-append: true
|
| 185 |
+
# Allow calls and assignments to be cuddled as long as the lines have any
|
| 186 |
+
# matching variables, fields or types. Default is true.
|
| 187 |
+
allow-assign-and-call: true
|
| 188 |
+
# Allow multiline assignments to be cuddled. Default is true.
|
| 189 |
+
allow-multiline-assign: true
|
| 190 |
+
# Allow declarations (var) to be cuddled.
|
| 191 |
+
allow-cuddle-declarations: true
|
| 192 |
+
# Allow trailing comments in ending of blocks
|
| 193 |
+
allow-trailing-comment: false
|
| 194 |
+
# Force newlines in end of case at this limit (0 = never).
|
| 195 |
+
force-case-trailing-whitespace: 0
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
exclusions:
|
| 199 |
+
# Exclude all linters from running on ent files.
|
| 200 |
+
paths:
|
| 201 |
+
- internal/ent
|
| 202 |
+
# Excluding configuration per-path, per-linter, per-text and per-source
|
| 203 |
+
rules:
|
| 204 |
+
# Exclude some linters from running on tools files.
|
| 205 |
+
- path: tools/
|
| 206 |
+
linters:
|
| 207 |
+
- gosec
|
| 208 |
+
- errcheck
|
| 209 |
+
# Exclude some linters from running on tests files.
|
| 210 |
+
- path: _test\.go$
|
| 211 |
+
linters:
|
| 212 |
+
- noctx
|
| 213 |
+
- sqlclosecheck
|
| 214 |
+
- gocyclo
|
| 215 |
+
- errcheck
|
| 216 |
+
- dupl
|
| 217 |
+
- gosec
|
| 218 |
+
- unused
|
| 219 |
+
- deadcode
|
| 220 |
+
- exhaustruct
|
| 221 |
+
- funlen
|
| 222 |
+
- maintidx
|
| 223 |
+
- cyclop
|
| 224 |
+
- gocognit
|
| 225 |
+
- unparam
|
| 226 |
+
- containedctx
|
| 227 |
+
- goconst
|
| 228 |
+
- ineffassign
|
| 229 |
+
- staticcheck
|
| 230 |
+
- bodyclose
|
| 231 |
+
- forcetypeassert
|
| 232 |
+
- predeclared
|
| 233 |
+
- testifylint
|
| 234 |
+
- musttag
|
| 235 |
+
- dupword
|
| 236 |
+
- intrange
|
| 237 |
+
- govet
|
| 238 |
+
- errname
|
| 239 |
+
- noinlineerr
|
| 240 |
+
- nilnil
|
| 241 |
+
- exhaustive
|
| 242 |
+
|
| 243 |
+
- path: _mock\.go$
|
| 244 |
+
linters:
|
| 245 |
+
- gofmt
|
| 246 |
+
|
| 247 |
+
# Exclude some staticcheck messages
|
| 248 |
+
- linters:
|
| 249 |
+
- staticcheck
|
| 250 |
+
text: "SA9003:"
|
| 251 |
+
|
| 252 |
+
- linters:
|
| 253 |
+
- staticcheck
|
| 254 |
+
text: "SA1019:"
|
| 255 |
+
|
| 256 |
+
- linters:
|
| 257 |
+
- staticcheck
|
| 258 |
+
text: "SA4009:"
|
| 259 |
+
|
| 260 |
+
- linters:
|
| 261 |
+
- staticcheck
|
| 262 |
+
text: "ST1001:"
|
| 263 |
+
|
| 264 |
+
- linters:
|
| 265 |
+
- staticcheck
|
| 266 |
+
text: "QF1008:"
|
| 267 |
+
|
| 268 |
+
- linters:
|
| 269 |
+
- staticcheck
|
| 270 |
+
text: "ST1005:"
|
| 271 |
+
|
| 272 |
+
- linters:
|
| 273 |
+
- stylecheck
|
| 274 |
+
text: "ST1001:"
|
| 275 |
+
|
| 276 |
+
- linters:
|
| 277 |
+
- stylecheck
|
| 278 |
+
text: "ST1003:"
|
| 279 |
+
|
| 280 |
+
- linters:
|
| 281 |
+
- stylecheck
|
| 282 |
+
text: "ST1006:"
|
| 283 |
+
|
| 284 |
+
- linters:
|
| 285 |
+
- stylecheck
|
| 286 |
+
text: "ST1016:"
|
| 287 |
+
|
| 288 |
+
# Exclude lll issues for long lines with go:generate
|
| 289 |
+
- linters:
|
| 290 |
+
- lll
|
| 291 |
+
source: "^//go:generate "
|
| 292 |
+
|
| 293 |
+
formatters:
|
| 294 |
+
# Enable specific formatter.
|
| 295 |
+
# Default: [] (uses standard Go formatting)
|
| 296 |
+
enable:
|
| 297 |
+
- gci
|
| 298 |
+
- gofmt
|
| 299 |
+
- gofumpt
|
| 300 |
+
- goimports
|
| 301 |
+
# - golines
|
| 302 |
+
settings:
|
| 303 |
+
gci:
|
| 304 |
+
# Section configuration to compare against.
|
| 305 |
+
# Section names are case-insensitive and may contain parameters in ().
|
| 306 |
+
# The default order of sections is `standard > default > custom > blank > dot > alias > localmodule`.
|
| 307 |
+
# If `custom-order` is `true`, it follows the order of `sections` option.
|
| 308 |
+
# Default: ["standard", "default"]
|
| 309 |
+
sections:
|
| 310 |
+
- standard # Standard section: captures all standard packages.
|
| 311 |
+
- default # Default section: contains all imports that could not be matched to another section type.
|
| 312 |
+
- blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled.
|
| 313 |
+
- dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled.
|
| 314 |
+
- alias # Alias section: contains all alias imports. This section is not present unless explicitly enabled.
|
| 315 |
+
- localmodule # Local module section: contains all local packages. This section is not present unless explicitly enabled.
|
| 316 |
+
# Checks that no inline comments are present.
|
| 317 |
+
# Default: false
|
| 318 |
+
no-inline-comments: false
|
| 319 |
+
# Checks that no prefix comments (comment lines above an import) are present.
|
| 320 |
+
# Default: false
|
| 321 |
+
no-prefix-comments: false
|
| 322 |
+
# Enable custom order of sections.
|
| 323 |
+
# If `true`, make the section order the same as the order of `sections`.
|
| 324 |
+
# Default: false
|
| 325 |
+
custom-order: true
|
| 326 |
+
# Drops lexical ordering for custom sections.
|
| 327 |
+
# Default: false
|
| 328 |
+
no-lex-order: true
|
| 329 |
+
|
| 330 |
+
golines:
|
| 331 |
+
# Target maximum line length.
|
| 332 |
+
# Default: 100
|
| 333 |
+
max-len: 180
|
| 334 |
+
# Length of a tabulation.
|
| 335 |
+
# Default: 4
|
| 336 |
+
tab-len: 4
|
| 337 |
+
# Shorten single-line comments.
|
| 338 |
+
# Default: false
|
| 339 |
+
shorten-comments: true
|
| 340 |
+
# Default: true
|
| 341 |
+
reformat-tags: false
|
| 342 |
+
# Split chained methods on the dots as opposed to the arguments.
|
| 343 |
+
# Default: true
|
| 344 |
+
chain-split-dots: false
|
| 345 |
+
|
| 346 |
+
goimports:
|
| 347 |
+
# Local prefixes to use with "goimports -local".
|
| 348 |
+
# Default: []
|
| 349 |
+
local-prefixes:
|
| 350 |
+
- "github.com/looplj/axonhub"
|
| 351 |
+
|
| 352 |
+
exclusions:
|
| 353 |
+
# Mode of the generated files analysis.
|
| 354 |
+
#
|
| 355 |
+
# - `strict`: sources are excluded by strictly following the Go generated file convention.
|
| 356 |
+
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
|
| 357 |
+
# This line must appear before the first non-comment, non-blank text in the file.
|
| 358 |
+
# https://go.dev/s/generatedcode
|
| 359 |
+
# - `lax`: sources are excluded if they contain lines like `autogenerated file`, `code generated`, `do not edit`, etc.
|
| 360 |
+
# - `disable`: disable the generated files exclusion.
|
| 361 |
+
#
|
| 362 |
+
# Default: lax
|
| 363 |
+
generated: lax
|
| 364 |
+
# Which file paths to exclude.
|
| 365 |
+
# This option is ignored when using `--stdin` as the path is unknown.
|
| 366 |
+
# Default: []
|
| 367 |
+
paths:
|
| 368 |
+
- ".resolvers.go"
|
| 369 |
+
- "axonhub.resolvers.go"
|
| 370 |
+
- "ent.resolvers.go"
|
| 371 |
+
- "internal/ent"
|
| 372 |
+
- "internal/server/gql/ent.resolvers.go"
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
issues:
|
| 376 |
+
# make issues output unique by line, default is true
|
| 377 |
+
uniq-by-line: true
|
| 378 |
+
|
| 379 |
+
max-issues-per-linter: 0
|
| 380 |
+
|
| 381 |
+
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
| 382 |
+
max-same-issues: 0
|
| 383 |
+
|
| 384 |
+
# Show only new issues: if there are unstaged changes or untracked files,
|
| 385 |
+
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
|
| 386 |
+
# It's a super-useful option for integration of golangci-lint into existing
|
| 387 |
+
# large codebase. It's not practical to fix all existing issues at the moment
|
| 388 |
+
# of integration: much better don't allow issues in new code.
|
| 389 |
+
# Default is false.
|
| 390 |
+
new: true
|
| 391 |
+
|
| 392 |
+
# Auto-fix enabled issues. Default is false.
|
| 393 |
+
fix: true
|
.goreleaser.yml
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: 2
|
| 2 |
+
|
| 3 |
+
project_name: axonhub
|
| 4 |
+
|
| 5 |
+
dist: .builds
|
| 6 |
+
|
| 7 |
+
before:
|
| 8 |
+
hooks:
|
| 9 |
+
- /bin/sh -c 'cd deploy && chmod +x *.sh'
|
| 10 |
+
- make build-frontend
|
| 11 |
+
- go mod tidy
|
| 12 |
+
|
| 13 |
+
builds:
|
| 14 |
+
- id: build
|
| 15 |
+
main: ./cmd/axonhub
|
| 16 |
+
binary: axonhub
|
| 17 |
+
flags:
|
| 18 |
+
- -tags=nomsgpack
|
| 19 |
+
ldflags:
|
| 20 |
+
- -s -w -X github.com/looplj/axonhub/internal/build.Version={{ .Tag }}
|
| 21 |
+
- -s -w -X github.com/looplj/axonhub/internal/build.Commit={{ .Commit }}
|
| 22 |
+
- -s -w -X github.com/looplj/axonhub/internal/build.BuildTime={{ .Date }}
|
| 23 |
+
env:
|
| 24 |
+
- CGO_ENABLED=0
|
| 25 |
+
goos:
|
| 26 |
+
- linux
|
| 27 |
+
- windows
|
| 28 |
+
- darwin
|
| 29 |
+
goarch:
|
| 30 |
+
- amd64
|
| 31 |
+
- arm64
|
| 32 |
+
- loong64
|
| 33 |
+
goarm:
|
| 34 |
+
- 7
|
| 35 |
+
ignore:
|
| 36 |
+
- goos: windows
|
| 37 |
+
goarch: arm
|
| 38 |
+
- goos: windows
|
| 39 |
+
goarch: s390x
|
| 40 |
+
- goos: windows
|
| 41 |
+
goarch: loong64
|
| 42 |
+
- goos: darwin
|
| 43 |
+
goarch: arm
|
| 44 |
+
- goos: darwin
|
| 45 |
+
goarch: s390x
|
| 46 |
+
- goos: darwin
|
| 47 |
+
goarch: loong64
|
| 48 |
+
|
| 49 |
+
release:
|
| 50 |
+
draft: true
|
| 51 |
+
|
| 52 |
+
archives:
|
| 53 |
+
- id: archive
|
| 54 |
+
ids: [build]
|
| 55 |
+
formats: [zip]
|
| 56 |
+
files:
|
| 57 |
+
- LICENSE
|
| 58 |
+
- CHANGELOG.md
|
| 59 |
+
- src: deploy/install.sh
|
| 60 |
+
dst: install.sh
|
| 61 |
+
- src: deploy/start.sh
|
| 62 |
+
dst: start.sh
|
| 63 |
+
- src: deploy/stop.sh
|
| 64 |
+
dst: stop.sh
|
| 65 |
+
- src: deploy/install.ps1
|
| 66 |
+
dst: install.ps1
|
| 67 |
+
- src: deploy/start.ps1
|
| 68 |
+
dst: start.ps1
|
| 69 |
+
- src: deploy/stop.ps1
|
| 70 |
+
dst: stop.ps1
|
| 71 |
+
- src: deploy/install.bat
|
| 72 |
+
dst: install.bat
|
| 73 |
+
- src: deploy/start.bat
|
| 74 |
+
dst: start.bat
|
| 75 |
+
- src: deploy/stop.bat
|
| 76 |
+
dst: stop.bat
|
| 77 |
+
|
| 78 |
+
checksum:
|
| 79 |
+
name_template: 'checksums.txt'
|
| 80 |
+
|
| 81 |
+
snapshot:
|
| 82 |
+
version_template: '{{ incpatch .Version }}-snapshot'
|
| 83 |
+
|
| 84 |
+
changelog:
|
| 85 |
+
sort: asc
|
| 86 |
+
filters:
|
| 87 |
+
exclude:
|
| 88 |
+
- '^examples:'
|
| 89 |
+
- '^ui:'
|
.mockery.yml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
all: false
|
| 2 |
+
dir: '{{.InterfaceDir}}'
|
| 3 |
+
filename: '{{.InterfaceName|snakecase}}.mock.gen.go'
|
| 4 |
+
force-file-write: true
|
| 5 |
+
formatter: goimports
|
| 6 |
+
log-level: info
|
| 7 |
+
structname: '{{.Mock}}{{.InterfaceName}}'
|
| 8 |
+
pkgname: '{{.SrcPackageName}}'
|
| 9 |
+
recursive: false
|
| 10 |
+
require-template-schema-exists: true
|
| 11 |
+
template: testify
|
| 12 |
+
template-schema: '{{.Template}}.schema.json'
|
| 13 |
+
packages:
|
| 14 |
+
github.com/looplj/axonhub/llm/pipeline:
|
| 15 |
+
config:
|
| 16 |
+
interfaces:
|
| 17 |
+
Executor:
|
.pre-commit-config.yaml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
repos:
|
| 2 |
+
- repo: 'https://github.com/golangci/golangci-lint'
|
| 3 |
+
rev: v2.8.0
|
| 4 |
+
hooks:
|
| 5 |
+
- id: golangci-lint
|
| 6 |
+
entry: golangci-lint run -vvv
|
| 7 |
+
|
| 8 |
+
- repo: local
|
| 9 |
+
hooks:
|
| 10 |
+
- id: run-go-test
|
| 11 |
+
name: Run go test
|
| 12 |
+
entry: bash -c 'go test ./... --short'
|
| 13 |
+
language: system
|
| 14 |
+
types: [go]
|
| 15 |
+
pass_filenames: false
|
| 16 |
+
|
| 17 |
+
- repo: https://github.com/dnephin/pre-commit-golang
|
| 18 |
+
rev: v0.5.1
|
| 19 |
+
hooks:
|
| 20 |
+
# - id: go-build
|
| 21 |
+
- id: go-mod-tidy
|
| 22 |
+
|
AGENTS.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Repository Guidelines
|
| 2 |
+
|
| 3 |
+
@CLAUDE.md read and follow its instructions.
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
v0.4.0
|
| 2 |
+
|
| 3 |
+
- Introduced thread-aware tracing with zero-SDK integration and configurable trace headers
|
| 4 |
+
- Added trace visualization interface for following end-to-end conversations
|
| 5 |
+
- Added configurable data storage policies to keep or trim trace payloads based on compliance needs
|
| 6 |
+
|
| 7 |
+
v0.3.0
|
| 8 |
+
|
| 9 |
+
- Launched project workspace management with per-project API keys and dashboards
|
| 10 |
+
- Improved project-scoped permissions and usage insights
|
| 11 |
+
|
| 12 |
+
v0.2.0
|
| 13 |
+
|
| 14 |
+
- Added multimodal image generation via chat completions and image_generation tools
|
| 15 |
+
- Documented provider support and sample usage for image workflows
|
| 16 |
+
|
| 17 |
+
v0.1.1
|
| 18 |
+
|
| 19 |
+
- Add OpenRouter outbound transformer
|
| 20 |
+
- Support Google Gemini OpenAI compatible API
|
CLAUDE.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CLAUDE.md
|
| 2 |
+
|
| 3 |
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
| 4 |
+
|
| 5 |
+
## Project Overview
|
| 6 |
+
|
| 7 |
+
AxonHub is an all-in-one AI development platform that serves as a unified API gateway for multiple AI providers. It provides OpenAI and Anthropic-compatible API interfaces with automatic request transformation, enabling seamless communication between clients and various AI providers through a sophisticated bidirectional data transformation pipeline.
|
| 8 |
+
|
| 9 |
+
### Core Architecture
|
| 10 |
+
- **Transformation Pipeline**: Bidirectional data transformation between clients and AI providers
|
| 11 |
+
- **Unified API Layer**: OpenAI/Anthropic-compatible interfaces with automatic translation
|
| 12 |
+
- **Channel Management**: Multi-provider support with configurable channels
|
| 13 |
+
- **Thread-aware Tracing**: Request tracing with thread linking capabilities
|
| 14 |
+
- **Permission System**: RBAC with fine-grained access control
|
| 15 |
+
- **System Management**: Web-based configuration interface
|
| 16 |
+
|
| 17 |
+
## Development Commands
|
| 18 |
+
|
| 19 |
+
### Backend (Go)
|
| 20 |
+
```bash
|
| 21 |
+
# Run the main server
|
| 22 |
+
go run cmd/axonhub/main.go
|
| 23 |
+
|
| 24 |
+
# Generate GraphQL and Ent code (run after schema changes)
|
| 25 |
+
make generate
|
| 26 |
+
|
| 27 |
+
# Run tests
|
| 28 |
+
go test ./...
|
| 29 |
+
|
| 30 |
+
# Run linting
|
| 31 |
+
golangci-lint run
|
| 32 |
+
|
| 33 |
+
# Build the application
|
| 34 |
+
make build-backend
|
| 35 |
+
|
| 36 |
+
# Use air for hot reload (development) - server auto-restarts on changes
|
| 37 |
+
air
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### Frontend (React)
|
| 41 |
+
```bash
|
| 42 |
+
# Navigate to frontend directory
|
| 43 |
+
cd frontend
|
| 44 |
+
|
| 45 |
+
# Install dependencies
|
| 46 |
+
pnpm install
|
| 47 |
+
|
| 48 |
+
# Start development server (port 5173) - already configured with backend proxy
|
| 49 |
+
pnpm dev
|
| 50 |
+
|
| 51 |
+
# Build for production
|
| 52 |
+
pnpm build
|
| 53 |
+
|
| 54 |
+
# Run linting
|
| 55 |
+
pnpm lint
|
| 56 |
+
|
| 57 |
+
# Format code
|
| 58 |
+
pnpm format
|
| 59 |
+
|
| 60 |
+
# Check for unused dependencies
|
| 61 |
+
pnpm knip
|
| 62 |
+
|
| 63 |
+
# Tests (Playwright)
|
| 64 |
+
pnpm test:e2e # run E2E suite via scripts/e2e
|
| 65 |
+
pnpm test:e2e:headed # headed browser mode
|
| 66 |
+
pnpm test:e2e:ui # Playwright UI runner
|
| 67 |
+
pnpm test:e2e:debug # debug mode
|
| 68 |
+
pnpm test:e2e:report # open last HTML report
|
| 69 |
+
pnpm test:ui # Playwright UI (direct)
|
| 70 |
+
pnpm test:ui:headed # Playwright headed (direct)
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### Make Commands
|
| 74 |
+
```bash
|
| 75 |
+
# Generate GraphQL and Ent code
|
| 76 |
+
make generate
|
| 77 |
+
|
| 78 |
+
# Build backend only
|
| 79 |
+
make build-backend
|
| 80 |
+
|
| 81 |
+
# Build frontend only
|
| 82 |
+
make build-frontend
|
| 83 |
+
|
| 84 |
+
# Build both frontend and backend
|
| 85 |
+
make build
|
| 86 |
+
|
| 87 |
+
# Cleanup test database
|
| 88 |
+
make cleanup-db
|
| 89 |
+
|
| 90 |
+
# E2E Testing
|
| 91 |
+
make e2e-test # Run full E2E test suite
|
| 92 |
+
make e2e-backend-start # Start E2E backend service
|
| 93 |
+
make e2e-backend-stop # Stop E2E backend service
|
| 94 |
+
make e2e-backend-status # Check E2E backend status
|
| 95 |
+
make e2e-backend-restart # Restart E2E backend service
|
| 96 |
+
make e2e-backend-clean # Clean up E2E test files
|
| 97 |
+
|
| 98 |
+
# Migration Testing
|
| 99 |
+
make migration-test TAG=v0.1.0 # Test migration from specific tag
|
| 100 |
+
make migration-test-all # Test migration for all recent versions
|
| 101 |
+
make migration-test-all-dbs TAG=v0.1.0 # Test migration across all DB types
|
| 102 |
+
|
| 103 |
+
# Data Syncing
|
| 104 |
+
make sync-faq # Sync FAQ from GitHub issues
|
| 105 |
+
make sync-models # Sync model developers data
|
| 106 |
+
|
| 107 |
+
# Utilities
|
| 108 |
+
make filter-logs # Filter and analyze load balance logs
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
## Architecture Overview
|
| 112 |
+
|
| 113 |
+
### Technology Stack
|
| 114 |
+
- **Backend**: Go 1.25.3+ with Gin HTTP framework, Ent ORM, gqlgen GraphQL, FX dependency injection
|
| 115 |
+
- **Frontend**: React 19 with TypeScript, TanStack Router, TanStack Query, Zustand, Tailwind CSS
|
| 116 |
+
- **Database**: SQLite (development), PostgreSQL/MySQL/TiDB (production)
|
| 117 |
+
- **Authentication**: JWT with role-based access control
|
| 118 |
+
|
| 119 |
+
### Backend Structure
|
| 120 |
+
- **Server Layer** (`internal/server/`): HTTP server and route handling with Gin
|
| 121 |
+
- **Business Logic** (`internal/server/biz/`): Core business logic and services
|
| 122 |
+
- **API Layer** (`internal/server/api/`): REST and GraphQL API handlers
|
| 123 |
+
- **Database** (`internal/ent/`): Ent ORM for database operations with SQLite
|
| 124 |
+
- **LLM Integration** (`internal/llm/`): AI provider transformers and pipeline processing
|
| 125 |
+
- **Context Management** (`internal/contexts/`): Context handling utilities
|
| 126 |
+
- **Utilities** (`internal/pkg/`): Shared utilities (HTTP client, streams, errors, JSON)
|
| 127 |
+
- **Auth & Scopes** (`internal/scopes/`): Permission system with role-based access control
|
| 128 |
+
|
| 129 |
+
### Frontend Structure
|
| 130 |
+
- **TanStack Router**: File-based routing in `frontend/src/routes/`
|
| 131 |
+
- **TanStack Query**: Data fetching and caching with GraphQL
|
| 132 |
+
- **TanStack Table**: Data tables with pagination/filtering
|
| 133 |
+
- **GraphQL**: API communication via `frontend/src/gql/`
|
| 134 |
+
- **Shadcn/ui**: Component library with Tailwind CSS
|
| 135 |
+
- **Zustand**: State management in `frontend/src/stores/`
|
| 136 |
+
- **AI SDK**: Integration for enhanced AI capabilities
|
| 137 |
+
- **Feature-based Organization**: Components organized by feature in `frontend/src/features/`
|
| 138 |
+
- **Shared Components**: Reusable components in `frontend/src/components/`
|
| 139 |
+
- **Custom Hooks**: Shared hooks in `frontend/src/hooks/`
|
| 140 |
+
- **Internationalization**: i18n support in `frontend/src/locales/` (en.json, zh.json)
|
| 141 |
+
- **Utilities**: Shared utilities in `frontend/src/utils/`
|
| 142 |
+
|
| 143 |
+
### Key Components
|
| 144 |
+
|
| 145 |
+
#### LLM Transformer System
|
| 146 |
+
- **Pipeline Architecture**: Enhanced request processing with retry capabilities
|
| 147 |
+
- **Persistent Transformers**: `PersistentInboundTransformer` and `PersistentOutboundTransformer`
|
| 148 |
+
- **Stream Processing**: Enhanced SSE support with chunk aggregation
|
| 149 |
+
- **Supported Providers**: OpenAI, Anthropic, DeepSeek, AI SDK
|
| 150 |
+
- **Auto-save**: Configurable persistence of chat requests and responses
|
| 151 |
+
- **Load Balancing**: Round-robin and failover strategies
|
| 152 |
+
|
| 153 |
+
#### Database Schema
|
| 154 |
+
- **Users**: Authentication and role management with soft delete
|
| 155 |
+
- **Roles**: Permission groups with scope-based access
|
| 156 |
+
- **Channels**: AI provider configurations
|
| 157 |
+
- **API Keys**: Authentication tokens
|
| 158 |
+
- **Requests**: Request logging and execution tracking
|
| 159 |
+
- **Systems**: System-wide configuration (storeChunks, etc.)
|
| 160 |
+
- **Soft Delete**: Data safety across all entities
|
| 161 |
+
|
| 162 |
+
#### Permission System
|
| 163 |
+
- **Enhanced Scopes**: read_channels, write_channels, read_users, read_settings, write_settings
|
| 164 |
+
- **Owner scope**: Full system access
|
| 165 |
+
- **Role-based access control**: Users can have multiple roles
|
| 166 |
+
- **Ent privacy policies**: Database-level permission enforcement
|
| 167 |
+
- **Granular permissions**: Fine-grained access control
|
| 168 |
+
|
| 169 |
+
#### System Management
|
| 170 |
+
- **Web Interface**: Complete system settings management
|
| 171 |
+
- **Configuration Options**: Controllable persistence and system behavior
|
| 172 |
+
- **Real-time Updates**: Live configuration changes
|
| 173 |
+
- **GraphQL API**: System configuration endpoints
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
### Development Workflow
|
| 177 |
+
|
| 178 |
+
#### General Notes
|
| 179 |
+
- All summary files should be stored in `.windsurf/summary` directory if available
|
| 180 |
+
|
| 181 |
+
#### Backend Development
|
| 182 |
+
1. **Code Changes**: Modify Go code in `internal/` directory
|
| 183 |
+
2. **Schema Changes**: If modifying Ent schemas (`internal/ent/schema/`), run `make generate`
|
| 184 |
+
- When changing any Ent schema or GraphQL schema, run `make generate` to regenerate models and resolvers
|
| 185 |
+
- The `make generate` command automatically enters the gql directory and runs go generate
|
| 186 |
+
3. **GraphQL Changes**: If modifying GraphQL schemas (`internal/server/gql/`), run `make generate`
|
| 187 |
+
4. **Hot Reload**: Use `air` for automatic server restart on changes (configured in `.air.toml`)
|
| 188 |
+
- **CRITICAL**: The server in development is managed by air - it will rebuild and start when code changes, so DO NOT restart manually
|
| 189 |
+
- Air watches `.go` and `.yml` files
|
| 190 |
+
- Excludes directories: `frontend`, `integration_test`, `scripts`, `tools`, `examples`
|
| 191 |
+
- Builds to `./tmp/axonhub`
|
| 192 |
+
|
| 193 |
+
**Dev vs prod builds**
|
| 194 |
+
- Dev hot-reload uses Air (`.air.toml`) and builds to `./tmp/axonhub`.
|
| 195 |
+
- Production build uses `make build-backend` and outputs `./axonhub`.
|
| 196 |
+
|
| 197 |
+
5. **Building**: Use `make build-backend` to build the server for production deployment
|
| 198 |
+
6. **Testing**: Run `go test ./...` for unit tests
|
| 199 |
+
7. **Linting**: Run `golangci-lint run` before committing
|
| 200 |
+
|
| 201 |
+
#### Frontend Development
|
| 202 |
+
1. **Start Dev Server**: Run `pnpm dev` in `frontend/` directory (port 5173)
|
| 203 |
+
- **CRITICAL**: DO NOT restart the development server - it's already started and managed
|
| 204 |
+
2. **Proxy Configuration**: Frontend proxies API requests to backend on port 8090
|
| 205 |
+
3. **Code Changes**: Modify React components in `frontend/src/`
|
| 206 |
+
4. **Internationalization**:
|
| 207 |
+
- **REQUIRED**: MUST ADD i18n key in the `frontend/src/locales/*.json` files if creating a new key in the code
|
| 208 |
+
- **REQUIRED**: MUST KEEP THE KEY IN THE CODE AND JSON FILE THE SAME
|
| 209 |
+
- Support both English and Chinese translations (`en.json` and `zh.json`)
|
| 210 |
+
- Add i18n keys to both `frontend/src/locales/en.json` and `frontend/src/locales/zh.json`
|
| 211 |
+
5. **Testing**: Use `pnpm test:e2e` for E2E tests or `pnpm test:ui` for Playwright UI tests
|
| 212 |
+
6. **Linting**: Run `pnpm lint` before committing
|
| 213 |
+
|
| 214 |
+
**Additional Frontend Development Rules:**
|
| 215 |
+
- Use `pnpm` as the package manager exclusively
|
| 216 |
+
- Use GraphQL input to filter data instead of filtering in the frontend
|
| 217 |
+
- Search filters must use debounce to avoid too many requests
|
| 218 |
+
- Add sidebar data and route when adding new feature pages
|
| 219 |
+
- Use `extractNumberID` to extract int id from the GUID
|
| 220 |
+
- Follow component organization in `frontend/src/features/`
|
| 221 |
+
|
| 222 |
+
#### Database Development
|
| 223 |
+
1. **Schema Changes**: Modify Ent schemas in `internal/ent/schema/`
|
| 224 |
+
2. **Code Generation**: Run `make generate` to regenerate Ent code
|
| 225 |
+
3. **Migrations**: Auto-applied on server start
|
| 226 |
+
4. **Testing**: Use `enttest.NewEntClient(t, "sqlite3", "file:ent?mode=memory&_fk=0")` for isolated tests
|
| 227 |
+
|
| 228 |
+
#### Integration Testing
|
| 229 |
+
1. **E2E Tests**: Run `make e2e-test` for full integration test suite
|
| 230 |
+
2. **Migration Tests**: Run `make migration-test TAG=v0.1.0` to test database migrations
|
| 231 |
+
3. **Cross-DB Tests**: Run `make migration-test-all-dbs TAG=v0.1.0` for multi-database migration testing
|
| 232 |
+
|
| 233 |
+
## Important Files
|
| 234 |
+
|
| 235 |
+
### Backend
|
| 236 |
+
- `cmd/axonhub/main.go`: Application entry point
|
| 237 |
+
- `internal/server/server.go`: HTTP server configuration
|
| 238 |
+
- `internal/llm/pipeline/`: Pipeline processing architecture
|
| 239 |
+
- `internal/ent/schema/`: Database schema definitions
|
| 240 |
+
- `internal/pkg/`: Shared utilities and helpers
|
| 241 |
+
- `conf/conf.go`: Configuration loading and validation
|
| 242 |
+
- `internal/server/gql/`: GraphQL schema and resolvers
|
| 243 |
+
|
| 244 |
+
### Frontend
|
| 245 |
+
- `frontend/src/app/`: React Router v7 app directory
|
| 246 |
+
- `frontend/src/features/`: Feature-based component organization
|
| 247 |
+
- `frontend/src/features/system/`: System management interface
|
| 248 |
+
- `frontend/src/locales/`: Internationalization files (en.json, zh.json)
|
| 249 |
+
|
| 250 |
+
### Configuration & Documentation
|
| 251 |
+
- `config.yml` / `config.example.yml`: Main configuration files
|
| 252 |
+
- `AGENTS.md`: Repository guidelines for contributors
|
| 253 |
+
- `README.md` / `README.zh-CN.md`: Project documentation
|
| 254 |
+
- `docs/`: Detailed documentation and architecture diagrams
|
| 255 |
+
|
| 256 |
+
## Key Development Patterns
|
| 257 |
+
|
| 258 |
+
### Adding a New AI Provider Channel
|
| 259 |
+
When introducing a new provider channel, keep backend and frontend changes aligned:
|
| 260 |
+
|
| 261 |
+
1. **Extend the channel enum in the Ent schema** – add the provider key to the `field.Enum("type")` list in `internal/ent/schema/channel.go` and regenerate Ent artifacts
|
| 262 |
+
2. **Wire the outbound transformer** – update the switch in `ChannelService.buildChannel` to construct the correct outbound transformer for the new enum
|
| 263 |
+
3. **Sync the frontend schema** – update:
|
| 264 |
+
- Zod schema in `frontend/src/features/channels/data/schema.ts`
|
| 265 |
+
- Channel configuration in `frontend/src/features/channels/data/constants.ts`
|
| 266 |
+
- Internationalization in `frontend/src/locales/en.json` and `frontend/src/locales/zh.json`
|
| 267 |
+
|
| 268 |
+
### Database Schema Changes
|
| 269 |
+
- Always run `make generate` after modifying Ent schemas
|
| 270 |
+
- Use `enttest.NewEntClient(t, "sqlite3", "file:ent?mode=memory&_fk=0")` for testing
|
| 271 |
+
- Follow soft delete patterns across all entities
|
| 272 |
+
|
| 273 |
+
### Go Development Patterns
|
| 274 |
+
- **REQUIRED**: USE `github.com/samber/lo` package to handle collection, slice, map, ptr, etc.
|
| 275 |
+
- Follow dependency injection patterns using FX framework
|
| 276 |
+
- Use structured logging with zap
|
| 277 |
+
- Implement proper context propagation
|
| 278 |
+
- **Key Dependencies**:
|
| 279 |
+
- `entgo.io/ent` - ORM framework with code generation
|
| 280 |
+
- `github.com/99designs/gqlgen` - GraphQL code generation
|
| 281 |
+
- `github.com/gin-gonic/gin` - HTTP framework
|
| 282 |
+
- `go.uber.org/fx` - Dependency injection
|
| 283 |
+
- `github.com/google/uuid` - UUID generation
|
| 284 |
+
- `github.com/redis/go-redis/v9` - Redis client
|
| 285 |
+
- `github.com/jackc/pgx/v5` - PostgreSQL driver
|
| 286 |
+
- `github.com/go-sql-driver/mysql` - MySQL driver
|
| 287 |
+
|
| 288 |
+
### Biz Service Rules
|
| 289 |
+
- **REQUIRED**: Ensure the dependency service not be nil, the logic code should not check the service is nil.
|
| 290 |
+
- **REQUIRED**: Dependency services are guaranteed initialized; business logic must not add nil checks.
|
| 291 |
+
|
| 292 |
+
### Error Handling
|
| 293 |
+
- **REQUIRED**: Always handle errors using the unified error response format from `internal/pkg/errors`
|
| 294 |
+
- Implement proper error wrapping with context
|
| 295 |
+
- Follow middleware-based error recovery patterns
|
| 296 |
+
|
| 297 |
+
### GraphQL Development
|
| 298 |
+
- **Method Signatures/New Fields**: Resolver method signatures and new field definitions should always be generated by modifying `*.graphql` schema files and running `make generate`
|
| 299 |
+
- **Implementation Content**: The implementation body of existing resolver methods can be manually modified
|
| 300 |
+
- Run `make generate` from the project root after schema changes
|
| 301 |
+
- Use GraphQL input filtering instead of frontend filtering for data queries
|
| 302 |
+
|
| 303 |
+
#### Adding New GraphQL Types
|
| 304 |
+
When adding new types or inputs to the GraphQL schema, you need to:
|
| 305 |
+
|
| 306 |
+
1. **Add the type/input definition** in the appropriate `.graphql` file in `internal/server/gql/`
|
| 307 |
+
2. **Add type mapping in `gqlgen.yml`** under the `models:` section - map the GraphQL type to the Go type:
|
| 308 |
+
```yaml
|
| 309 |
+
MyNewType:
|
| 310 |
+
model:
|
| 311 |
+
- github.com/looplj/axonhub/internal/package.MyType
|
| 312 |
+
MyNewInput:
|
| 313 |
+
model:
|
| 314 |
+
- github.com/looplj/axonhub/internal/package.MyInput
|
| 315 |
+
```
|
| 316 |
+
3. **Run `make generate`** to regenerate the code
|
| 317 |
+
4. **Implement the resolver** in the appropriate `*_resolvers.go` file
|
| 318 |
+
|
| 319 |
+
## Quality gates (match CI / pre-commit)
|
| 320 |
+
|
| 321 |
+
Backend:
|
| 322 |
+
- `go test -v --short ./...`
|
| 323 |
+
- `golangci-lint run`
|
| 324 |
+
|
| 325 |
+
Pre-commit hooks (see `.pre-commit-config.yaml`) also run:
|
| 326 |
+
- `go mod tidy`
|
| 327 |
+
|
| 328 |
+
## Testing
|
| 329 |
+
|
| 330 |
+
### Backend Testing
|
| 331 |
+
- **Unit Tests**: Go unit tests with testify
|
| 332 |
+
- **Integration Tests**: Database integration tests with in-memory SQLite
|
| 333 |
+
- **Test Database**: Use `enttest.NewEntClient(t, "sqlite3", "file:ent?mode=memory&_fk=0")` for isolated tests
|
| 334 |
+
- **Code Quality**: `golangci-lint run` for Go linting
|
| 335 |
+
- **Error Handling**: Use unified error format from `internal/pkg/errors`
|
| 336 |
+
|
| 337 |
+
### Frontend Testing
|
| 338 |
+
- **E2E Tests**: Playwright E2E tests with UI and headed modes
|
| 339 |
+
- **Test Commands**:
|
| 340 |
+
- `pnpm test:e2e` - Run full E2E test suite
|
| 341 |
+
- `pnpm test:e2e:headed` - Run E2E tests in headed mode
|
| 342 |
+
- `pnpm test:e2e:ui` - Run E2E tests in UI mode
|
| 343 |
+
- `pnpm test:e2e:debug` - Run E2E tests in debug mode
|
| 344 |
+
- `pnpm test:ui` - Run Playwright tests in UI mode
|
| 345 |
+
- `pnpm test:ui:headed` - Run Playwright tests in headed mode
|
| 346 |
+
- **Code Quality**: `pnpm lint` for TypeScript/ESLint checking
|
| 347 |
+
- **Test Credentials**: Frontend testing uses `my@example.com` / `pwd123456`
|
| 348 |
+
|
| 349 |
+
### Integration Testing
|
| 350 |
+
- **E2E Test Suite**: Use `make e2e-test` or `bash ./scripts/e2e/e2e-test.sh` for full integration tests
|
| 351 |
+
- **Migration Testing**: Use `make migration-test TAG=v0.1.0` to test database migrations
|
| 352 |
+
- **Cross-DB Testing**: Use `make migration-test-all-dbs TAG=v0.1.0` to test migrations across all supported databases
|
| 353 |
+
- **Test Cleanup**: Use `make cleanup-db` to remove playwright test data from database
|
| 354 |
+
|
| 355 |
+
## Key Features in Development
|
| 356 |
+
- Enhanced transformer stream aggregation
|
| 357 |
+
- Configurable persistence behavior
|
| 358 |
+
- System options for controlling data storage
|
| 359 |
+
- Improved error handling and recovery mechanisms
|
| 360 |
+
- Stream closing when client disconnects
|
| 361 |
+
- Real-time request tracing and monitoring
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
## Configuration
|
| 365 |
+
|
| 366 |
+
### Environment Setup
|
| 367 |
+
- Uses SQLite database (axonhub.db) by default
|
| 368 |
+
- Configuration loaded from `conf/conf.go` with YAML and env var support
|
| 369 |
+
- Logging with structured JSON output using zap
|
| 370 |
+
- FX dependency injection framework
|
| 371 |
+
- Go version: 1.25.3+
|
| 372 |
+
- Frontend development server: port 5173 (proxies to backend)
|
| 373 |
+
- Backend API: port 8090
|
| 374 |
+
|
| 375 |
+
### Database Support
|
| 376 |
+
- **Development**: SQLite (default, auto-migration)
|
| 377 |
+
- **Production**: PostgreSQL 15+, MySQL 8.0+, TiDB V8.0+
|
| 378 |
+
- **Cloud**: TiDB Cloud, Neon DB (serverless options)
|
| 379 |
+
- All support automatic schema migration
|
| 380 |
+
|
| 381 |
+
### Key Configuration Files
|
| 382 |
+
- `config.yml` / `config.example.yml`: Main configuration files (YAML format)
|
| 383 |
+
- `.air.toml`: Air hot reload configuration for backend development
|
| 384 |
+
- `.golangci.yml`: Go linting configuration
|
| 385 |
+
- `.pre-commit-config.yaml`: Git pre-commit hooks
|
| 386 |
+
- `frontend/vite.config.ts`: Frontend build configuration
|
| 387 |
+
- `docker-compose.yml`: Docker Compose configuration for containerized deployment
|
| 388 |
+
- `render.yaml`: Render deployment configuration
|
| 389 |
+
|
| 390 |
+
### Configuration Management
|
| 391 |
+
- **Primary Config**: `config.yml` (create from `config.example.yml`)
|
| 392 |
+
- **Environment Variables**: All configuration options can be overridden with environment variables
|
| 393 |
+
- Format: `AXONHUB_{SECTION}_{KEY}` (e.g., `AXONHUB_SERVER_PORT=8090`)
|
| 394 |
+
- Nested keys use underscores (e.g., `AXONHUB_SERVER_CORS_ENABLED=true`)
|
| 395 |
+
- **Database Support**: Configurable via `db.dialect` and `db.dsn`
|
| 396 |
+
- SQLite (default): `dialect: "sqlite3"`, `dsn: "axonhub.db"`
|
| 397 |
+
- PostgreSQL: `dialect: "postgres"`, `dsn: "host=..."`
|
| 398 |
+
- MySQL: `dialect: "mysql"`, `dsn: "user:pass@tcp(...)/axonhub"`
|
| 399 |
+
- TiDB: `dialect: "tidb"`, `dsn: "user.root:pass@tcp(...)/axonhub?tls=true"`
|
| 400 |
+
|
| 401 |
+
## Common Development Tasks
|
| 402 |
+
|
| 403 |
+
### Running a Single Test
|
| 404 |
+
```bash
|
| 405 |
+
# Backend: Run specific test
|
| 406 |
+
go test -v -run TestFunctionName ./path/to/package
|
| 407 |
+
|
| 408 |
+
# Frontend: Run specific test file
|
| 409 |
+
pnpm test:e2e -- path/to/test.spec.ts
|
| 410 |
+
```
|
| 411 |
+
|
| 412 |
+
### Database Migration
|
| 413 |
+
```bash
|
| 414 |
+
# Generate new migration after schema changes
|
| 415 |
+
make generate
|
| 416 |
+
|
| 417 |
+
# The migration will be auto-applied on server start
|
| 418 |
+
```
|
| 419 |
+
|
| 420 |
+
### Debugging Stream Processing
|
| 421 |
+
- Check `internal/llm/pipeline/` for stream aggregation logic
|
| 422 |
+
- Monitor SSE connections in browser DevTools Network tab
|
| 423 |
+
- Use request tracing with `AH-Trace-Id` header for debugging
|
| 424 |
+
|
| 425 |
+
### Performance Optimization
|
| 426 |
+
- Use request tracing to identify bottlenecks
|
| 427 |
+
- Monitor database queries with Ent's built-in logging
|
| 428 |
+
- Check channel load balancing configuration for optimal distribution
|
LICENSE
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
AxonHub Licensing Overview:
|
| 2 |
+
1. Priority: If a specific file contains a different license header, the terms in that header take precedence for that file.
|
| 3 |
+
2. Scope: The `llm/` directory in this project is licensed under the GNU Lesser General Public License version 3.0 (LGPL-3.0). See `llm/LICENSE` for details.
|
| 4 |
+
3. Scope: The `llm/bedrock/` directory in this project is copied from anthropic-sdk-go. See `llm/bedrock/NOTICE` for details.
|
| 5 |
+
4. General: All other parts of this project are licensed under the Apache License, Version 2.0, unless otherwise specified.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
Apache License
|
| 10 |
+
Version 2.0, January 2004
|
| 11 |
+
http://www.apache.org/licenses/
|
| 12 |
+
|
| 13 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 14 |
+
|
| 15 |
+
1. Definitions.
|
| 16 |
+
|
| 17 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 18 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 19 |
+
|
| 20 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 21 |
+
the copyright owner that is granting the License.
|
| 22 |
+
|
| 23 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 24 |
+
other entities that control, are controlled by, or are under common
|
| 25 |
+
control with that entity. For the purposes of this definition,
|
| 26 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 27 |
+
direction or management of such entity, whether by contract or
|
| 28 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 29 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 30 |
+
|
| 31 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 32 |
+
exercising permissions granted by this License.
|
| 33 |
+
|
| 34 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 35 |
+
including but not limited to software source code, documentation
|
| 36 |
+
source, and configuration files.
|
| 37 |
+
|
| 38 |
+
"Object" form shall mean any form resulting from mechanical
|
| 39 |
+
transformation or translation of a Source form, including but
|
| 40 |
+
not limited to compiled object code, generated documentation,
|
| 41 |
+
and conversions to other media types.
|
| 42 |
+
|
| 43 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 44 |
+
Object form, made available under the License, as indicated by a
|
| 45 |
+
copyright notice that is included in or attached to the work
|
| 46 |
+
(an example is provided in the Appendix below).
|
| 47 |
+
|
| 48 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 49 |
+
form, that is based on (or derived from) the Work and for which the
|
| 50 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 51 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 52 |
+
of this License, Derivative Works shall not include works that remain
|
| 53 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 54 |
+
the Work and Derivative Works thereof.
|
| 55 |
+
|
| 56 |
+
"Contribution" shall mean any work of authorship, including
|
| 57 |
+
the original version of the Work and any modifications or additions
|
| 58 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 59 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 60 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 61 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 62 |
+
means any form of electronic, verbal, or written communication sent
|
| 63 |
+
to the Licensor or its representatives, including but not limited to
|
| 64 |
+
communication on electronic mailing lists, source code control systems,
|
| 65 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 66 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 67 |
+
excluding communication that is conspicuously marked or otherwise
|
| 68 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 69 |
+
|
| 70 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 71 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 72 |
+
subsequently incorporated within the Work.
|
| 73 |
+
|
| 74 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 78 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 79 |
+
Work and such Derivative Works in Source or Object form.
|
| 80 |
+
|
| 81 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 82 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 83 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 84 |
+
(except as stated in this section) patent license to make, have made,
|
| 85 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 86 |
+
where such license applies only to those patent claims licensable
|
| 87 |
+
by such Contributor that are necessarily infringed by their
|
| 88 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 89 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 90 |
+
institute patent litigation against any entity (including a
|
| 91 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 92 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 93 |
+
or contributory patent infringement, then any patent licenses
|
| 94 |
+
granted to You under this License for that Work shall terminate
|
| 95 |
+
as of the date such litigation is filed.
|
| 96 |
+
|
| 97 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 98 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 99 |
+
modifications, and in Source or Object form, provided that You
|
| 100 |
+
meet the following conditions:
|
| 101 |
+
|
| 102 |
+
(a) You must give any other recipients of the Work or
|
| 103 |
+
Derivative Works a copy of this License; and
|
| 104 |
+
|
| 105 |
+
(b) You must cause any modified files to carry prominent notices
|
| 106 |
+
stating that You changed the files; and
|
| 107 |
+
|
| 108 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 109 |
+
that You distribute, all copyright, patent, trademark, and
|
| 110 |
+
attribution notices from the Source form of the Work,
|
| 111 |
+
excluding those notices that do not pertain to any part of
|
| 112 |
+
the Derivative Works; and
|
| 113 |
+
|
| 114 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 115 |
+
distribution, then any Derivative Works that You distribute must
|
| 116 |
+
include a readable copy of the attribution notices contained
|
| 117 |
+
within such NOTICE file, excluding those notices that do not
|
| 118 |
+
pertain to any part of the Derivative Works, in at least one
|
| 119 |
+
of the following places: within a NOTICE text file distributed
|
| 120 |
+
as part of the Derivative Works; within the Source form or
|
| 121 |
+
documentation, if provided along with the Derivative Works; or,
|
| 122 |
+
within a display generated by the Derivative Works, if and
|
| 123 |
+
wherever such third-party notices normally appear. The contents
|
| 124 |
+
of the NOTICE file are for informational purposes only and
|
| 125 |
+
do not modify the License. You may add Your own attribution
|
| 126 |
+
notices within Derivative Works that You distribute, alongside
|
| 127 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 128 |
+
that such additional attribution notices cannot be construed
|
| 129 |
+
as modifying the License.
|
| 130 |
+
|
| 131 |
+
You may add Your own copyright statement to Your modifications and
|
| 132 |
+
may provide additional or different license terms and conditions
|
| 133 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 134 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 135 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 136 |
+
the conditions stated in this License.
|
| 137 |
+
|
| 138 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 139 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 140 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 141 |
+
this License, without any additional terms or conditions.
|
| 142 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 143 |
+
the terms of any separate license agreement you may have executed
|
| 144 |
+
with Licensor regarding such Contributions.
|
| 145 |
+
|
| 146 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 147 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 148 |
+
except as required for reasonable and customary use in describing the
|
| 149 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 150 |
+
|
| 151 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 152 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 153 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 154 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 155 |
+
implied, including, without limitation, any warranties or conditions
|
| 156 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 157 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 158 |
+
appropriateness of using or redistributing the Work and assume any
|
| 159 |
+
risks associated with Your exercise of permissions under this License.
|
| 160 |
+
|
| 161 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 162 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 163 |
+
unless required by applicable law (such as deliberate and grossly
|
| 164 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 165 |
+
liable to You for damages, including any direct, indirect, special,
|
| 166 |
+
incidental, or consequential damages of any character arising as a
|
| 167 |
+
result of this License or out of the use or inability to use the
|
| 168 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 169 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 170 |
+
other commercial damages or losses), even if such Contributor
|
| 171 |
+
has been advised of the possibility of such damages.
|
| 172 |
+
|
| 173 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 174 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 175 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 176 |
+
or other liability obligations and/or rights consistent with this
|
| 177 |
+
License. However, in accepting such obligations, You may act only
|
| 178 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 179 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 180 |
+
defend, and hold each Contributor harmless for any liability
|
| 181 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 182 |
+
of your accepting any such warranty or additional liability.
|
| 183 |
+
|
| 184 |
+
END OF TERMS AND CONDITIONS
|
| 185 |
+
|
| 186 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 187 |
+
|
| 188 |
+
To apply the Apache License to your work, attach the following
|
| 189 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 190 |
+
replaced with your own identifying information. (Don't include
|
| 191 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 192 |
+
comment syntax for the file format. We also recommend that a
|
| 193 |
+
file or class name and description of purpose be included on the
|
| 194 |
+
same "printed page" as the copyright notice for easier
|
| 195 |
+
identification within third-party archives.
|
| 196 |
+
|
| 197 |
+
Copyright 2026 looplj
|
| 198 |
+
|
| 199 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 200 |
+
you may not use this file except in compliance with the License.
|
| 201 |
+
You may obtain a copy of the License at
|
| 202 |
+
|
| 203 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 204 |
+
|
| 205 |
+
Unless required by applicable law or agreed to in writing, software
|
| 206 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 207 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 208 |
+
See the License for the specific language governing permissions and
|
| 209 |
+
limitations under the License.
|
| 210 |
+
|
Makefile
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.PHONY: generate build backend frontend cleanup-db \
|
| 2 |
+
e2e-test e2e-backend-start e2e-backend-stop e2e-backend-status e2e-backend-restart e2e-backend-clean \
|
| 3 |
+
migration-test migration-test-all migration-test-all-dbs \
|
| 4 |
+
sync-faq sync-models filter-logs
|
| 5 |
+
|
| 6 |
+
# Generate GraphQL and Ent code
|
| 7 |
+
generate:
|
| 8 |
+
@echo "Generating GraphQL and Ent code..."
|
| 9 |
+
cd internal/server/gql && go generate
|
| 10 |
+
@echo "Generation completed!"
|
| 11 |
+
|
| 12 |
+
generate-openapi:
|
| 13 |
+
@echo "Generating GraphQL and Ent code..."
|
| 14 |
+
cd internal/server/gql/openapi && go generate
|
| 15 |
+
@echo "Generation completed!"
|
| 16 |
+
|
| 17 |
+
# Build the backend application
|
| 18 |
+
build-backend:
|
| 19 |
+
@echo "Building axonhub backend..."
|
| 20 |
+
go build -ldflags "-s -w" -tags=nomsgpack -o axonhub ./cmd/axonhub
|
| 21 |
+
@echo "Backend build completed!"
|
| 22 |
+
|
| 23 |
+
# Build the frontend application
|
| 24 |
+
build-frontend:
|
| 25 |
+
@echo "Building axonhub frontend..."
|
| 26 |
+
cd frontend && pnpm vite build
|
| 27 |
+
@echo "Copying frontend dist to server static directory..."
|
| 28 |
+
rm -rf internal/server/static/dist/assets
|
| 29 |
+
mkdir -p internal/server/static/dist
|
| 30 |
+
cp -r frontend/dist/* internal/server/static/dist/
|
| 31 |
+
@echo "Frontend build completed!"
|
| 32 |
+
|
| 33 |
+
# Build both frontend and backend
|
| 34 |
+
build: build-frontend build-backend
|
| 35 |
+
@echo "Full build completed!"
|
| 36 |
+
|
| 37 |
+
# Cleanup test database - remove all playwright test data
|
| 38 |
+
cleanup-db:
|
| 39 |
+
@echo "Cleaning up playwright test data from database..."
|
| 40 |
+
@sqlite3 axonhub.db "DELETE FROM user_roles WHERE user_id IN (SELECT id FROM users WHERE email LIKE 'pw-test-%' OR first_name LIKE 'pw-test%');"
|
| 41 |
+
@sqlite3 axonhub.db "DELETE FROM user_projects WHERE user_id IN (SELECT id FROM users WHERE email LIKE 'pw-test-%' OR first_name LIKE 'pw-test%');"
|
| 42 |
+
@sqlite3 axonhub.db "DELETE FROM user_projects WHERE project_id IN (SELECT id FROM projects WHERE slug LIKE 'pw-test-%' OR name LIKE 'pw-test-%');"
|
| 43 |
+
@sqlite3 axonhub.db "DELETE FROM api_keys WHERE name LIKE 'pw-test-%';"
|
| 44 |
+
@sqlite3 axonhub.db "DELETE FROM api_keys WHERE user_id IN (SELECT id FROM users WHERE email LIKE 'pw-test-%' OR first_name LIKE 'pw-test%');"
|
| 45 |
+
@sqlite3 axonhub.db "DELETE FROM api_keys WHERE project_id IN (SELECT id FROM projects WHERE slug LIKE 'pw-test-%' OR name LIKE 'pw-test-%');"
|
| 46 |
+
@sqlite3 axonhub.db "DELETE FROM roles WHERE code LIKE 'pw-test-%' OR name LIKE 'pw-test-%';"
|
| 47 |
+
@sqlite3 axonhub.db "DELETE FROM roles WHERE project_id IN (SELECT id FROM projects WHERE slug LIKE 'pw-test-%' OR name LIKE 'pw-test-%');"
|
| 48 |
+
@sqlite3 axonhub.db "DELETE FROM usage_logs WHERE project_id IN (SELECT id FROM projects WHERE slug LIKE 'pw-test-%' OR name LIKE 'pw-test-%');"
|
| 49 |
+
@sqlite3 axonhub.db "DELETE FROM requests WHERE project_id IN (SELECT id FROM projects WHERE slug LIKE 'pw-test-%' OR name LIKE 'pw-test-%');"
|
| 50 |
+
@sqlite3 axonhub.db "DELETE FROM users WHERE email LIKE 'pw-test-%' OR first_name LIKE 'pw-test%';"
|
| 51 |
+
@sqlite3 axonhub.db "DELETE FROM projects WHERE slug LIKE 'pw-test-%' OR name LIKE 'pw-test-%';"
|
| 52 |
+
@echo "Cleanup completed!"
|
| 53 |
+
|
| 54 |
+
# --- E2E Testing ---
|
| 55 |
+
|
| 56 |
+
# Run the full E2E test suite
|
| 57 |
+
e2e-test:
|
| 58 |
+
@echo "Running E2E tests..."
|
| 59 |
+
@./scripts/e2e/e2e-test.sh
|
| 60 |
+
|
| 61 |
+
# Start the E2E backend service
|
| 62 |
+
e2e-backend-start:
|
| 63 |
+
@echo "Starting E2E backend..."
|
| 64 |
+
@./scripts/e2e/e2e-backend.sh start
|
| 65 |
+
|
| 66 |
+
# Stop the E2E backend service
|
| 67 |
+
e2e-backend-stop:
|
| 68 |
+
@echo "Stopping E2E backend..."
|
| 69 |
+
@./scripts/e2e/e2e-backend.sh stop
|
| 70 |
+
|
| 71 |
+
# Check E2E backend status
|
| 72 |
+
e2e-backend-status:
|
| 73 |
+
@./scripts/e2e/e2e-backend.sh status
|
| 74 |
+
|
| 75 |
+
# Restart the E2E backend service
|
| 76 |
+
e2e-backend-restart:
|
| 77 |
+
@echo "Restarting E2E backend..."
|
| 78 |
+
@./scripts/e2e/e2e-backend.sh restart
|
| 79 |
+
|
| 80 |
+
# Clean up E2E test files
|
| 81 |
+
e2e-backend-clean:
|
| 82 |
+
@echo "Cleaning up E2E test files..."
|
| 83 |
+
@./scripts/e2e/e2e-backend.sh clean
|
| 84 |
+
|
| 85 |
+
# --- Migration Testing ---
|
| 86 |
+
|
| 87 |
+
# Test database migration from a specific tag
|
| 88 |
+
# Usage: make migration-test TAG=v0.1.0
|
| 89 |
+
migration-test:
|
| 90 |
+
@if [ -z "$(TAG)" ]; then echo "Error: TAG is required. Usage: make migration-test TAG=v0.1.0"; exit 1; fi
|
| 91 |
+
@echo "Running migration test from $(TAG)..."
|
| 92 |
+
@./scripts/migration/migration-test.sh $(TAG)
|
| 93 |
+
|
| 94 |
+
# Run migration tests for all recent stable versions
|
| 95 |
+
migration-test-all:
|
| 96 |
+
@echo "Running migration tests for all versions..."
|
| 97 |
+
@./scripts/migration/migration-test-all.sh
|
| 98 |
+
|
| 99 |
+
# Test migration across all supported database types
|
| 100 |
+
# Usage: make migration-test-all-dbs TAG=v0.1.0
|
| 101 |
+
migration-test-all-dbs:
|
| 102 |
+
@if [ -z "$(TAG)" ]; then echo "Error: TAG is required. Usage: make migration-test-all-dbs TAG=v0.1.0"; exit 1; fi
|
| 103 |
+
@echo "Running migration tests across all DBs from $(TAG)..."
|
| 104 |
+
@./scripts/migration/test-migration-all-dbs.sh $(TAG)
|
| 105 |
+
|
| 106 |
+
# --- Data Syncing ---
|
| 107 |
+
|
| 108 |
+
# Sync FAQ from GitHub issues
|
| 109 |
+
sync-faq:
|
| 110 |
+
@echo "Syncing FAQ from GitHub..."
|
| 111 |
+
@node ./scripts/sync/sync-github-faq.js
|
| 112 |
+
|
| 113 |
+
# Sync model developers data
|
| 114 |
+
sync-models:
|
| 115 |
+
@echo "Syncing model developers..."
|
| 116 |
+
@node ./scripts/sync/sync-model-developers.js
|
| 117 |
+
|
| 118 |
+
# --- Utilities ---
|
| 119 |
+
|
| 120 |
+
# Filter and analyze load balance logs
|
| 121 |
+
filter-logs:
|
| 122 |
+
@echo "Filtering load balance logs..."
|
| 123 |
+
@./scripts/utils/filter-load-balance-logs.sh
|
cmd/axonhub/main.go
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package main
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"bytes"
|
| 5 |
+
"context"
|
| 6 |
+
"fmt"
|
| 7 |
+
"os"
|
| 8 |
+
|
| 9 |
+
"github.com/andreazorzetto/yh/highlight"
|
| 10 |
+
"github.com/hokaccha/go-prettyjson"
|
| 11 |
+
"go.uber.org/fx"
|
| 12 |
+
"go.uber.org/fx/fxevent"
|
| 13 |
+
"gopkg.in/yaml.v3"
|
| 14 |
+
|
| 15 |
+
sdk "go.opentelemetry.io/otel/sdk/metric"
|
| 16 |
+
|
| 17 |
+
"github.com/looplj/axonhub/conf"
|
| 18 |
+
"github.com/looplj/axonhub/internal/build"
|
| 19 |
+
"github.com/looplj/axonhub/internal/ent"
|
| 20 |
+
"github.com/looplj/axonhub/internal/log"
|
| 21 |
+
"github.com/looplj/axonhub/internal/metrics"
|
| 22 |
+
"github.com/looplj/axonhub/internal/server"
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
func main() {
|
| 26 |
+
if len(os.Args) > 1 {
|
| 27 |
+
switch os.Args[1] {
|
| 28 |
+
case "config":
|
| 29 |
+
handleConfigCommand()
|
| 30 |
+
return
|
| 31 |
+
case "version", "--version", "-v":
|
| 32 |
+
showVersion()
|
| 33 |
+
return
|
| 34 |
+
case "help", "--help", "-h":
|
| 35 |
+
showHelp()
|
| 36 |
+
case "build-info":
|
| 37 |
+
showBuildInfo()
|
| 38 |
+
return
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
startServer()
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
func showBuildInfo() {
|
| 46 |
+
fmt.Println(build.GetBuildInfo())
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
type logger struct{}
|
| 50 |
+
|
| 51 |
+
func (l *logger) LogEvent(event fxevent.Event) {
|
| 52 |
+
log.Debug(context.Background(), "fx event", log.Any("event", event))
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
func startServer() {
|
| 56 |
+
server.Run(
|
| 57 |
+
fx.WithLogger(func() fxevent.Logger {
|
| 58 |
+
return &logger{}
|
| 59 |
+
}),
|
| 60 |
+
fx.Provide(conf.Load),
|
| 61 |
+
fx.Provide(metrics.NewProvider),
|
| 62 |
+
fx.Invoke(func(lc fx.Lifecycle, server *server.Server, provider *sdk.MeterProvider, ent *ent.Client) {
|
| 63 |
+
lc.Append(fx.Hook{
|
| 64 |
+
OnStart: func(ctx context.Context) error {
|
| 65 |
+
if provider != nil {
|
| 66 |
+
return metrics.SetupMetrics(provider, server.Config.Name)
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
return nil
|
| 70 |
+
},
|
| 71 |
+
OnStop: func(ctx context.Context) error {
|
| 72 |
+
if provider != nil {
|
| 73 |
+
return provider.Shutdown(ctx)
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
return nil
|
| 77 |
+
},
|
| 78 |
+
})
|
| 79 |
+
lc.Append(fx.Hook{
|
| 80 |
+
OnStart: func(ctx context.Context) error {
|
| 81 |
+
go func() {
|
| 82 |
+
err := server.Run()
|
| 83 |
+
if err != nil {
|
| 84 |
+
log.Error(context.Background(), "server run error:", log.Cause(err))
|
| 85 |
+
os.Exit(1)
|
| 86 |
+
}
|
| 87 |
+
}()
|
| 88 |
+
|
| 89 |
+
return nil
|
| 90 |
+
},
|
| 91 |
+
OnStop: func(ctx context.Context) error {
|
| 92 |
+
err := server.Shutdown(ctx)
|
| 93 |
+
if err != nil {
|
| 94 |
+
log.Error(context.Background(), "server shutdown error:", log.Cause(err))
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
err = ent.Close()
|
| 98 |
+
if err != nil {
|
| 99 |
+
log.Error(context.Background(), "ent close error:", log.Cause(err))
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
return nil
|
| 103 |
+
},
|
| 104 |
+
})
|
| 105 |
+
}),
|
| 106 |
+
)
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
func handleConfigCommand() {
|
| 110 |
+
if len(os.Args) < 3 {
|
| 111 |
+
fmt.Println("Usage: axonhub config <preview|validate|get>")
|
| 112 |
+
os.Exit(1)
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
switch os.Args[2] {
|
| 116 |
+
case "preview":
|
| 117 |
+
configPreview()
|
| 118 |
+
case "validate":
|
| 119 |
+
configValidate()
|
| 120 |
+
case "get":
|
| 121 |
+
configGet()
|
| 122 |
+
default:
|
| 123 |
+
fmt.Println("Usage: axonhub config <preview|validate|get>")
|
| 124 |
+
os.Exit(1)
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
func configPreview() {
|
| 129 |
+
format := "yml"
|
| 130 |
+
|
| 131 |
+
for i := 3; i < len(os.Args); i++ {
|
| 132 |
+
if os.Args[i] == "--format" || os.Args[i] == "-f" {
|
| 133 |
+
if i+1 < len(os.Args) {
|
| 134 |
+
format = os.Args[i+1]
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
config, err := conf.Load()
|
| 140 |
+
if err != nil {
|
| 141 |
+
fmt.Printf("Failed to load config: %v\n", err)
|
| 142 |
+
os.Exit(1)
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
var output string
|
| 146 |
+
|
| 147 |
+
switch format {
|
| 148 |
+
case "json":
|
| 149 |
+
b, err := prettyjson.Marshal(config)
|
| 150 |
+
if err != nil {
|
| 151 |
+
fmt.Printf("Failed to preview config: %v\n", err)
|
| 152 |
+
os.Exit(1)
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
output = string(b)
|
| 156 |
+
case "yml", "yaml":
|
| 157 |
+
b, err := yaml.Marshal(config)
|
| 158 |
+
if err != nil {
|
| 159 |
+
fmt.Printf("Failed to preview config: %v\n", err)
|
| 160 |
+
os.Exit(1)
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
output, err = highlight.Highlight(bytes.NewBuffer(b))
|
| 164 |
+
if err != nil {
|
| 165 |
+
fmt.Printf("Failed to preview config: %v\n", err)
|
| 166 |
+
os.Exit(1)
|
| 167 |
+
}
|
| 168 |
+
default:
|
| 169 |
+
fmt.Printf("Unsupported format: %s\n", format)
|
| 170 |
+
os.Exit(1)
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
fmt.Println(output)
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
func configValidate() {
|
| 177 |
+
config, err := conf.Load()
|
| 178 |
+
if err != nil {
|
| 179 |
+
fmt.Printf("Failed to load config: %v\n", err)
|
| 180 |
+
os.Exit(1)
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
errors := validateConfig(config)
|
| 184 |
+
|
| 185 |
+
if len(errors) == 0 {
|
| 186 |
+
fmt.Println("Configuration is valid!")
|
| 187 |
+
return
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
fmt.Println("Configuration validation failed:")
|
| 191 |
+
|
| 192 |
+
for _, err := range errors {
|
| 193 |
+
fmt.Printf(" - %s\n", err)
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
os.Exit(1)
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
func validateConfig(config conf.Config) []string {
|
| 200 |
+
var errors []string
|
| 201 |
+
|
| 202 |
+
if config.APIServer.Port <= 0 || config.APIServer.Port > 65535 {
|
| 203 |
+
errors = append(errors, "server.port must be between 1 and 65535")
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
if config.DB.DSN == "" {
|
| 207 |
+
errors = append(errors, "db.dsn cannot be empty")
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
if config.Log.Name == "" {
|
| 211 |
+
errors = append(errors, "log.name cannot be empty")
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
if config.APIServer.CORS.Enabled && len(config.APIServer.CORS.AllowedOrigins) == 0 {
|
| 215 |
+
errors = append(errors, "server.cors.allowed_origins cannot be empty when CORS is enabled")
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
return errors
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
func configGet() {
|
| 222 |
+
if len(os.Args) < 4 {
|
| 223 |
+
fmt.Println("Usage: axonhub config get <key>")
|
| 224 |
+
fmt.Println("")
|
| 225 |
+
fmt.Println("Available keys:")
|
| 226 |
+
fmt.Println(" server.port Server port number")
|
| 227 |
+
fmt.Println(" server.name Server name")
|
| 228 |
+
fmt.Println(" db.dialect Database dialect")
|
| 229 |
+
fmt.Println(" db.dsn Database DSN")
|
| 230 |
+
os.Exit(1)
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
key := os.Args[3]
|
| 234 |
+
|
| 235 |
+
config, err := conf.Load()
|
| 236 |
+
if err != nil {
|
| 237 |
+
fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
|
| 238 |
+
os.Exit(1)
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
var value any
|
| 242 |
+
|
| 243 |
+
switch key {
|
| 244 |
+
case "server.port":
|
| 245 |
+
value = config.APIServer.Port
|
| 246 |
+
case "server.name":
|
| 247 |
+
value = config.APIServer.Name
|
| 248 |
+
case "server.base_path":
|
| 249 |
+
value = config.APIServer.BasePath
|
| 250 |
+
case "server.debug":
|
| 251 |
+
value = config.APIServer.Debug
|
| 252 |
+
case "db.dialect":
|
| 253 |
+
value = config.DB.Dialect
|
| 254 |
+
case "db.dsn":
|
| 255 |
+
value = config.DB.DSN
|
| 256 |
+
default:
|
| 257 |
+
fmt.Fprintf(os.Stderr, "Unknown config key: %s\n", key)
|
| 258 |
+
os.Exit(1)
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
fmt.Println(value)
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
func showHelp() {
|
| 265 |
+
fmt.Println("AxonHub AI Gateway")
|
| 266 |
+
fmt.Println("")
|
| 267 |
+
fmt.Println("Usage:")
|
| 268 |
+
fmt.Println(" axonhub Start the server (default)")
|
| 269 |
+
fmt.Println(" axonhub config preview Preview configuration")
|
| 270 |
+
fmt.Println(" axonhub config validate Validate configuration")
|
| 271 |
+
fmt.Println(" axonhub config get <key> Get a specific config value")
|
| 272 |
+
fmt.Println(" axonhub version Show version")
|
| 273 |
+
fmt.Println(" axonhub help Show this help message")
|
| 274 |
+
fmt.Println("")
|
| 275 |
+
fmt.Println("Options:")
|
| 276 |
+
fmt.Println(" -f, --format FORMAT Output format for config preview (yml, json)")
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
func showVersion() {
|
| 280 |
+
fmt.Println(build.Version)
|
| 281 |
+
}
|
conf/conf.go
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package conf
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"context"
|
| 5 |
+
"encoding"
|
| 6 |
+
"errors"
|
| 7 |
+
"fmt"
|
| 8 |
+
"reflect"
|
| 9 |
+
"strings"
|
| 10 |
+
"time"
|
| 11 |
+
|
| 12 |
+
"github.com/go-viper/mapstructure/v2"
|
| 13 |
+
"github.com/spf13/viper"
|
| 14 |
+
"go.uber.org/fx"
|
| 15 |
+
"go.uber.org/zap/zapcore"
|
| 16 |
+
|
| 17 |
+
"github.com/looplj/axonhub/internal/log"
|
| 18 |
+
"github.com/looplj/axonhub/internal/metrics"
|
| 19 |
+
"github.com/looplj/axonhub/internal/pkg/xcache"
|
| 20 |
+
"github.com/looplj/axonhub/internal/server"
|
| 21 |
+
"github.com/looplj/axonhub/internal/server/db"
|
| 22 |
+
"github.com/looplj/axonhub/internal/server/gc"
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
type Config struct {
|
| 26 |
+
fx.Out `yaml:"-" json:"-"`
|
| 27 |
+
|
| 28 |
+
DB db.Config `conf:"db" yaml:"db" json:"db"`
|
| 29 |
+
Log log.Config `conf:"log" yaml:"log" json:"log"`
|
| 30 |
+
APIServer server.Config `conf:"server" yaml:"server" json:"server"`
|
| 31 |
+
Metrics metrics.Config `conf:"metrics" yaml:"metrics" json:"metrics"`
|
| 32 |
+
GC gc.Config `conf:"gc" yaml:"gc" json:"gc"`
|
| 33 |
+
Cache xcache.Config `conf:"cache" yaml:"cache" json:"cache"`
|
| 34 |
+
ProviderQuota providerQuotaConfig `conf:"provider_quota" yaml:"provider_quota" json:"provider_quota"`
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
type providerQuotaConfig struct {
|
| 38 |
+
CheckInterval time.Duration `conf:"check_interval" yaml:"check_interval" json:"check_interval"`
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// Load loads configuration from YAML file and environment variables.
|
| 42 |
+
func Load() (Config, error) {
|
| 43 |
+
v := viper.New()
|
| 44 |
+
|
| 45 |
+
// Set config file name and paths
|
| 46 |
+
v.SetConfigName("config")
|
| 47 |
+
v.SetConfigType("yml")
|
| 48 |
+
v.AddConfigPath(".")
|
| 49 |
+
v.AddConfigPath("/etc/axonhub/")
|
| 50 |
+
v.AddConfigPath("$HOME/.config/axonhub/")
|
| 51 |
+
v.AddConfigPath("./conf")
|
| 52 |
+
|
| 53 |
+
// Enable environment variable support
|
| 54 |
+
v.AutomaticEnv()
|
| 55 |
+
v.SetEnvPrefix("AXONHUB")
|
| 56 |
+
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
| 57 |
+
|
| 58 |
+
// Set default values
|
| 59 |
+
setDefaults(v)
|
| 60 |
+
|
| 61 |
+
// Read config file
|
| 62 |
+
if err := v.ReadInConfig(); err != nil {
|
| 63 |
+
var configFileNotFoundError viper.ConfigFileNotFoundError
|
| 64 |
+
if !errors.As(err, &configFileNotFoundError) {
|
| 65 |
+
return Config{}, fmt.Errorf("failed to read config file: %w", err)
|
| 66 |
+
}
|
| 67 |
+
// Config file not found, use defaults and environment variables
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// Parse log level from string before unmarshaling
|
| 71 |
+
logLevelStr := v.GetString("log.level")
|
| 72 |
+
|
| 73 |
+
logLevel, err := parseLogLevel(logLevelStr)
|
| 74 |
+
if err != nil {
|
| 75 |
+
return Config{}, fmt.Errorf("invalid log level '%s': %w", logLevelStr, err)
|
| 76 |
+
}
|
| 77 |
+
// Set the parsed log level back to viper for unmarshaling
|
| 78 |
+
v.Set("log.level", int(logLevel))
|
| 79 |
+
|
| 80 |
+
// Unmarshal config
|
| 81 |
+
var config Config
|
| 82 |
+
if err := v.Unmarshal(&config, func(dc *mapstructure.DecoderConfig) {
|
| 83 |
+
dc.DecodeHook = customizedDecodeHook
|
| 84 |
+
dc.TagName = "conf"
|
| 85 |
+
}); err != nil {
|
| 86 |
+
return Config{}, fmt.Errorf("failed to unmarshal config: %w", err)
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
log.Debug(context.Background(), "Config loaded successfully", log.Any("config", config))
|
| 90 |
+
|
| 91 |
+
return config, nil
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
var (
|
| 95 |
+
_TypeTextUnmarshaler = reflect.TypeFor[encoding.TextUnmarshaler]()
|
| 96 |
+
_TypeDuration = reflect.TypeFor[time.Duration]()
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
func customizedDecodeHook(srcType reflect.Type, dstType reflect.Type, data any) (any, error) {
|
| 100 |
+
str, ok := data.(string)
|
| 101 |
+
if !ok {
|
| 102 |
+
return data, nil
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
switch {
|
| 106 |
+
case reflect.PointerTo(dstType).Implements(_TypeTextUnmarshaler):
|
| 107 |
+
value := reflect.New(dstType)
|
| 108 |
+
|
| 109 |
+
u, _ := value.Interface().(encoding.TextUnmarshaler)
|
| 110 |
+
if err := u.UnmarshalText([]byte(str)); err != nil {
|
| 111 |
+
return nil, err
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
return u, nil
|
| 115 |
+
case dstType == _TypeDuration:
|
| 116 |
+
if strings.TrimSpace(str) == "" {
|
| 117 |
+
return time.Duration(0), nil
|
| 118 |
+
}
|
| 119 |
+
return time.ParseDuration(str)
|
| 120 |
+
default:
|
| 121 |
+
return data, nil
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
// setDefaults sets default configuration values.
|
| 126 |
+
func setDefaults(v *viper.Viper) {
|
| 127 |
+
// Server defaults
|
| 128 |
+
v.SetDefault("server.host", "0.0.0.0")
|
| 129 |
+
v.SetDefault("server.port", 8090)
|
| 130 |
+
v.SetDefault("server.name", "AxonHub")
|
| 131 |
+
v.SetDefault("server.base_path", "")
|
| 132 |
+
v.SetDefault("server.request_timeout", "30s")
|
| 133 |
+
v.SetDefault("server.llm_request_timeout", "600s")
|
| 134 |
+
v.SetDefault("server.trace.thread_header", "AH-Thread-Id")
|
| 135 |
+
v.SetDefault("server.trace.trace_header", "AH-Trace-Id")
|
| 136 |
+
v.SetDefault("server.trace.extra_trace_headers", []string{})
|
| 137 |
+
v.SetDefault("server.trace.extra_trace_body_fields", []string{})
|
| 138 |
+
v.SetDefault("server.trace.claude_code_trace_enabled", false)
|
| 139 |
+
v.SetDefault("server.trace.codex_trace_enabled", false)
|
| 140 |
+
v.SetDefault("server.debug", false)
|
| 141 |
+
|
| 142 |
+
// CORS defaults
|
| 143 |
+
v.SetDefault("server.cors.enabled", false)
|
| 144 |
+
v.SetDefault("server.cors.debug", false)
|
| 145 |
+
v.SetDefault("server.cors.allowed_origins", []string{"http://localhost:8090"})
|
| 146 |
+
v.SetDefault("server.cors.allowed_methods", []string{"GET", "POST", "DELETE", "PATCH", "PUT", "OPTIONS", "HEAD"})
|
| 147 |
+
v.SetDefault("server.cors.allowed_headers", []string{"Content-Type", "Authorization", "X-API-Key", "X-Goog-Api-Key", "X-Project-ID", "X-Thread-ID", "X-Trace-ID"})
|
| 148 |
+
v.SetDefault("server.cors.exposed_headers", []string{})
|
| 149 |
+
v.SetDefault("server.cors.allow_credentials", false)
|
| 150 |
+
v.SetDefault("server.cors.max_age", "30m")
|
| 151 |
+
|
| 152 |
+
// Database defaults
|
| 153 |
+
v.SetDefault("db.dialect", "sqlite3")
|
| 154 |
+
v.SetDefault("db.dsn", "file:axonhub.db?cache=shared&_fk=1&journal_mode=WAL")
|
| 155 |
+
v.SetDefault("db.debug", false)
|
| 156 |
+
|
| 157 |
+
// Log defaults
|
| 158 |
+
v.SetDefault("log.name", "axonhub")
|
| 159 |
+
v.SetDefault("log.debug", false)
|
| 160 |
+
v.SetDefault("log.skip_level", 1)
|
| 161 |
+
v.SetDefault("log.level", "info")
|
| 162 |
+
v.SetDefault("log.level_key", "level")
|
| 163 |
+
v.SetDefault("log.time_key", "time")
|
| 164 |
+
v.SetDefault("log.caller_key", "label")
|
| 165 |
+
v.SetDefault("log.function_key", "")
|
| 166 |
+
v.SetDefault("log.name_key", "logger")
|
| 167 |
+
v.SetDefault("log.encoding", "json")
|
| 168 |
+
v.SetDefault("log.includes", []string{})
|
| 169 |
+
v.SetDefault("log.excludes", []string{})
|
| 170 |
+
v.SetDefault("log.output", "stdio")
|
| 171 |
+
v.SetDefault("log.file.path", "logs/axonhub.log")
|
| 172 |
+
v.SetDefault("log.file.max_size", 100) // MB
|
| 173 |
+
v.SetDefault("log.file.max_age", 30) // days
|
| 174 |
+
v.SetDefault("log.file.max_backups", 10) // files
|
| 175 |
+
v.SetDefault("log.file.local_time", true)
|
| 176 |
+
|
| 177 |
+
// Metrics defaults
|
| 178 |
+
v.SetDefault("metrics.enabled", false)
|
| 179 |
+
|
| 180 |
+
// GC defaults
|
| 181 |
+
v.SetDefault("gc.cron", "0 2 * * *") // Daily at 2:00 AM
|
| 182 |
+
|
| 183 |
+
// Provider quota defaults
|
| 184 |
+
v.SetDefault("provider_quota.check_interval", "20m") // Check every 20 minutes
|
| 185 |
+
|
| 186 |
+
// Cache defaults
|
| 187 |
+
v.SetDefault("cache.mode", "memory")
|
| 188 |
+
v.SetDefault("cache.default_expiration", "5m")
|
| 189 |
+
v.SetDefault("cache.cleanup_interval", "10m")
|
| 190 |
+
v.SetDefault("cache.redis.addr", "")
|
| 191 |
+
v.SetDefault("cache.redis.url", "")
|
| 192 |
+
v.SetDefault("cache.redis.username", "")
|
| 193 |
+
v.SetDefault("cache.redis.password", "")
|
| 194 |
+
// Note: cache.redis.db has no default value to allow explicit override to 0
|
| 195 |
+
v.SetDefault("cache.redis.tls", false)
|
| 196 |
+
v.SetDefault("cache.redis.tls_insecure_skip_verify", false)
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// parseLogLevel converts a string log level to zapcore.Level.
|
| 200 |
+
func parseLogLevel(level string) (zapcore.Level, error) {
|
| 201 |
+
switch strings.ToLower(level) {
|
| 202 |
+
case "debug":
|
| 203 |
+
return zapcore.DebugLevel, nil
|
| 204 |
+
case "info":
|
| 205 |
+
return zapcore.InfoLevel, nil
|
| 206 |
+
case "warn", "warning":
|
| 207 |
+
return zapcore.WarnLevel, nil
|
| 208 |
+
case "error":
|
| 209 |
+
return zapcore.ErrorLevel, nil
|
| 210 |
+
case "panic":
|
| 211 |
+
return zapcore.PanicLevel, nil
|
| 212 |
+
case "fatal":
|
| 213 |
+
return zapcore.FatalLevel, nil
|
| 214 |
+
default:
|
| 215 |
+
return zapcore.InfoLevel, fmt.Errorf("unknown log level: %s", level)
|
| 216 |
+
}
|
| 217 |
+
}
|
config.example.yml
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AxonHub Configuration Example
|
| 2 |
+
# Copy this file to config.yml and modify as needed
|
| 3 |
+
|
| 4 |
+
# Server configuration
|
| 5 |
+
server:
|
| 6 |
+
host: "0.0.0.0" # Server host (env: AXONHUB_SERVER_HOST)
|
| 7 |
+
port: 8090 # Server port (env: AXONHUB_SERVER_PORT)
|
| 8 |
+
name: "AxonHub" # Server name (env: AXONHUB_SERVER_NAME)
|
| 9 |
+
base_path: "" # Base path for API routes (env: AXONHUB_SERVER_BASE_PATH)
|
| 10 |
+
request_timeout: "30s" # Request timeout duration (env: AXONHUB_SERVER_REQUEST_TIMEOUT)
|
| 11 |
+
llm_request_timeout: "600s" # LLM request timeout duration (env: AXONHUB_SERVER_LLM_REQUEST_TIMEOUT)
|
| 12 |
+
trace:
|
| 13 |
+
thread_header: "AH-Thread-Id" # Thread ID header name (env: AXONHUB_SERVER_TRACE_THREAD_HEADER)
|
| 14 |
+
trace_header: "AH-Trace-Id" # Trace ID header name (env: AXONHUB_SERVER_TRACE_TRACE_HEADER)
|
| 15 |
+
extra_trace_headers: [] # Extra trace headers (env: AXONHUB_SERVER_TRACE_EXTRA_TRACE_HEADERS)
|
| 16 |
+
extra_trace_body_fields: [] # Extra trace body fields (env: AXONHUB_SERVER_TRACE_EXTRA_TRACE_BODY_FIELDS)
|
| 17 |
+
claude_code_trace_enabled: false # Enable extracting trace IDs from Claude Code request metadata (env: AXONHUB_SERVER_TRACE_CLAUDE_CODE_TRACE_ENABLED)
|
| 18 |
+
codex_trace_enabled: false # Enable extracting trace IDs from Codex Session_id header (env: AXONHUB_SERVER_TRACE_CODEX_TRACE_ENABLED)
|
| 19 |
+
debug: false # Enable debug mode (env: AXONHUB_SERVER_DEBUG)
|
| 20 |
+
cors:
|
| 21 |
+
enabled: true # Enable CORS middleware (env: AXONHUB_SERVER_CORS_ENABLED)
|
| 22 |
+
debug: false # Enable CORS debug logging (env: AXONHUB_SERVER_CORS_DEBUG)
|
| 23 |
+
allowed_origins: # Allowed origins for CORS (env: AXONHUB_SERVER_CORS_ALLOWED_ORIGINS)
|
| 24 |
+
- "http://localhost:3000"
|
| 25 |
+
- "http://localhost:5173"
|
| 26 |
+
allowed_methods: # Allowed HTTP methods (env: AXONHUB_SERVER_CORS_ALLOWED_METHODS)
|
| 27 |
+
- "GET"
|
| 28 |
+
- "POST"
|
| 29 |
+
- "DELETE"
|
| 30 |
+
- "PATCH"
|
| 31 |
+
- "PUT"
|
| 32 |
+
- "OPTIONS"
|
| 33 |
+
- "HEAD"
|
| 34 |
+
allowed_headers: # Allowed headers (env: AXONHUB_SERVER_CORS_ALLOWED_HEADERS)
|
| 35 |
+
- "Content-Type"
|
| 36 |
+
- "Authorization"
|
| 37 |
+
- "X-API-Key"
|
| 38 |
+
- "X-Goog-Api-Key"
|
| 39 |
+
- "X-Project-ID"
|
| 40 |
+
- "AH-Thread-Id"
|
| 41 |
+
- "AH-Trace-Id"
|
| 42 |
+
# Add any other headers your frontend sends
|
| 43 |
+
exposed_headers: [] # Exposed headers (env: AXONHUB_SERVER_CORS_EXPOSED_HEADERS)
|
| 44 |
+
allow_credentials: true # Allow credentials (env: AXONHUB_SERVER_CORS_ALLOW_CREDENTIALS)
|
| 45 |
+
max_age: 1m # Max age for preflight cache in seconds (env: AXONHUB_SERVER_CORS_MAX_AGE)
|
| 46 |
+
|
| 47 |
+
# Database configuration
|
| 48 |
+
db:
|
| 49 |
+
dialect: "sqlite3" # Database dialect: sqlite3, postgres, mysql (env: AXONHUB_DB_DIALECT)
|
| 50 |
+
dsn: "file:axonhub.db?cache=shared&_fk=1" # Database connection string (env: AXONHUB_DB_DSN)
|
| 51 |
+
debug: false # Enable database debug logging (env: AXONHUB_DB_DEBUG)
|
| 52 |
+
|
| 53 |
+
# Cache configuration
|
| 54 |
+
cache:
|
| 55 |
+
# One of: memory, redis, two-level (memory + redis)
|
| 56 |
+
mode: "memory" # (env: AXONHUB_CACHE_MODE)
|
| 57 |
+
|
| 58 |
+
# Memory cache configuration
|
| 59 |
+
memory:
|
| 60 |
+
expiration: "5s" # Memory cache TTL (env: AXONHUB_CACHE_MEMORY_EXPIRATION)
|
| 61 |
+
cleanup_interval: "10m" # Memory cache cleanup interval (env: AXONHUB_CACHE_MEMORY_CLEANUP_INTERVAL)
|
| 62 |
+
|
| 63 |
+
# Redis cache configuration
|
| 64 |
+
redis:
|
| 65 |
+
# Simple mode: Use addr for plain host:port
|
| 66 |
+
addr: "" # Redis address (host:port) (env: AXONHUB_CACHE_REDIS_ADDR)
|
| 67 |
+
|
| 68 |
+
# URL mode: Use url for full redis:// or rediss:// URLs (takes priority over addr)
|
| 69 |
+
# Supported formats:
|
| 70 |
+
# - "redis://127.0.0.1:6379"
|
| 71 |
+
# - "rediss://127.0.0.1:6380" (auto-enables TLS)
|
| 72 |
+
# - "redis://user:pass@127.0.0.1:6379/0"
|
| 73 |
+
url: "" # Redis URL (env: AXONHUB_CACHE_REDIS_URL)
|
| 74 |
+
|
| 75 |
+
# Optional overrides (apply to both addr and url modes)
|
| 76 |
+
username: "" # Overrides URL username (env: AXONHUB_CACHE_REDIS_USERNAME)
|
| 77 |
+
password: "" # Overrides URL password (env: AXONHUB_CACHE_REDIS_PASSWORD)
|
| 78 |
+
db: 0 # Overrides URL /db (env: AXONHUB_CACHE_REDIS_DB)
|
| 79 |
+
tls: false # Enable TLS for addr mode (env: AXONHUB_CACHE_REDIS_TLS)
|
| 80 |
+
tls_insecure_skip_verify: false # Skip TLS cert verification (env: AXONHUB_CACHE_REDIS_TLS_INSECURE_SKIP_VERIFY)
|
| 81 |
+
expiration: "30m" # Redis cache TTL (env: AXONHUB_CACHE_REDIS_EXPIRATION)
|
| 82 |
+
|
| 83 |
+
# Logging configuration
|
| 84 |
+
log:
|
| 85 |
+
name: "axonhub" # Logger name (env: AXONHUB_LOG_NAME)
|
| 86 |
+
debug: false # Enable debug logging (env: AXONHUB_LOG_DEBUG)
|
| 87 |
+
level: "info" # Log level: debug, info, warn, error, panic, fatal (env: AXONHUB_LOG_LEVEL)
|
| 88 |
+
level_key: "level" # Key name for log level field (env: AXONHUB_LOG_LEVEL_KEY)
|
| 89 |
+
time_key: "time" # Key name for timestamp field (env: AXONHUB_LOG_TIME_KEY)
|
| 90 |
+
caller_key: "label" # Key name for caller info field (env: AXONHUB_LOG_CALLER_KEY)
|
| 91 |
+
function_key: "" # Key name for function field (env: AXONHUB_LOG_FUNCTION_KEY)
|
| 92 |
+
name_key: "logger" # Key name for logger name field (env: AXONHUB_LOG_NAME_KEY)
|
| 93 |
+
encoding: "json" # Log encoding: json, console, console_json (env: AXONHUB_LOG_ENCODING)
|
| 94 |
+
includes: [] # Logger names to include (env: AXONHUB_LOG_INCLUDES)
|
| 95 |
+
excludes: [] # Logger names to exclude (env: AXONHUB_LOG_EXCLUDES)
|
| 96 |
+
output: "stdio" # Output target: file or stdio (env: AXONHUB_LOG_OUTPUT)
|
| 97 |
+
file: # File-based logging configuration (env prefix: AXONHUB_LOG_FILE_*)
|
| 98 |
+
path: "logs/axonhub.log" # Log file path (env: AXONHUB_LOG_FILE_PATH)
|
| 99 |
+
max_size: 100 # Max size in MB before rotation (env: AXONHUB_LOG_FILE_MAX_SIZE)
|
| 100 |
+
max_age: 30 # Max age in days to retain (env: AXONHUB_LOG_FILE_MAX_AGE)
|
| 101 |
+
max_backups: 10 # Max number of old log files to retain (env: AXONHUB_LOG_FILE_MAX_BACKUPS)
|
| 102 |
+
local_time: true # Use local time for rotated files (env: AXONHUB_LOG_FILE_LOCAL_TIME)
|
| 103 |
+
|
| 104 |
+
# Metrics configuration
|
| 105 |
+
metrics:
|
| 106 |
+
enabled: false # Enable metrics collection (env: AXONHUB_METRICS_ENABLED)
|
| 107 |
+
exporter:
|
| 108 |
+
type: "oltphttp" # Metrics exporter type: prometheus, console (env: AXONHUB_METRICS_EXPORTER_TYPE)
|
| 109 |
+
endpoint: "localhost:8080" # Metrics exporter endpoint (env: AXONHUB_METRICS_EXPORTER_ENDPOINT)
|
| 110 |
+
insecure: true # Enable insecure connection (env: AXONHUB_METRICS_EXPORTER_INSECURE)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# Garbage Collection configuration
|
| 114 |
+
gc:
|
| 115 |
+
cron: "0 2 * * *" # Cron expression for GC execution (env: AXONHUB_GC_CRON)
|
| 116 |
+
# This example runs daily at 2:00 AM
|
| 117 |
+
# Format: minute hour day month day_of_week
|
| 118 |
+
# Examples:
|
| 119 |
+
# "0 2 * * *" - Daily at 2:00 AM
|
| 120 |
+
# "0 3 * * 0" - Weekly on Sunday at 3:00 AM
|
| 121 |
+
# "0 4 1 * *" - Monthly on 1st day at 4:00 AM
|
| 122 |
+
|
| 123 |
+
# Provider Quota configuration
|
| 124 |
+
provider_quota:
|
| 125 |
+
check_interval: "20m" # Interval for checking provider quota status (env: AXONHUB_PROVIDER_QUOTA_CHECK_INTERVAL)
|
| 126 |
+
# Supported values: 1m, 2m, 3m, 4m, 5m, 6m, 10m, 12m, 15m, 20m, 30m, 1h, 2h, etc.
|
| 127 |
+
# Default: 20m
|
| 128 |
+
# Longer intervals reduce API calls but show less fresh quota data
|
| 129 |
+
|
| 130 |
+
# Environment Variable Examples:
|
| 131 |
+
# export AXONHUB_SERVER_PORT=8080
|
| 132 |
+
# export AXONHUB_DB_DSN="postgres://user:pass@localhost/axonhub?sslmode=disable"
|
| 133 |
+
# export AXONHUB_LOG_LEVEL="debug"
|
| 134 |
+
# export AXONHUB_LOG_ENCODING="console"
|
| 135 |
+
# export AXONHUB_LOG_OUTPUT="stdio" # or "file"
|
| 136 |
+
# export AXONHUB_LOG_FILE_PATH="/var/log/axonhub/axonhub.log"
|
| 137 |
+
# export AXONHUB_LOG_FILE_MAX_SIZE=200
|
| 138 |
+
# export AXONHUB_LOG_FILE_MAX_AGE=14
|
| 139 |
+
# export AXONHUB_LOG_FILE_MAX_BACKUPS=7
|
| 140 |
+
# export AXONHUB_SERVER_TRACE_HEADER="X-Trace-ID"
|
| 141 |
+
# export AXONHUB_METRICS_ENABLED=false
|
| 142 |
+
# export AXONHUB_GC_CRON="0 3 * * 0" # Run weekly on Sunday at 3:00 AM
|
deploy/axonhub.service
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[Unit]
|
| 2 |
+
Description=AxonHub AI Gateway
|
| 3 |
+
After=network.target
|
| 4 |
+
|
| 5 |
+
[Service]
|
| 6 |
+
Type=simple
|
| 7 |
+
User=axonhub
|
| 8 |
+
WorkingDirectory=/opt/axonhub
|
| 9 |
+
ExecStart=/opt/axonhub/axonhub
|
| 10 |
+
Restart=always
|
| 11 |
+
RestartSec=5
|
| 12 |
+
|
| 13 |
+
[Install]
|
| 14 |
+
WantedBy=multi-user.target
|
deploy/helm/.helmignore
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Patterns to ignore when building packages.
|
| 2 |
+
# This supports shell glob matching, relative path matching, and
|
| 3 |
+
# negation (prefixed with !). Only one pattern per line.
|
| 4 |
+
.DS_Store
|
| 5 |
+
# Common VCS dirs
|
| 6 |
+
.git/
|
| 7 |
+
.gitignore
|
| 8 |
+
.bzr/
|
| 9 |
+
.bzrignore
|
| 10 |
+
.hg/
|
| 11 |
+
.hgignore
|
| 12 |
+
.svn/
|
| 13 |
+
# Common backup files
|
| 14 |
+
*.swp
|
| 15 |
+
*.bak
|
| 16 |
+
*.tmp
|
| 17 |
+
*.orig
|
| 18 |
+
*~
|
| 19 |
+
# Various IDEs
|
| 20 |
+
.project
|
| 21 |
+
.idea/
|
| 22 |
+
*.tmproj
|
| 23 |
+
.vscode/
|
deploy/helm/Chart.yaml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
apiVersion: v2
|
| 2 |
+
name: axonhub
|
| 3 |
+
version: 0.1.0
|
| 4 |
+
appVersion: "latest"
|
| 5 |
+
description: A Helm chart for deploying AxonHub on Kubernetes
|
| 6 |
+
type: application
|
| 7 |
+
home: https://github.com/looplj/axonhub
|
| 8 |
+
sources:
|
| 9 |
+
- https://github.com/looplj/axonhub
|
| 10 |
+
maintainers:
|
| 11 |
+
- name: AxonHub Team
|
| 12 |
+
email: team@axonhub.ai
|
deploy/helm/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AxonHub Helm Chart
|
| 2 |
+
|
| 3 |
+
This Helm chart deploys AxonHub on Kubernetes with PostgreSQL database.
|
| 4 |
+
|
| 5 |
+
## Prerequisites
|
| 6 |
+
|
| 7 |
+
- Kubernetes 1.19+
|
| 8 |
+
- Helm 3.0+
|
| 9 |
+
- PV provisioner support in the underlying infrastructure (for persistence)
|
| 10 |
+
|
| 11 |
+
## Installing the Chart
|
| 12 |
+
|
| 13 |
+
To install the chart with the release name `axonhub`:
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
helm install axonhub ./deploy/helm
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
## Configuration
|
| 20 |
+
|
| 21 |
+
The following table lists the configurable parameters of the AxonHub chart and their default values.
|
| 22 |
+
|
| 23 |
+
### Global Parameters
|
| 24 |
+
|
| 25 |
+
| Parameter | Description | Default |
|
| 26 |
+
|-----------|-------------|---------|
|
| 27 |
+
| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` |
|
| 28 |
+
|
| 29 |
+
### AxonHub Parameters
|
| 30 |
+
|
| 31 |
+
| Parameter | Description | Default |
|
| 32 |
+
|-----------|-------------|---------|
|
| 33 |
+
| `axonhub.replicaCount` | Number of AxonHub replicas | `1` |
|
| 34 |
+
| `axonhub.image.repository` | AxonHub image repository | `looplj/axonhub` |
|
| 35 |
+
| `axonhub.image.tag` | AxonHub image tag | `latest` |
|
| 36 |
+
| `axonhub.image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
| 37 |
+
| `axonhub.dbPassword` | Database password | `axonhub_password` |
|
| 38 |
+
| `axonhub.service.type` | Kubernetes service type | `ClusterIP` |
|
| 39 |
+
| `axonhub.service.port` | Service port | `8090` |
|
| 40 |
+
| `axonhub.resources` | CPU/Memory resource requests/limits | `{}` |
|
| 41 |
+
| `axonhub.persistence.enabled` | Enable persistence using PVC | `false` |
|
| 42 |
+
| `axonhub.persistence.size` | PVC storage request size | `10Gi` |
|
| 43 |
+
|
| 44 |
+
### PostgreSQL Parameters
|
| 45 |
+
|
| 46 |
+
| Parameter | Description | Default |
|
| 47 |
+
|-----------|-------------|---------|
|
| 48 |
+
| `postgresql.enabled` | Deploy PostgreSQL chart | `true` |
|
| 49 |
+
| `postgresql.replicaCount` | Number of PostgreSQL replicas | `1` |
|
| 50 |
+
| `postgresql.image.repository` | PostgreSQL image repository | `postgres` |
|
| 51 |
+
| `postgresql.image.tag` | PostgreSQL image tag | `16-alpine` |
|
| 52 |
+
| `postgresql.auth.postgresPassword` | PostgreSQL admin password | `axonhub_password` |
|
| 53 |
+
| `postgresql.auth.username` | PostgreSQL user name | `axonhub` |
|
| 54 |
+
| `postgresql.auth.password` | PostgreSQL user password | `axonhub_password` |
|
| 55 |
+
| `postgresql.auth.database` | PostgreSQL database name | `axonhub` |
|
| 56 |
+
| `postgresql.service.type` | Kubernetes service type | `ClusterIP` |
|
| 57 |
+
| `postgresql.service.port` | PostgreSQL service port | `5432` |
|
| 58 |
+
| `postgresql.primary.persistence.enabled` | Enable PostgreSQL persistence | `true` |
|
| 59 |
+
| `postgresql.primary.persistence.size` | PVC storage request size | `8Gi` |
|
| 60 |
+
|
| 61 |
+
### Ingress Parameters
|
| 62 |
+
|
| 63 |
+
| Parameter | Description | Default |
|
| 64 |
+
|-----------|-------------|---------|
|
| 65 |
+
| `ingress.enabled` | Enable ingress controller resource | `false` |
|
| 66 |
+
| `ingress.className` | IngressClass that will be used | `""` |
|
| 67 |
+
| `ingress.annotations` | Ingress annotations | `{}` |
|
| 68 |
+
| `ingress.hosts` | Ingress hostnames | `[{host: axonhub.local, paths: [{path: /, pathType: Prefix}]}]` |
|
| 69 |
+
| `ingress.tls` | Ingress TLS configuration | `[]` |
|
| 70 |
+
|
| 71 |
+
## Database Configuration Options
|
| 72 |
+
|
| 73 |
+
### Using Internal PostgreSQL (Default)
|
| 74 |
+
|
| 75 |
+
The chart includes PostgreSQL by default. This is suitable for:
|
| 76 |
+
- Development environments
|
| 77 |
+
- Small production deployments
|
| 78 |
+
- When you want managed database within Kubernetes
|
| 79 |
+
|
| 80 |
+
```yaml
|
| 81 |
+
postgresql:
|
| 82 |
+
enabled: true # Default setting
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### Using External Database
|
| 86 |
+
|
| 87 |
+
Disable internal PostgreSQL and configure external database connection:
|
| 88 |
+
|
| 89 |
+
```yaml
|
| 90 |
+
postgresql:
|
| 91 |
+
enabled: false
|
| 92 |
+
|
| 93 |
+
axonhub:
|
| 94 |
+
env:
|
| 95 |
+
AXONHUB_DB_DSN: "postgres://username:password@external-db-host:5432/database?sslmode=require"
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
## Production Deployment
|
| 99 |
+
|
| 100 |
+
For production deployments, you should:
|
| 101 |
+
|
| 102 |
+
1. Change default passwords:
|
| 103 |
+
```yaml
|
| 104 |
+
axonhub:
|
| 105 |
+
dbPassword: "your-secure-password"
|
| 106 |
+
|
| 107 |
+
postgresql:
|
| 108 |
+
auth:
|
| 109 |
+
postgresPassword: "your-secure-postgres-password"
|
| 110 |
+
password: "your-secure-password"
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
2. Enable persistence:
|
| 114 |
+
```yaml
|
| 115 |
+
axonhub:
|
| 116 |
+
persistence:
|
| 117 |
+
enabled: true
|
| 118 |
+
size: 20Gi
|
| 119 |
+
|
| 120 |
+
postgresql:
|
| 121 |
+
primary:
|
| 122 |
+
persistence:
|
| 123 |
+
enabled: true
|
| 124 |
+
size: 20Gi
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
3. Configure resource limits:
|
| 128 |
+
```yaml
|
| 129 |
+
axonhub:
|
| 130 |
+
resources:
|
| 131 |
+
limits:
|
| 132 |
+
cpu: 2000m
|
| 133 |
+
memory: 2Gi
|
| 134 |
+
requests:
|
| 135 |
+
cpu: 1000m
|
| 136 |
+
memory: 1Gi
|
| 137 |
+
|
| 138 |
+
postgresql:
|
| 139 |
+
resources:
|
| 140 |
+
limits:
|
| 141 |
+
cpu: 1000m
|
| 142 |
+
memory: 1Gi
|
| 143 |
+
requests:
|
| 144 |
+
cpu: 500m
|
| 145 |
+
memory: 512Mi
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
4. Enable ingress for external access:
|
| 149 |
+
```yaml
|
| 150 |
+
ingress:
|
| 151 |
+
enabled: true
|
| 152 |
+
className: "nginx"
|
| 153 |
+
hosts:
|
| 154 |
+
- host: axonhub.yourdomain.com
|
| 155 |
+
paths:
|
| 156 |
+
- path: /
|
| 157 |
+
pathType: Prefix
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
## Upgrading
|
| 161 |
+
|
| 162 |
+
To upgrade the chart:
|
| 163 |
+
|
| 164 |
+
```bash
|
| 165 |
+
helm upgrade axonhub ./deploy/helm -f values-production.yaml
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
## Uninstalling
|
| 169 |
+
|
| 170 |
+
To uninstall/delete the release:
|
| 171 |
+
|
| 172 |
+
```bash
|
| 173 |
+
helm uninstall axonhub
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
## Verification
|
| 177 |
+
|
| 178 |
+
After installation, you can verify the deployment:
|
| 179 |
+
|
| 180 |
+
```bash
|
| 181 |
+
# Check pods status
|
| 182 |
+
kubectl get pods
|
| 183 |
+
|
| 184 |
+
# Check services
|
| 185 |
+
kubectl get svc
|
| 186 |
+
|
| 187 |
+
# Port forward to test
|
| 188 |
+
kubectl port-forward svc/axonhub 8090:8090
|
| 189 |
+
|
| 190 |
+
# Test health endpoint
|
| 191 |
+
curl http://localhost:8090/health
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
## Troubleshooting
|
| 195 |
+
|
| 196 |
+
Common issues and solutions:
|
| 197 |
+
|
| 198 |
+
1. **Database connection failed**: Check PostgreSQL pod status and logs
|
| 199 |
+
2. **Insufficient resources**: Adjust resource requests/limits in values.yaml
|
| 200 |
+
3. **Persistent volume issues**: Ensure your cluster has PV provisioner configured
|
| 201 |
+
4. **Ingress not working**: Verify ingress controller is installed and configured
|
| 202 |
+
|
| 203 |
+
## Architecture
|
| 204 |
+
|
| 205 |
+
The chart deploys:
|
| 206 |
+
- AxonHub application as a Deployment
|
| 207 |
+
- PostgreSQL database as a StatefulSet
|
| 208 |
+
- Services for both components
|
| 209 |
+
- Optional ingress for external access
|
| 210 |
+
- Persistent volumes for data persistence
|
deploy/helm/install.bat
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
|
| 4 |
+
set RELEASE_NAME=%1
|
| 5 |
+
if "%RELEASE_NAME%"=="" set RELEASE_NAME=axonhub
|
| 6 |
+
|
| 7 |
+
set NAMESPACE=%2
|
| 8 |
+
if "%NAMESPACE%"=="" set NAMESPACE=default
|
| 9 |
+
|
| 10 |
+
echo Installing AxonHub with Helm...
|
| 11 |
+
echo Release name: %RELEASE_NAME%
|
| 12 |
+
echo Namespace: %NAMESPACE%
|
| 13 |
+
|
| 14 |
+
REM Create namespace if it doesn't exist
|
| 15 |
+
kubectl create namespace %NAMESPACE% 2>nul || echo Namespace already exists
|
| 16 |
+
|
| 17 |
+
REM Install the chart
|
| 18 |
+
helm install %RELEASE_NAME% ./deploy/helm ^
|
| 19 |
+
--namespace %NAMESPACE% ^
|
| 20 |
+
--timeout 10m0s
|
| 21 |
+
|
| 22 |
+
echo.
|
| 23 |
+
echo Installation completed!
|
| 24 |
+
echo.
|
| 25 |
+
echo To access AxonHub:
|
| 26 |
+
echo 1. Port forward the service:
|
| 27 |
+
echo kubectl port-forward svc/%RELEASE_NAME% 8090:8090 -n %NAMESPACE%
|
| 28 |
+
echo.
|
| 29 |
+
echo 2. Visit http://localhost:8090 in your browser
|
| 30 |
+
echo.
|
| 31 |
+
echo To check the status:
|
| 32 |
+
echo kubectl get pods -n %NAMESPACE%
|
| 33 |
+
echo.
|
| 34 |
+
echo To view logs:
|
| 35 |
+
echo kubectl logs -l app.kubernetes.io/name=axonhub -n %NAMESPACE%
|
| 36 |
+
echo.
|
| 37 |
+
echo To uninstall:
|
| 38 |
+
echo helm uninstall %RELEASE_NAME% -n %NAMESPACE%
|
deploy/helm/install.sh
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# AxonHub Helm Installation Script
|
| 4 |
+
|
| 5 |
+
set -e
|
| 6 |
+
|
| 7 |
+
RELEASE_NAME=${1:-axonhub}
|
| 8 |
+
NAMESPACE=${2:-default}
|
| 9 |
+
|
| 10 |
+
echo "Installing AxonHub with Helm..."
|
| 11 |
+
echo "Release name: $RELEASE_NAME"
|
| 12 |
+
echo "Namespace: $NAMESPACE"
|
| 13 |
+
|
| 14 |
+
# Create namespace if it doesn't exist
|
| 15 |
+
kubectl create namespace $NAMESPACE 2>/dev/null || true
|
| 16 |
+
|
| 17 |
+
# Install the chart
|
| 18 |
+
helm install $RELEASE_NAME ./deploy/helm \
|
| 19 |
+
--namespace $NAMESPACE \
|
| 20 |
+
--timeout 10m0s
|
| 21 |
+
|
| 22 |
+
echo ""
|
| 23 |
+
echo "Installation completed!"
|
| 24 |
+
echo ""
|
| 25 |
+
echo "To access AxonHub:"
|
| 26 |
+
echo "1. Port forward the service:"
|
| 27 |
+
echo " kubectl port-forward svc/$RELEASE_NAME 8090:8090 -n $NAMESPACE"
|
| 28 |
+
echo ""
|
| 29 |
+
echo "2. Visit http://localhost:8090 in your browser"
|
| 30 |
+
echo ""
|
| 31 |
+
echo "To check the status:"
|
| 32 |
+
echo " kubectl get pods -n $NAMESPACE"
|
| 33 |
+
echo ""
|
| 34 |
+
echo "To view logs:"
|
| 35 |
+
echo " kubectl logs -l app.kubernetes.io/name=axonhub -n $NAMESPACE"
|
| 36 |
+
echo ""
|
| 37 |
+
echo "To uninstall:"
|
| 38 |
+
echo " helm uninstall $RELEASE_NAME -n $NAMESPACE"
|
deploy/helm/templates/NOTES.txt
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Thank you for installing AxonHub!
|
| 2 |
+
|
| 3 |
+
1. Get the application URL by running these commands:
|
| 4 |
+
{{- if .Values.ingress.enabled }}
|
| 5 |
+
{{- range $host := .Values.ingress.hosts }}
|
| 6 |
+
{{- range .paths }}
|
| 7 |
+
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
| 8 |
+
{{- end }}
|
| 9 |
+
{{- end }}
|
| 10 |
+
{{- else if contains "NodePort" .Values.axonhub.service.type }}
|
| 11 |
+
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "axonhub.fullname" . }})
|
| 12 |
+
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
| 13 |
+
echo http://$NODE_IP:$NODE_PORT
|
| 14 |
+
{{- else if contains "LoadBalancer" .Values.axonhub.service.type }}
|
| 15 |
+
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
| 16 |
+
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "axonhub.fullname" . }}'
|
| 17 |
+
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "axonhub.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
| 18 |
+
echo http://$SERVICE_IP:{{ .Values.axonhub.service.port }}
|
| 19 |
+
{{- else if contains "ClusterIP" .Values.axonhub.service.type }}
|
| 20 |
+
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "axonhub.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
| 21 |
+
echo "Visit http://127.0.0.1:8090 to use your application"
|
| 22 |
+
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8090:8090
|
| 23 |
+
{{- end }}
|
| 24 |
+
|
| 25 |
+
2. Check the status of your deployment:
|
| 26 |
+
kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "axonhub.name" . }}"
|
| 27 |
+
|
| 28 |
+
3. View logs:
|
| 29 |
+
kubectl logs --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "axonhub.name" . }}" -c axonhub
|
| 30 |
+
|
| 31 |
+
4. Access the application health endpoint:
|
| 32 |
+
kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "axonhub.fullname" . }} 8090:8090
|
| 33 |
+
# Then visit http://localhost:8090/health
|
| 34 |
+
|
| 35 |
+
{{- if .Values.postgresql.enabled }}
|
| 36 |
+
5. PostgreSQL database is available at:
|
| 37 |
+
Host: {{ include "axonhub.postgresql.serviceName" . }}.{{ .Release.Namespace }}.svc.cluster.local
|
| 38 |
+
Port: {{ .Values.postgresql.service.port }}
|
| 39 |
+
Database: {{ .Values.postgresql.auth.database }}
|
| 40 |
+
Username: {{ .Values.postgresql.auth.username }}
|
| 41 |
+
{{- end }}
|
| 42 |
+
|
| 43 |
+
WARNING: Default passwords are used. Please change them in production environments!
|
deploy/helm/templates/_helpers.tpl
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{/*
|
| 2 |
+
Expand the name of the chart.
|
| 3 |
+
*/}}
|
| 4 |
+
{{- define "axonhub.name" -}}
|
| 5 |
+
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
| 6 |
+
{{- end }}
|
| 7 |
+
|
| 8 |
+
{{/*
|
| 9 |
+
Create a default fully qualified app name.
|
| 10 |
+
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
| 11 |
+
If release name contains chart name it will be used as a full name.
|
| 12 |
+
*/}}
|
| 13 |
+
{{- define "axonhub.fullname" -}}
|
| 14 |
+
{{- if .Values.fullnameOverride }}
|
| 15 |
+
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
| 16 |
+
{{- else }}
|
| 17 |
+
{{- $name := default .Chart.Name .Values.nameOverride }}
|
| 18 |
+
{{- if contains $name .Release.Name }}
|
| 19 |
+
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
| 20 |
+
{{- else }}
|
| 21 |
+
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
| 22 |
+
{{- end }}
|
| 23 |
+
{{- end }}
|
| 24 |
+
{{- end }}
|
| 25 |
+
|
| 26 |
+
{{/*
|
| 27 |
+
Create chart name and version as used by the chart label.
|
| 28 |
+
*/}}
|
| 29 |
+
{{- define "axonhub.chart" -}}
|
| 30 |
+
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
| 31 |
+
{{- end }}
|
| 32 |
+
|
| 33 |
+
{{/*
|
| 34 |
+
Common labels
|
| 35 |
+
*/}}
|
| 36 |
+
{{- define "axonhub.labels" -}}
|
| 37 |
+
helm.sh/chart: {{ include "axonhub.chart" . }}
|
| 38 |
+
{{ include "axonhub.selectorLabels" . }}
|
| 39 |
+
{{- if .Chart.AppVersion }}
|
| 40 |
+
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
| 41 |
+
{{- end }}
|
| 42 |
+
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
| 43 |
+
{{- end }}
|
| 44 |
+
|
| 45 |
+
{{/*
|
| 46 |
+
Selector labels
|
| 47 |
+
*/}}
|
| 48 |
+
{{- define "axonhub.selectorLabels" -}}
|
| 49 |
+
app.kubernetes.io/name: {{ include "axonhub.name" . }}
|
| 50 |
+
app.kubernetes.io/instance: {{ .Release.Name }}
|
| 51 |
+
{{- end }}
|
| 52 |
+
|
| 53 |
+
{{/*
|
| 54 |
+
Create the name of the service account to use
|
| 55 |
+
*/}}
|
| 56 |
+
{{- define "axonhub.serviceAccountName" -}}
|
| 57 |
+
{{- if .Values.serviceAccount.create }}
|
| 58 |
+
{{- default (include "axonhub.fullname" .) .Values.serviceAccount.name }}
|
| 59 |
+
{{- else }}
|
| 60 |
+
{{- default "default" .Values.serviceAccount.name }}
|
| 61 |
+
{{- end }}
|
| 62 |
+
{{- end }}
|
| 63 |
+
|
| 64 |
+
{{/*
|
| 65 |
+
PostgreSQL fullname
|
| 66 |
+
*/}}
|
| 67 |
+
{{- define "axonhub.postgresql.fullname" -}}
|
| 68 |
+
{{- printf "%s-postgresql" (include "axonhub.fullname" .) | trunc 63 | trimSuffix "-" }}
|
| 69 |
+
{{- end }}
|
| 70 |
+
|
| 71 |
+
{{/*
|
| 72 |
+
PostgreSQL service name
|
| 73 |
+
*/}}
|
| 74 |
+
{{- define "axonhub.postgresql.serviceName" -}}
|
| 75 |
+
{{- include "axonhub.postgresql.fullname" . }}
|
| 76 |
+
{{- end }}
|
deploy/helm/templates/data-pvc.yaml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{- if .Values.axonhub.persistence.enabled }}
|
| 2 |
+
apiVersion: v1
|
| 3 |
+
kind: PersistentVolumeClaim
|
| 4 |
+
metadata:
|
| 5 |
+
name: {{ include "axonhub.fullname" . }}-data
|
| 6 |
+
labels:
|
| 7 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 8 |
+
app.kubernetes.io/component: axonhub
|
| 9 |
+
spec:
|
| 10 |
+
accessModes:
|
| 11 |
+
{{- toYaml .Values.axonhub.persistence.accessModes | nindent 4 }}
|
| 12 |
+
resources:
|
| 13 |
+
requests:
|
| 14 |
+
storage: {{ .Values.axonhub.persistence.size }}
|
| 15 |
+
{{- if .Values.axonhub.persistence.storageClass }}
|
| 16 |
+
storageClassName: {{ .Values.axonhub.persistence.storageClass }}
|
| 17 |
+
{{- end }}
|
| 18 |
+
{{- end }}
|
deploy/helm/templates/deployment.yaml
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
apiVersion: apps/v1
|
| 2 |
+
kind: Deployment
|
| 3 |
+
metadata:
|
| 4 |
+
name: {{ include "axonhub.fullname" . }}
|
| 5 |
+
labels:
|
| 6 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 7 |
+
app.kubernetes.io/component: axonhub
|
| 8 |
+
spec:
|
| 9 |
+
{{- if not .Values.autoscaling.enabled }}
|
| 10 |
+
replicas: {{ .Values.axonhub.replicaCount }}
|
| 11 |
+
{{- end }}
|
| 12 |
+
selector:
|
| 13 |
+
matchLabels:
|
| 14 |
+
{{- include "axonhub.selectorLabels" . | nindent 6 }}
|
| 15 |
+
app.kubernetes.io/component: axonhub
|
| 16 |
+
template:
|
| 17 |
+
metadata:
|
| 18 |
+
{{- with .Values.axonhub.podAnnotations }}
|
| 19 |
+
annotations:
|
| 20 |
+
{{- toYaml . | nindent 8 }}
|
| 21 |
+
{{- end }}
|
| 22 |
+
labels:
|
| 23 |
+
{{- include "axonhub.selectorLabels" . | nindent 8 }}
|
| 24 |
+
app.kubernetes.io/component: axonhub
|
| 25 |
+
spec:
|
| 26 |
+
{{- with .Values.global.imagePullSecrets }}
|
| 27 |
+
imagePullSecrets:
|
| 28 |
+
{{- toYaml . | nindent 8 }}
|
| 29 |
+
{{- end }}
|
| 30 |
+
serviceAccountName: {{ include "axonhub.serviceAccountName" . }}
|
| 31 |
+
securityContext:
|
| 32 |
+
{{- toYaml .Values.axonhub.podSecurityContext | nindent 8 }}
|
| 33 |
+
{{- if .Values.postgresql.enabled }}
|
| 34 |
+
initContainers:
|
| 35 |
+
- name: wait-for-postgres
|
| 36 |
+
image: busybox:1.36
|
| 37 |
+
command: ['sh', '-c', 'until nc -z {{ include "axonhub.postgresql.serviceName" . }} 5432; do echo waiting for postgresql; sleep 2; done;']
|
| 38 |
+
{{- end }}
|
| 39 |
+
containers:
|
| 40 |
+
- name: {{ .Chart.Name }}
|
| 41 |
+
securityContext:
|
| 42 |
+
{{- toYaml .Values.axonhub.securityContext | nindent 12 }}
|
| 43 |
+
image: "{{ .Values.axonhub.image.repository }}:{{ .Values.axonhub.image.tag | default .Chart.AppVersion }}"
|
| 44 |
+
imagePullPolicy: {{ .Values.axonhub.image.pullPolicy }}
|
| 45 |
+
env:
|
| 46 |
+
{{- range $key, $value := .Values.axonhub.env }}
|
| 47 |
+
- name: {{ $key }}
|
| 48 |
+
value: {{ $value | quote }}
|
| 49 |
+
{{- end }}
|
| 50 |
+
ports:
|
| 51 |
+
- name: http
|
| 52 |
+
containerPort: {{ .Values.axonhub.service.port }}
|
| 53 |
+
protocol: TCP
|
| 54 |
+
livenessProbe:
|
| 55 |
+
{{- toYaml .Values.axonhub.livenessProbe | nindent 12 }}
|
| 56 |
+
readinessProbe:
|
| 57 |
+
{{- toYaml .Values.axonhub.readinessProbe | nindent 12 }}
|
| 58 |
+
resources:
|
| 59 |
+
{{- toYaml .Values.axonhub.resources | nindent 12 }}
|
| 60 |
+
{{- if .Values.axonhub.persistence.enabled }}
|
| 61 |
+
volumeMounts:
|
| 62 |
+
- name: data
|
| 63 |
+
mountPath: /data
|
| 64 |
+
{{- end }}
|
| 65 |
+
{{- with .Values.axonhub.nodeSelector }}
|
| 66 |
+
nodeSelector:
|
| 67 |
+
{{- toYaml . | nindent 8 }}
|
| 68 |
+
{{- end }}
|
| 69 |
+
{{- with .Values.axonhub.affinity }}
|
| 70 |
+
affinity:
|
| 71 |
+
{{- toYaml . | nindent 8 }}
|
| 72 |
+
{{- end }}
|
| 73 |
+
{{- with .Values.axonhub.tolerations }}
|
| 74 |
+
tolerations:
|
| 75 |
+
{{- toYaml . | nindent 8 }}
|
| 76 |
+
{{- end }}
|
| 77 |
+
{{- if .Values.axonhub.persistence.enabled }}
|
| 78 |
+
volumes:
|
| 79 |
+
- name: data
|
| 80 |
+
persistentVolumeClaim:
|
| 81 |
+
claimName: {{ include "axonhub.fullname" . }}-data
|
| 82 |
+
{{- end }}
|
deploy/helm/templates/hpa.yaml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{- if .Values.autoscaling.enabled }}
|
| 2 |
+
apiVersion: autoscaling/v2
|
| 3 |
+
kind: HorizontalPodAutoscaler
|
| 4 |
+
metadata:
|
| 5 |
+
name: {{ include "axonhub.fullname" . }}
|
| 6 |
+
labels:
|
| 7 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 8 |
+
app.kubernetes.io/component: axonhub
|
| 9 |
+
spec:
|
| 10 |
+
scaleTargetRef:
|
| 11 |
+
apiVersion: apps/v1
|
| 12 |
+
kind: Deployment
|
| 13 |
+
name: {{ include "axonhub.fullname" . }}
|
| 14 |
+
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
| 15 |
+
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
| 16 |
+
metrics:
|
| 17 |
+
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
| 18 |
+
- type: Resource
|
| 19 |
+
resource:
|
| 20 |
+
name: cpu
|
| 21 |
+
target:
|
| 22 |
+
type: Utilization
|
| 23 |
+
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
| 24 |
+
{{- end }}
|
| 25 |
+
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
| 26 |
+
- type: Resource
|
| 27 |
+
resource:
|
| 28 |
+
name: memory
|
| 29 |
+
target:
|
| 30 |
+
type: Utilization
|
| 31 |
+
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
| 32 |
+
{{- end }}
|
| 33 |
+
{{- end }}
|
deploy/helm/templates/ingress.yaml
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{- if .Values.ingress.enabled -}}
|
| 2 |
+
{{- $fullName := include "axonhub.fullname" . -}}
|
| 3 |
+
{{- $svcPort := .Values.axonhub.service.port -}}
|
| 4 |
+
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
| 5 |
+
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
| 6 |
+
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
| 7 |
+
{{- end }}
|
| 8 |
+
{{- end }}
|
| 9 |
+
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
| 10 |
+
apiVersion: networking.k8s.io/v1
|
| 11 |
+
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
| 12 |
+
apiVersion: networking.k8s.io/v1beta1
|
| 13 |
+
{{- else -}}
|
| 14 |
+
apiVersion: extensions/v1beta1
|
| 15 |
+
{{- end }}
|
| 16 |
+
kind: Ingress
|
| 17 |
+
metadata:
|
| 18 |
+
name: {{ $fullName }}
|
| 19 |
+
labels:
|
| 20 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 21 |
+
app.kubernetes.io/component: axonhub
|
| 22 |
+
{{- with .Values.ingress.annotations }}
|
| 23 |
+
annotations:
|
| 24 |
+
{{- toYaml . | nindent 4 }}
|
| 25 |
+
{{- end }}
|
| 26 |
+
spec:
|
| 27 |
+
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
| 28 |
+
ingressClassName: {{ .Values.ingress.className }}
|
| 29 |
+
{{- end }}
|
| 30 |
+
{{- if .Values.ingress.tls }}
|
| 31 |
+
tls:
|
| 32 |
+
{{- range .Values.ingress.tls }}
|
| 33 |
+
- hosts:
|
| 34 |
+
{{- range .hosts }}
|
| 35 |
+
- {{ . | quote }}
|
| 36 |
+
{{- end }}
|
| 37 |
+
secretName: {{ .secretName }}
|
| 38 |
+
{{- end }}
|
| 39 |
+
{{- end }}
|
| 40 |
+
rules:
|
| 41 |
+
{{- range .Values.ingress.hosts }}
|
| 42 |
+
- host: {{ .host | quote }}
|
| 43 |
+
http:
|
| 44 |
+
paths:
|
| 45 |
+
{{- range .paths }}
|
| 46 |
+
- path: {{ .path }}
|
| 47 |
+
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
| 48 |
+
pathType: {{ .pathType }}
|
| 49 |
+
{{- end }}
|
| 50 |
+
backend:
|
| 51 |
+
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
| 52 |
+
service:
|
| 53 |
+
name: {{ $fullName }}
|
| 54 |
+
port:
|
| 55 |
+
number: {{ $svcPort }}
|
| 56 |
+
{{- else }}
|
| 57 |
+
serviceName: {{ $fullName }}
|
| 58 |
+
servicePort: {{ $svcPort }}
|
| 59 |
+
{{- end }}
|
| 60 |
+
{{- end }}
|
| 61 |
+
{{- end }}
|
| 62 |
+
{{- end }}
|
deploy/helm/templates/postgresql-service.yaml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{- if .Values.postgresql.enabled -}}
|
| 2 |
+
apiVersion: v1
|
| 3 |
+
kind: Service
|
| 4 |
+
metadata:
|
| 5 |
+
name: {{ include "axonhub.postgresql.serviceName" . }}
|
| 6 |
+
labels:
|
| 7 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 8 |
+
app.kubernetes.io/component: postgresql
|
| 9 |
+
spec:
|
| 10 |
+
type: {{ .Values.postgresql.service.type }}
|
| 11 |
+
ports:
|
| 12 |
+
- port: {{ .Values.postgresql.service.port }}
|
| 13 |
+
targetPort: postgresql
|
| 14 |
+
protocol: TCP
|
| 15 |
+
name: postgresql
|
| 16 |
+
selector:
|
| 17 |
+
{{- include "axonhub.selectorLabels" . | nindent 4 }}
|
| 18 |
+
app.kubernetes.io/component: postgresql
|
| 19 |
+
{{- end }}
|
deploy/helm/templates/postgresql-statefulset.yaml
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{- if .Values.postgresql.enabled -}}
|
| 2 |
+
apiVersion: apps/v1
|
| 3 |
+
kind: StatefulSet
|
| 4 |
+
metadata:
|
| 5 |
+
name: {{ include "axonhub.postgresql.fullname" . }}
|
| 6 |
+
labels:
|
| 7 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 8 |
+
app.kubernetes.io/component: postgresql
|
| 9 |
+
spec:
|
| 10 |
+
serviceName: {{ include "axonhub.postgresql.serviceName" . }}
|
| 11 |
+
replicas: {{ .Values.postgresql.replicaCount }}
|
| 12 |
+
selector:
|
| 13 |
+
matchLabels:
|
| 14 |
+
{{- include "axonhub.selectorLabels" . | nindent 6 }}
|
| 15 |
+
app.kubernetes.io/component: postgresql
|
| 16 |
+
template:
|
| 17 |
+
metadata:
|
| 18 |
+
labels:
|
| 19 |
+
{{- include "axonhub.selectorLabels" . | nindent 8 }}
|
| 20 |
+
app.kubernetes.io/component: postgresql
|
| 21 |
+
spec:
|
| 22 |
+
{{- with .Values.global.imagePullSecrets }}
|
| 23 |
+
imagePullSecrets:
|
| 24 |
+
{{- toYaml . | nindent 8 }}
|
| 25 |
+
{{- end }}
|
| 26 |
+
securityContext:
|
| 27 |
+
{{- toYaml .Values.postgresql.podSecurityContext | nindent 8 }}
|
| 28 |
+
containers:
|
| 29 |
+
- name: postgresql
|
| 30 |
+
securityContext:
|
| 31 |
+
{{- toYaml .Values.postgresql.securityContext | nindent 12 }}
|
| 32 |
+
image: "{{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }}"
|
| 33 |
+
imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }}
|
| 34 |
+
env:
|
| 35 |
+
- name: POSTGRES_DB
|
| 36 |
+
value: {{ .Values.postgresql.auth.database | quote }}
|
| 37 |
+
- name: POSTGRES_USER
|
| 38 |
+
value: {{ .Values.postgresql.auth.username | quote }}
|
| 39 |
+
- name: POSTGRES_PASSWORD
|
| 40 |
+
value: {{ .Values.postgresql.auth.password | quote }}
|
| 41 |
+
ports:
|
| 42 |
+
- name: postgresql
|
| 43 |
+
containerPort: 5432
|
| 44 |
+
protocol: TCP
|
| 45 |
+
livenessProbe:
|
| 46 |
+
{{- toYaml .Values.postgresql.livenessProbe | nindent 12 }}
|
| 47 |
+
readinessProbe:
|
| 48 |
+
{{- toYaml .Values.postgresql.readinessProbe | nindent 12 }}
|
| 49 |
+
resources:
|
| 50 |
+
{{- toYaml .Values.postgresql.resources | nindent 12 }}
|
| 51 |
+
volumeMounts:
|
| 52 |
+
- name: data
|
| 53 |
+
mountPath: /var/lib/postgresql/data
|
| 54 |
+
subPath: postgresql-db
|
| 55 |
+
{{- with .Values.postgresql.nodeSelector }}
|
| 56 |
+
nodeSelector:
|
| 57 |
+
{{- toYaml . | nindent 8 }}
|
| 58 |
+
{{- end }}
|
| 59 |
+
{{- with .Values.postgresql.affinity }}
|
| 60 |
+
affinity:
|
| 61 |
+
{{- toYaml . | nindent 8 }}
|
| 62 |
+
{{- end }}
|
| 63 |
+
{{- with .Values.postgresql.tolerations }}
|
| 64 |
+
tolerations:
|
| 65 |
+
{{- toYaml . | nindent 8 }}
|
| 66 |
+
{{- end }}
|
| 67 |
+
volumeClaimTemplates:
|
| 68 |
+
- metadata:
|
| 69 |
+
name: data
|
| 70 |
+
spec:
|
| 71 |
+
accessModes:
|
| 72 |
+
{{- toYaml .Values.postgresql.primary.persistence.accessModes | nindent 10 }}
|
| 73 |
+
resources:
|
| 74 |
+
requests:
|
| 75 |
+
storage: {{ .Values.postgresql.primary.persistence.size }}
|
| 76 |
+
{{- if .Values.postgresql.primary.persistence.storageClass }}
|
| 77 |
+
storageClassName: {{ .Values.postgresql.primary.persistence.storageClass }}
|
| 78 |
+
{{- end }}
|
| 79 |
+
{{- end }}
|
deploy/helm/templates/service.yaml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
apiVersion: v1
|
| 2 |
+
kind: Service
|
| 3 |
+
metadata:
|
| 4 |
+
name: {{ include "axonhub.fullname" . }}
|
| 5 |
+
labels:
|
| 6 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 7 |
+
app.kubernetes.io/component: axonhub
|
| 8 |
+
spec:
|
| 9 |
+
type: {{ .Values.axonhub.service.type }}
|
| 10 |
+
ports:
|
| 11 |
+
- port: {{ .Values.axonhub.service.port }}
|
| 12 |
+
targetPort: http
|
| 13 |
+
protocol: TCP
|
| 14 |
+
name: http
|
| 15 |
+
selector:
|
| 16 |
+
{{- include "axonhub.selectorLabels" . | nindent 4 }}
|
| 17 |
+
app.kubernetes.io/component: axonhub
|
deploy/helm/templates/serviceaccount.yaml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{{- if .Values.serviceAccount.create -}}
|
| 2 |
+
apiVersion: v1
|
| 3 |
+
kind: ServiceAccount
|
| 4 |
+
metadata:
|
| 5 |
+
name: {{ include "axonhub.serviceAccountName" . }}
|
| 6 |
+
labels:
|
| 7 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 8 |
+
{{- with .Values.serviceAccount.annotations }}
|
| 9 |
+
annotations:
|
| 10 |
+
{{- toYaml . | nindent 4 }}
|
| 11 |
+
{{- end }}
|
| 12 |
+
{{- end }}
|
deploy/helm/templates/tests/test-connection.yaml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
apiVersion: v1
|
| 2 |
+
kind: Pod
|
| 3 |
+
metadata:
|
| 4 |
+
name: "{{ include "axonhub.fullname" . }}-test-connection"
|
| 5 |
+
labels:
|
| 6 |
+
{{- include "axonhub.labels" . | nindent 4 }}
|
| 7 |
+
app.kubernetes.io/component: test
|
| 8 |
+
annotations:
|
| 9 |
+
"helm.sh/hook": test
|
| 10 |
+
spec:
|
| 11 |
+
containers:
|
| 12 |
+
- name: wget
|
| 13 |
+
image: busybox
|
| 14 |
+
command: ['wget']
|
| 15 |
+
args: ['{{ include "axonhub.fullname" . }}:{{ .Values.axonhub.service.port }}']
|
| 16 |
+
restartPolicy: Never
|
deploy/helm/values-production.yaml
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Production values for AxonHub Helm chart
|
| 2 |
+
# Copy this file and modify according to your production requirements
|
| 3 |
+
|
| 4 |
+
# Global configuration
|
| 5 |
+
global:
|
| 6 |
+
imagePullSecrets: []
|
| 7 |
+
# Add your image pull secrets here if using private registry
|
| 8 |
+
# - name: my-registry-secret
|
| 9 |
+
|
| 10 |
+
# AxonHub application configuration
|
| 11 |
+
axonhub:
|
| 12 |
+
replicaCount: 2
|
| 13 |
+
|
| 14 |
+
image:
|
| 15 |
+
repository: looplj/axonhub
|
| 16 |
+
tag: "latest" # Consider using specific version in production
|
| 17 |
+
pullPolicy: IfNotPresent
|
| 18 |
+
|
| 19 |
+
# Environment variables - customize the database connection string for your environment
|
| 20 |
+
env:
|
| 21 |
+
AXONHUB_DB_DIALECT: postgres
|
| 22 |
+
# For external database - modify this connection string
|
| 23 |
+
AXONHUB_DB_DSN: postgres://axonhub:your_db_password@your-db-host:5432/axonhub?sslmode=require
|
| 24 |
+
# For internal PostgreSQL - uncomment below and comment above
|
| 25 |
+
# AXONHUB_DB_DSN: postgres://axonhub:CHANGE_ME_POSTGRES_USER_123456@axonhub-postgresql:5432/axonhub?sslmode=disable
|
| 26 |
+
|
| 27 |
+
# Environment variables are defined above in the axonhub.env section
|
| 28 |
+
|
| 29 |
+
# Service configuration
|
| 30 |
+
service:
|
| 31 |
+
type: ClusterIP
|
| 32 |
+
port: 8090
|
| 33 |
+
|
| 34 |
+
# Resources - adjust based on your workload
|
| 35 |
+
resources:
|
| 36 |
+
limits:
|
| 37 |
+
cpu: 2000m
|
| 38 |
+
memory: 2Gi
|
| 39 |
+
requests:
|
| 40 |
+
cpu: 1000m
|
| 41 |
+
memory: 1Gi
|
| 42 |
+
|
| 43 |
+
# Health checks
|
| 44 |
+
livenessProbe:
|
| 45 |
+
httpGet:
|
| 46 |
+
path: /health
|
| 47 |
+
port: 8090
|
| 48 |
+
initialDelaySeconds: 60
|
| 49 |
+
periodSeconds: 30
|
| 50 |
+
timeoutSeconds: 10
|
| 51 |
+
failureThreshold: 3
|
| 52 |
+
|
| 53 |
+
readinessProbe:
|
| 54 |
+
httpGet:
|
| 55 |
+
path: /health
|
| 56 |
+
port: 8090
|
| 57 |
+
initialDelaySeconds: 30
|
| 58 |
+
periodSeconds: 10
|
| 59 |
+
timeoutSeconds: 5
|
| 60 |
+
failureThreshold: 3
|
| 61 |
+
|
| 62 |
+
# Security context
|
| 63 |
+
podSecurityContext:
|
| 64 |
+
fsGroup: 1000
|
| 65 |
+
|
| 66 |
+
securityContext:
|
| 67 |
+
runAsNonRoot: true
|
| 68 |
+
runAsUser: 1000
|
| 69 |
+
runAsGroup: 1000
|
| 70 |
+
|
| 71 |
+
# Persistence - enable for production
|
| 72 |
+
persistence:
|
| 73 |
+
enabled: true
|
| 74 |
+
size: 20Gi
|
| 75 |
+
storageClass: "" # Specify your storage class
|
| 76 |
+
accessModes:
|
| 77 |
+
- ReadWriteOnce
|
| 78 |
+
|
| 79 |
+
# PostgreSQL configuration - set to false if using external database
|
| 80 |
+
postgresql:
|
| 81 |
+
enabled: true
|
| 82 |
+
replicaCount: 1
|
| 83 |
+
|
| 84 |
+
image:
|
| 85 |
+
repository: postgres
|
| 86 |
+
tag: "16-alpine"
|
| 87 |
+
pullPolicy: IfNotPresent
|
| 88 |
+
|
| 89 |
+
# Security - CHANGE THESE VALUES IN PRODUCTION
|
| 90 |
+
auth:
|
| 91 |
+
postgresPassword: "CHANGE_ME_POSTGRES_ADMIN_123456"
|
| 92 |
+
username: "axonhub"
|
| 93 |
+
password: "CHANGE_ME_POSTGRES_USER_123456"
|
| 94 |
+
database: "axonhub"
|
| 95 |
+
|
| 96 |
+
# Service configuration
|
| 97 |
+
service:
|
| 98 |
+
type: ClusterIP
|
| 99 |
+
port: 5432
|
| 100 |
+
|
| 101 |
+
# Resources
|
| 102 |
+
resources:
|
| 103 |
+
limits:
|
| 104 |
+
cpu: 1000m
|
| 105 |
+
memory: 1Gi
|
| 106 |
+
requests:
|
| 107 |
+
cpu: 500m
|
| 108 |
+
memory: 512Mi
|
| 109 |
+
|
| 110 |
+
# Persistence
|
| 111 |
+
primary:
|
| 112 |
+
persistence:
|
| 113 |
+
enabled: true
|
| 114 |
+
size: 20Gi
|
| 115 |
+
storageClass: "" # Specify your storage class
|
| 116 |
+
accessModes:
|
| 117 |
+
- ReadWriteOnce
|
| 118 |
+
|
| 119 |
+
# Health checks
|
| 120 |
+
livenessProbe:
|
| 121 |
+
exec:
|
| 122 |
+
command:
|
| 123 |
+
- pg_isready
|
| 124 |
+
- -U
|
| 125 |
+
- axonhub
|
| 126 |
+
initialDelaySeconds: 30
|
| 127 |
+
periodSeconds: 10
|
| 128 |
+
timeoutSeconds: 5
|
| 129 |
+
failureThreshold: 6
|
| 130 |
+
|
| 131 |
+
readinessProbe:
|
| 132 |
+
exec:
|
| 133 |
+
command:
|
| 134 |
+
- pg_isready
|
| 135 |
+
- -U
|
| 136 |
+
- axonhub
|
| 137 |
+
initialDelaySeconds: 5
|
| 138 |
+
periodSeconds: 10
|
| 139 |
+
timeoutSeconds: 5
|
| 140 |
+
failureThreshold: 6
|
| 141 |
+
|
| 142 |
+
# Ingress configuration - enable for external access
|
| 143 |
+
ingress:
|
| 144 |
+
enabled: true
|
| 145 |
+
className: "nginx" # Adjust based on your ingress controller
|
| 146 |
+
annotations:
|
| 147 |
+
# Add your ingress annotations here
|
| 148 |
+
# nginx.ingress.kubernetes.io/rewrite-target: /
|
| 149 |
+
cert-manager.io/cluster-issuer: "letsencrypt-prod" # For SSL certificates
|
| 150 |
+
hosts:
|
| 151 |
+
- host: axonhub.yourdomain.com # Change to your domain
|
| 152 |
+
paths:
|
| 153 |
+
- path: /
|
| 154 |
+
pathType: Prefix
|
| 155 |
+
tls:
|
| 156 |
+
- secretName: axonhub-tls
|
| 157 |
+
hosts:
|
| 158 |
+
- axonhub.yourdomain.com # Change to your domain
|
| 159 |
+
|
| 160 |
+
# Autoscaling - enable for production workloads
|
| 161 |
+
autoscaling:
|
| 162 |
+
enabled: true
|
| 163 |
+
minReplicas: 2
|
| 164 |
+
maxReplicas: 10
|
| 165 |
+
targetCPUUtilizationPercentage: 80
|
| 166 |
+
|
| 167 |
+
# Service account
|
| 168 |
+
serviceAccount:
|
| 169 |
+
create: true
|
| 170 |
+
annotations: {}
|
| 171 |
+
name: ""
|
deploy/helm/values.yaml
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Default values for axonhub.
|
| 2 |
+
# This is a YAML-formatted file.
|
| 3 |
+
# Declare variables to be passed into your templates.
|
| 4 |
+
|
| 5 |
+
global:
|
| 6 |
+
# Global image pull secrets
|
| 7 |
+
imagePullSecrets: []
|
| 8 |
+
|
| 9 |
+
# AxonHub application configuration
|
| 10 |
+
axonhub:
|
| 11 |
+
replicaCount: 1
|
| 12 |
+
|
| 13 |
+
image:
|
| 14 |
+
repository: looplj/axonhub
|
| 15 |
+
pullPolicy: IfNotPresent
|
| 16 |
+
tag: "latest"
|
| 17 |
+
|
| 18 |
+
# Environment variables for AxonHub
|
| 19 |
+
env:
|
| 20 |
+
AXONHUB_DB_DIALECT: postgres
|
| 21 |
+
# Full database connection string - customize this for your environment
|
| 22 |
+
AXONHUB_DB_DSN: postgres://axonhub:axonhub_password@axonhub-postgresql:5432/axonhub?sslmode=disable
|
| 23 |
+
|
| 24 |
+
# Service configuration
|
| 25 |
+
service:
|
| 26 |
+
type: ClusterIP
|
| 27 |
+
port: 8090
|
| 28 |
+
|
| 29 |
+
# Resource limits and requests
|
| 30 |
+
resources:
|
| 31 |
+
limits:
|
| 32 |
+
cpu: 1000m
|
| 33 |
+
memory: 1Gi
|
| 34 |
+
requests:
|
| 35 |
+
cpu: 500m
|
| 36 |
+
memory: 512Mi
|
| 37 |
+
|
| 38 |
+
# Health checks
|
| 39 |
+
livenessProbe:
|
| 40 |
+
httpGet:
|
| 41 |
+
path: /health
|
| 42 |
+
port: 8090
|
| 43 |
+
initialDelaySeconds: 60
|
| 44 |
+
periodSeconds: 30
|
| 45 |
+
timeoutSeconds: 10
|
| 46 |
+
failureThreshold: 3
|
| 47 |
+
|
| 48 |
+
readinessProbe:
|
| 49 |
+
httpGet:
|
| 50 |
+
path: /health
|
| 51 |
+
port: 8090
|
| 52 |
+
initialDelaySeconds: 30
|
| 53 |
+
periodSeconds: 10
|
| 54 |
+
timeoutSeconds: 5
|
| 55 |
+
failureThreshold: 3
|
| 56 |
+
|
| 57 |
+
# Security context
|
| 58 |
+
podSecurityContext:
|
| 59 |
+
fsGroup: 1000
|
| 60 |
+
|
| 61 |
+
securityContext:
|
| 62 |
+
runAsNonRoot: true
|
| 63 |
+
runAsUser: 1000
|
| 64 |
+
runAsGroup: 1000
|
| 65 |
+
|
| 66 |
+
# Node selection
|
| 67 |
+
nodeSelector: {}
|
| 68 |
+
tolerations: []
|
| 69 |
+
affinity: {}
|
| 70 |
+
|
| 71 |
+
# Persistent volume for data storage (optional)
|
| 72 |
+
persistence:
|
| 73 |
+
enabled: false
|
| 74 |
+
size: 10Gi
|
| 75 |
+
storageClass: ""
|
| 76 |
+
accessModes:
|
| 77 |
+
- ReadWriteOnce
|
| 78 |
+
|
| 79 |
+
# PostgreSQL database configuration
|
| 80 |
+
postgresql:
|
| 81 |
+
enabled: true
|
| 82 |
+
replicaCount: 1
|
| 83 |
+
|
| 84 |
+
image:
|
| 85 |
+
repository: postgres
|
| 86 |
+
tag: "16-alpine"
|
| 87 |
+
pullPolicy: IfNotPresent
|
| 88 |
+
|
| 89 |
+
# Database credentials
|
| 90 |
+
auth:
|
| 91 |
+
postgresPassword: "axonhub_password" # Should be overridden in production
|
| 92 |
+
username: "axonhub"
|
| 93 |
+
password: "axonhub_password" # Should be overridden in production
|
| 94 |
+
database: "axonhub"
|
| 95 |
+
|
| 96 |
+
# Service configuration
|
| 97 |
+
service:
|
| 98 |
+
type: ClusterIP
|
| 99 |
+
port: 5432
|
| 100 |
+
|
| 101 |
+
# Resource limits
|
| 102 |
+
resources:
|
| 103 |
+
limits:
|
| 104 |
+
cpu: 500m
|
| 105 |
+
memory: 512Mi
|
| 106 |
+
requests:
|
| 107 |
+
cpu: 250m
|
| 108 |
+
memory: 256Mi
|
| 109 |
+
|
| 110 |
+
# Persistence
|
| 111 |
+
primary:
|
| 112 |
+
persistence:
|
| 113 |
+
enabled: true
|
| 114 |
+
size: 8Gi
|
| 115 |
+
storageClass: ""
|
| 116 |
+
accessModes:
|
| 117 |
+
- ReadWriteOnce
|
| 118 |
+
|
| 119 |
+
# Health check
|
| 120 |
+
livenessProbe:
|
| 121 |
+
exec:
|
| 122 |
+
command:
|
| 123 |
+
- pg_isready
|
| 124 |
+
- -U
|
| 125 |
+
- axonhub
|
| 126 |
+
initialDelaySeconds: 30
|
| 127 |
+
periodSeconds: 10
|
| 128 |
+
timeoutSeconds: 5
|
| 129 |
+
failureThreshold: 6
|
| 130 |
+
|
| 131 |
+
readinessProbe:
|
| 132 |
+
exec:
|
| 133 |
+
command:
|
| 134 |
+
- pg_isready
|
| 135 |
+
- -U
|
| 136 |
+
- axonhub
|
| 137 |
+
initialDelaySeconds: 5
|
| 138 |
+
periodSeconds: 10
|
| 139 |
+
timeoutSeconds: 5
|
| 140 |
+
failureThreshold: 6
|
| 141 |
+
|
| 142 |
+
# Ingress configuration
|
| 143 |
+
ingress:
|
| 144 |
+
enabled: true
|
| 145 |
+
className: ""
|
| 146 |
+
annotations: {}
|
| 147 |
+
hosts:
|
| 148 |
+
- host: axonhub.local
|
| 149 |
+
paths:
|
| 150 |
+
- path: /
|
| 151 |
+
pathType: Prefix
|
| 152 |
+
tls: []
|
| 153 |
+
|
| 154 |
+
# Autoscaling configuration
|
| 155 |
+
autoscaling:
|
| 156 |
+
enabled: false
|
| 157 |
+
minReplicas: 1
|
| 158 |
+
maxReplicas: 10
|
| 159 |
+
targetCPUUtilizationPercentage: 80
|
| 160 |
+
|
| 161 |
+
# Service account configuration
|
| 162 |
+
serviceAccount:
|
| 163 |
+
create: true
|
| 164 |
+
annotations: {}
|
| 165 |
+
name: ""
|
deploy/install.bat
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
|
| 4 |
+
REM AxonHub Windows installer wrapper (.bat)
|
| 5 |
+
|
| 6 |
+
where powershell >NUL 2>NUL
|
| 7 |
+
if %ERRORLEVEL% NEQ 0 (
|
| 8 |
+
echo [ERROR] PowerShell is required to run this installer.
|
| 9 |
+
echo Please run on Windows 7+ with PowerShell available in PATH.
|
| 10 |
+
exit /b 1
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
set "SCRIPT_DIR=%~dp0"
|
| 14 |
+
powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%install.ps1" %*
|
| 15 |
+
exit /b %ERRORLEVEL%
|
deploy/install.ps1
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param(
|
| 2 |
+
[Parameter(ValueFromRemainingArguments=$true)]
|
| 3 |
+
[string[]]$ArgsFromCmd
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
$ErrorActionPreference = 'Stop'
|
| 7 |
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
| 8 |
+
|
| 9 |
+
function Write-Info([string]$m){ Write-Host "[INFO] $m" -ForegroundColor Cyan }
|
| 10 |
+
function Write-Success([string]$m){ Write-Host "[SUCCESS] $m" -ForegroundColor Green }
|
| 11 |
+
function Write-Warn([string]$m){ Write-Host "[WARNING] $m" -ForegroundColor Yellow }
|
| 12 |
+
function Write-Err([string]$m){ Write-Host "[ERROR] $m" -ForegroundColor Red }
|
| 13 |
+
|
| 14 |
+
$Repo = 'looplj/axonhub'
|
| 15 |
+
$Api = "https://api.github.com/repos/$Repo"
|
| 16 |
+
|
| 17 |
+
$IncludeBeta = $false
|
| 18 |
+
$IncludeRC = $false
|
| 19 |
+
$VerboseFlag = $false
|
| 20 |
+
$Version = $env:AXONHUB_VERSION
|
| 21 |
+
|
| 22 |
+
function Show-Usage {
|
| 23 |
+
Write-Host @'
|
| 24 |
+
AxonHub Installer (Windows)
|
| 25 |
+
|
| 26 |
+
Usage:
|
| 27 |
+
install.bat [options] [version]
|
| 28 |
+
|
| 29 |
+
Options:
|
| 30 |
+
-b, --beta Consider beta pre-releases
|
| 31 |
+
-r, --rc Consider release-candidate pre-releases
|
| 32 |
+
-v, --verbose Print extra debug logs
|
| 33 |
+
-h, --help Show this help and exit
|
| 34 |
+
'@
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
# Parse args
|
| 38 |
+
foreach($a in $ArgsFromCmd){
|
| 39 |
+
switch -Regex ($a){
|
| 40 |
+
'^(--beta|-b)$' { $IncludeBeta = $true; continue }
|
| 41 |
+
'^(--rc|-r)$' { $IncludeRC = $true; continue }
|
| 42 |
+
'^(--verbose|-v)$' { $VerboseFlag = $true; continue }
|
| 43 |
+
'^(--help|-h)$' { Show-Usage; exit 0 }
|
| 44 |
+
default { if(-not $Version){ $Version = $a } else { Write-Warn "Ignoring extra argument: $a" } }
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
function Get-Platform {
|
| 49 |
+
$archEnv = $env:PROCESSOR_ARCHITECTURE
|
| 50 |
+
switch ($archEnv.ToLower()){
|
| 51 |
+
'amd64' { return 'windows_amd64' }
|
| 52 |
+
'arm64' { return 'windows_arm64' }
|
| 53 |
+
default { Write-Err "Unsupported architecture: $archEnv"; exit 1 }
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
function Invoke-GHApi([string]$url){
|
| 58 |
+
$headers = @{
|
| 59 |
+
'Accept'='application/vnd.github+json'
|
| 60 |
+
'X-GitHub-Api-Version'='2022-11-28'
|
| 61 |
+
'User-Agent'='axonhub-installer'
|
| 62 |
+
}
|
| 63 |
+
if($env:GITHUB_TOKEN){ $headers['Authorization'] = "Bearer $($env:GITHUB_TOKEN)" }
|
| 64 |
+
return Invoke-RestMethod -Method GET -Uri $url -Headers $headers -ErrorAction Stop
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
function Get-LatestReleaseTag {
|
| 68 |
+
try {
|
| 69 |
+
$json = Invoke-GHApi "$Api/releases/latest"
|
| 70 |
+
return $json.tag_name
|
| 71 |
+
} catch {
|
| 72 |
+
# Fallback to HTML redirect (best-effort)
|
| 73 |
+
Write-Warn "API failed or rate-limited, falling back to HTML redirect..."
|
| 74 |
+
try {
|
| 75 |
+
$resp = Invoke-WebRequest -Uri "https://github.com/$Repo/releases/latest" -Headers @{ 'User-Agent'='axonhub-installer' } -MaximumRedirection 0 -ErrorAction Stop
|
| 76 |
+
} catch { $resp = $_.Exception.Response }
|
| 77 |
+
if($resp -and $resp.Headers['Location']){
|
| 78 |
+
$loc = $resp.Headers['Location']
|
| 79 |
+
if($loc -match '/tag/([^/]+)$'){ return $matches[1] }
|
| 80 |
+
}
|
| 81 |
+
Write-Err "Could not determine latest release version"
|
| 82 |
+
exit 1
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
function Get-LatestVersion([bool]$includeBeta,[bool]$includeRC){
|
| 87 |
+
if(-not $includeBeta -and -not $includeRC){ return Get-LatestReleaseTag }
|
| 88 |
+
Write-Info "Fetching releases (beta=$includeBeta, rc=$includeRC) ..."
|
| 89 |
+
try {
|
| 90 |
+
$rels = Invoke-GHApi "$Api/releases?per_page=100"
|
| 91 |
+
$filtered = $rels | Where-Object { -not $_.draft }
|
| 92 |
+
if($includeBeta -and $includeRC){
|
| 93 |
+
$filtered = $filtered | Where-Object { $_.tag_name -match '-beta' -or $_.tag_name -match '-rc' }
|
| 94 |
+
} elseif($includeBeta){
|
| 95 |
+
$filtered = $filtered | Where-Object { $_.tag_name -match '-beta' }
|
| 96 |
+
} else {
|
| 97 |
+
$filtered = $filtered | Where-Object { $_.tag_name -match '-rc' }
|
| 98 |
+
}
|
| 99 |
+
if(-not $filtered){
|
| 100 |
+
Write-Warn 'No matching pre-release found; falling back to latest stable.'
|
| 101 |
+
return Get-LatestReleaseTag
|
| 102 |
+
}
|
| 103 |
+
# GitHub API returns releases in reverse chronological order; take the first
|
| 104 |
+
return ($filtered | Select-Object -First 1).tag_name
|
| 105 |
+
} catch {
|
| 106 |
+
Write-Warn 'Failed to fetch releases; falling back to latest stable.'
|
| 107 |
+
return Get-LatestReleaseTag
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
function Get-AssetUrl([string]$version,[string]$platform){
|
| 112 |
+
Write-Info "Resolving asset for $version ($platform) ..."
|
| 113 |
+
try {
|
| 114 |
+
$tag = $version
|
| 115 |
+
$json = Invoke-GHApi "$Api/releases/tags/$tag"
|
| 116 |
+
$asset = $json.assets | Where-Object { $_.browser_download_url -match $platform -and $_.browser_download_url -like '*.zip' } | Select-Object -First 1
|
| 117 |
+
if($asset){ return $asset.browser_download_url }
|
| 118 |
+
} catch {}
|
| 119 |
+
# Fallback by pattern
|
| 120 |
+
$clean = $version.TrimStart('v')
|
| 121 |
+
$file = "axonhub_${clean}_${platform}.zip"
|
| 122 |
+
$candidate = "https://github.com/$Repo/releases/download/$version/$file"
|
| 123 |
+
try {
|
| 124 |
+
$head = Invoke-WebRequest -Uri $candidate -Method Head -ErrorAction Stop
|
| 125 |
+
return $candidate
|
| 126 |
+
} catch {
|
| 127 |
+
Write-Err "Could not find asset for platform $platform in release $version"
|
| 128 |
+
exit 1
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
function Ensure-Dirs([string]$path){ if(-not (Test-Path $path)){ New-Item -ItemType Directory -Force -Path $path | Out-Null } }
|
| 133 |
+
|
| 134 |
+
# Main
|
| 135 |
+
Write-Info 'Starting AxonHub installation...'
|
| 136 |
+
|
| 137 |
+
$Platform = Get-Platform
|
| 138 |
+
Write-Info "Detected platform: $Platform"
|
| 139 |
+
|
| 140 |
+
if(-not $Version){ $Version = Get-LatestVersion $IncludeBeta $IncludeRC }
|
| 141 |
+
Write-Info "Using version: $Version"
|
| 142 |
+
|
| 143 |
+
$BaseDir = Join-Path $env:LOCALAPPDATA 'AxonHub'
|
| 144 |
+
$ConfigDir = $BaseDir
|
| 145 |
+
$DataDir = $BaseDir
|
| 146 |
+
$LogDir = $BaseDir
|
| 147 |
+
Ensure-Dirs $BaseDir
|
| 148 |
+
Ensure-Dirs (Join-Path $BaseDir 'logs')
|
| 149 |
+
|
| 150 |
+
$AssetUrl = Get-AssetUrl $Version $Platform
|
| 151 |
+
$TempDir = New-Item -ItemType Directory -Path (Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())) -Force
|
| 152 |
+
$ZipPath = Join-Path $TempDir 'axonhub.zip'
|
| 153 |
+
Write-Info "Downloading: $AssetUrl"
|
| 154 |
+
Invoke-WebRequest -Uri $AssetUrl -OutFile $ZipPath -UseBasicParsing
|
| 155 |
+
|
| 156 |
+
Write-Info 'Extracting archive...'
|
| 157 |
+
Expand-Archive -Path $ZipPath -DestinationPath $TempDir -Force
|
| 158 |
+
|
| 159 |
+
$BinaryPath = Get-ChildItem -Path $TempDir -Recurse -Filter 'axonhub.exe' -File | Select-Object -First 1 | ForEach-Object { $_.FullName }
|
| 160 |
+
if(-not $BinaryPath){ Write-Err 'axonhub.exe not found in archive'; exit 1 }
|
| 161 |
+
|
| 162 |
+
$TargetBinary = Join-Path $BaseDir 'axonhub.exe'
|
| 163 |
+
Copy-Item -Path $BinaryPath -Destination $TargetBinary -Force
|
| 164 |
+
|
| 165 |
+
# Create default config if missing
|
| 166 |
+
$ConfigFile = Join-Path $BaseDir 'config.yml'
|
| 167 |
+
if(-not (Test-Path $ConfigFile)){
|
| 168 |
+
Write-Info 'Creating default configuration...'
|
| 169 |
+
$baseForDSN = ($BaseDir -replace '\\','/')
|
| 170 |
+
@"
|
| 171 |
+
server:
|
| 172 |
+
port: 8090
|
| 173 |
+
name: "AxonHub"
|
| 174 |
+
debug: false
|
| 175 |
+
|
| 176 |
+
db:
|
| 177 |
+
dialect: "sqlite3"
|
| 178 |
+
dsn: "$baseForDSN/axonhub.db?cache=shared&_fk=1&journal_mode=WAL"
|
| 179 |
+
|
| 180 |
+
cache:
|
| 181 |
+
mode: "memory"
|
| 182 |
+
cache:
|
| 183 |
+
expiration: "5s"
|
| 184 |
+
cleanup_interval: "5s"
|
| 185 |
+
|
| 186 |
+
log:
|
| 187 |
+
level: "info"
|
| 188 |
+
encoding: "json"
|
| 189 |
+
output: "file"
|
| 190 |
+
file:
|
| 191 |
+
path: "$baseForDSN/logs/axonhub.log"
|
| 192 |
+
max_size: 100
|
| 193 |
+
max_age: 30
|
| 194 |
+
max_backups: 10
|
| 195 |
+
local_time: true
|
| 196 |
+
"@ | Set-Content -Path $ConfigFile -Encoding UTF8
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
Write-Success 'AxonHub installation completed!'
|
| 200 |
+
|
| 201 |
+
# Get configured port for display
|
| 202 |
+
$port = 8090
|
| 203 |
+
if(Test-Path $TargetBinary){
|
| 204 |
+
try {
|
| 205 |
+
$configPort = & $TargetBinary config get server.port 2>$null
|
| 206 |
+
if($configPort -match '^[0-9]+$'){
|
| 207 |
+
$port = $configPort
|
| 208 |
+
}
|
| 209 |
+
} catch {}
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
Write-Info "Next steps:"
|
| 213 |
+
Write-Host " 1. Edit configuration: $ConfigFile"
|
| 214 |
+
Write-Host " 2. Start AxonHub: start.bat"
|
| 215 |
+
Write-Host " 3. Stop AxonHub: stop.bat"
|
| 216 |
+
Write-Host " 4. View logs: $BaseDir\axonhub.log (or logs\axonhub.log in config)"
|
| 217 |
+
Write-Host " 5. Access web interface: http://localhost:$port"
|
deploy/install.sh
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# AxonHub Installation Script
|
| 4 |
+
# This script downloads and installs the latest AxonHub release for direct start/stop usage (no systemd)
|
| 5 |
+
|
| 6 |
+
set -e
|
| 7 |
+
|
| 8 |
+
# Colors for output
|
| 9 |
+
RED='\033[0;31m'
|
| 10 |
+
GREEN='\033[0;32m'
|
| 11 |
+
YELLOW='\033[1;33m'
|
| 12 |
+
BLUE='\033[0;34m'
|
| 13 |
+
NC='\033[0m' # No Color
|
| 14 |
+
|
| 15 |
+
# Configuration
|
| 16 |
+
INSTALL_DIR="/usr/local/bin"
|
| 17 |
+
# Resolve non-root user's HOME when running via sudo
|
| 18 |
+
if [[ -n "$SUDO_USER" && "$SUDO_USER" != "root" ]]; then
|
| 19 |
+
USER_HOME="$(eval echo ~${SUDO_USER})"
|
| 20 |
+
else
|
| 21 |
+
USER_HOME="$HOME"
|
| 22 |
+
fi
|
| 23 |
+
BASE_DIR="${USER_HOME}/.config/axonhub"
|
| 24 |
+
CONFIG_DIR="${BASE_DIR}"
|
| 25 |
+
DATA_DIR="${BASE_DIR}"
|
| 26 |
+
LOG_DIR="${BASE_DIR}"
|
| 27 |
+
SERVICE_USER="axonhub"
|
| 28 |
+
|
| 29 |
+
# GitHub repository
|
| 30 |
+
REPO="looplj/axonhub"
|
| 31 |
+
GITHUB_API="https://api.github.com/repos/${REPO}"
|
| 32 |
+
|
| 33 |
+
# CLI options (default: exclude beta/rc)
|
| 34 |
+
INCLUDE_BETA="false"
|
| 35 |
+
INCLUDE_RC="false"
|
| 36 |
+
VERBOSE="false"
|
| 37 |
+
|
| 38 |
+
print_info() {
|
| 39 |
+
echo -e "${BLUE}[INFO]${NC} $1" 1>&2
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
curl_gh() {
|
| 43 |
+
# Curl helper for GitHub with proper headers and optional token
|
| 44 |
+
local url="$1"
|
| 45 |
+
local headers=(
|
| 46 |
+
-H "Accept: application/vnd.github+json"
|
| 47 |
+
-H "X-GitHub-Api-Version: 2022-11-28"
|
| 48 |
+
-H "User-Agent: axonhub-installer"
|
| 49 |
+
)
|
| 50 |
+
if [[ -n "$GITHUB_TOKEN" ]]; then
|
| 51 |
+
headers+=( -H "Authorization: Bearer $GITHUB_TOKEN" )
|
| 52 |
+
fi
|
| 53 |
+
curl -fsSL "${headers[@]}" "$url"
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
print_success() {
|
| 57 |
+
echo -e "${GREEN}[SUCCESS]${NC} $1" 1>&2
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
print_warning() {
|
| 61 |
+
echo -e "${YELLOW}[WARNING]${NC} $1" 1>&2
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
print_error() {
|
| 65 |
+
echo -e "${RED}[ERROR]${NC} $1" 1>&2
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
# Verbose logger
|
| 69 |
+
debug() {
|
| 70 |
+
if [[ "$VERBOSE" == "true" ]]; then
|
| 71 |
+
echo -e "${YELLOW}[DEBUG]${NC} $1" 1>&2
|
| 72 |
+
fi
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
usage() {
|
| 76 |
+
cat 1>&2 <<EOF
|
| 77 |
+
AxonHub Installer
|
| 78 |
+
|
| 79 |
+
Usage:
|
| 80 |
+
sudo ./install.sh [options] [version]
|
| 81 |
+
|
| 82 |
+
Options:
|
| 83 |
+
-b, --beta Consider beta pre-releases when resolving latest version
|
| 84 |
+
-r, --rc Consider release-candidate (rc) pre-releases when resolving latest version
|
| 85 |
+
-v, --verbose Print extra debug logs
|
| 86 |
+
-h, --help Show this help and exit
|
| 87 |
+
|
| 88 |
+
Notes:
|
| 89 |
+
- By default, beta/rc versions are filtered out and the latest stable release is used.
|
| 90 |
+
- If a version is provided (e.g., v1.2.3), flags are ignored and that version is used.
|
| 91 |
+
- If both --beta and --rc are provided, the newest matching pre-release is selected.
|
| 92 |
+
EOF
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
check_root() {
|
| 96 |
+
if [[ $EUID -ne 0 ]]; then
|
| 97 |
+
print_error "This script must be run as root (use sudo)"
|
| 98 |
+
exit 1
|
| 99 |
+
fi
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
detect_architecture() {
|
| 103 |
+
local arch=$(uname -m)
|
| 104 |
+
local os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
| 105 |
+
|
| 106 |
+
case $arch in
|
| 107 |
+
x86_64|amd64)
|
| 108 |
+
arch="amd64"
|
| 109 |
+
;;
|
| 110 |
+
aarch64|arm64)
|
| 111 |
+
arch="arm64"
|
| 112 |
+
;;
|
| 113 |
+
*)
|
| 114 |
+
print_error "Unsupported architecture: $arch"
|
| 115 |
+
exit 1
|
| 116 |
+
;;
|
| 117 |
+
esac
|
| 118 |
+
|
| 119 |
+
case $os in
|
| 120 |
+
linux)
|
| 121 |
+
os="linux"
|
| 122 |
+
;;
|
| 123 |
+
darwin)
|
| 124 |
+
os="darwin"
|
| 125 |
+
;;
|
| 126 |
+
*)
|
| 127 |
+
print_error "Unsupported operating system: $os"
|
| 128 |
+
exit 1
|
| 129 |
+
;;
|
| 130 |
+
esac
|
| 131 |
+
|
| 132 |
+
echo "${os}_${arch}"
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
get_latest_release() {
|
| 136 |
+
print_info "Fetching latest release information..."
|
| 137 |
+
|
| 138 |
+
local tag_name
|
| 139 |
+
# Try GitHub API first
|
| 140 |
+
if json=$(curl_gh "${GITHUB_API}/releases/latest" 2>/dev/null); then
|
| 141 |
+
tag_name=$(echo "$json" | tr -d '\n\r\t' | sed -nE 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' | head -1)
|
| 142 |
+
fi
|
| 143 |
+
|
| 144 |
+
# Fallback: follow the HTML redirect to the latest tag
|
| 145 |
+
if [[ -z "$tag_name" ]]; then
|
| 146 |
+
print_warning "API failed or rate-limited, falling back to HTML redirect..."
|
| 147 |
+
local final_url
|
| 148 |
+
final_url=$(curl -fsSL -H "User-Agent: axonhub-installer" -o /dev/null -w "%{url_effective}" "https://github.com/${REPO}/releases/latest" || true)
|
| 149 |
+
tag_name=$(echo "$final_url" | sed -nE 's#.*/tag/([^/]+).*#\1#p' | head -1)
|
| 150 |
+
fi
|
| 151 |
+
|
| 152 |
+
if [[ -z "$tag_name" ]]; then
|
| 153 |
+
print_error "Could not determine latest release version"
|
| 154 |
+
exit 1
|
| 155 |
+
fi
|
| 156 |
+
|
| 157 |
+
debug "Selected tag: $tag_name"
|
| 158 |
+
echo "$tag_name"
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
# Get the latest version based on flags (default stable; with --beta/--rc select matching pre-releases)
|
| 162 |
+
get_latest_version() {
|
| 163 |
+
local include_beta="$1"
|
| 164 |
+
local include_rc="$2"
|
| 165 |
+
|
| 166 |
+
# Default path: stable-only
|
| 167 |
+
if [[ "$include_beta" != "true" && "$include_rc" != "true" ]]; then
|
| 168 |
+
get_latest_release
|
| 169 |
+
return
|
| 170 |
+
fi
|
| 171 |
+
|
| 172 |
+
print_info "Fetching releases to determine latest version (beta=${include_beta}, rc=${include_rc})..."
|
| 173 |
+
|
| 174 |
+
local json tag_name pattern
|
| 175 |
+
tag_name=""
|
| 176 |
+
if [[ "$include_beta" == "true" && "$include_rc" == "true" ]]; then
|
| 177 |
+
pattern='-beta|-rc'
|
| 178 |
+
elif [[ "$include_beta" == "true" ]]; then
|
| 179 |
+
pattern='-beta'
|
| 180 |
+
else
|
| 181 |
+
pattern='-rc'
|
| 182 |
+
fi
|
| 183 |
+
|
| 184 |
+
if json=$(curl_gh "${GITHUB_API}/releases?per_page=100" 2>/dev/null); then
|
| 185 |
+
local tags pairs
|
| 186 |
+
if command -v jq >/dev/null 2>&1; then
|
| 187 |
+
tags=$(echo "$json" | jq -r '.[] | select(.draft==false) | .tag_name')
|
| 188 |
+
else
|
| 189 |
+
tags=$(echo "$json" | grep -oE '"tag_name"\s*:\s*"[^"]+"' | sed -E 's/.*"tag_name"\s*:\s*"([^"]+)".*/\1/')
|
| 190 |
+
fi
|
| 191 |
+
|
| 192 |
+
debug "Fetched release tags (first 20): $(printf '%s\n' "$tags" | head -n20 | tr '\n' ' ')"
|
| 193 |
+
|
| 194 |
+
# Build pairs of cleaned|original, where cleaned strips any prefix before the semantic version
|
| 195 |
+
pairs=$(printf '%s\n' "$tags" | awk '{
|
| 196 |
+
orig=$0;
|
| 197 |
+
cleaned=orig;
|
| 198 |
+
if (match(cleaned,/(v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z\.\-]+)*)$/,m)) {
|
| 199 |
+
cleaned=m[1];
|
| 200 |
+
}
|
| 201 |
+
print cleaned"|"orig
|
| 202 |
+
}')
|
| 203 |
+
|
| 204 |
+
# Filter by pattern on the cleaned part and semver-sort to pick the highest; return original tag
|
| 205 |
+
local best
|
| 206 |
+
best=$(printf '%s\n' "$pairs" |
|
| 207 |
+
awk -F'|' -v pat="$pattern" '$1 ~ pat {print $0}' |
|
| 208 |
+
awk -F'|' '{ t=$1; sub(/^v/,"",t); print t"|"$2 }' |
|
| 209 |
+
sort -t '|' -k1,1V | tail -n1 | cut -d'|' -f2)
|
| 210 |
+
|
| 211 |
+
tag_name="$best"
|
| 212 |
+
fi
|
| 213 |
+
|
| 214 |
+
if [[ -z "$tag_name" ]]; then
|
| 215 |
+
print_warning "No matching pre-release found; falling back to latest stable release."
|
| 216 |
+
tag_name=$(get_latest_release)
|
| 217 |
+
fi
|
| 218 |
+
|
| 219 |
+
echo "$tag_name"
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
# Normalize version by removing a leading 'v'
|
| 223 |
+
normalize_version() {
|
| 224 |
+
local v="$1"
|
| 225 |
+
v="${v#v}"
|
| 226 |
+
echo "$v"
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
# Return 0 (true) if $1 < $2 in semantic version order, using sort -V
|
| 230 |
+
version_lt() {
|
| 231 |
+
local a b first
|
| 232 |
+
a=$(normalize_version "$1")
|
| 233 |
+
b=$(normalize_version "$2")
|
| 234 |
+
first=$(printf '%s\n' "$a" "$b" | sort -V | head -n1)
|
| 235 |
+
[[ "$first" == "$a" && "$a" != "$b" ]]
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
# Get asset download url for a given version and platform (e.g., darwin_arm64), prefer .zip
|
| 239 |
+
get_asset_download_url() {
|
| 240 |
+
local version=$1
|
| 241 |
+
local platform=$2
|
| 242 |
+
local url=""
|
| 243 |
+
|
| 244 |
+
print_info "Resolving asset download URL for ${version} (${platform})..."
|
| 245 |
+
debug "Querying ${GITHUB_API}/releases/tags/${version}"
|
| 246 |
+
if json=$(curl_gh "${GITHUB_API}/releases/tags/${version}" 2>/dev/null); then
|
| 247 |
+
if command -v jq >/dev/null 2>&1; then
|
| 248 |
+
debug "Assets on tag (names): $(echo "$json" | jq -r '.assets[]?.name' | tr '\n' ' ')"
|
| 249 |
+
url=$(echo "$json" | jq -r --arg platform "$platform" '.assets[]?.browser_download_url | select(test($platform)) | select(endswith(".zip"))' | head -n1)
|
| 250 |
+
else
|
| 251 |
+
url=$(echo "$json" \
|
| 252 |
+
| tr -d '\n\r\t' \
|
| 253 |
+
| sed -nE 's/.*("browser_download_url"[[[:space:]]]*:[[:space:]]*"[^"]+").*/\1/p' \
|
| 254 |
+
| sed -nE 's/.*"browser_download_url"[[[:space:]]]*:[[:space:]]*"([^"]+)".*/\1/p' \
|
| 255 |
+
| grep "$platform" \
|
| 256 |
+
| grep '\.zip$' -m 1)
|
| 257 |
+
fi
|
| 258 |
+
fi
|
| 259 |
+
debug "Matched asset URL from tag endpoint: ${url:-<none>}"
|
| 260 |
+
|
| 261 |
+
# Fallback to patterned URL if API failed or empty
|
| 262 |
+
if [[ -z "$url" ]]; then
|
| 263 |
+
print_warning "API failed or no asset matched; trying list endpoint..."
|
| 264 |
+
if json2=$(curl_gh "${GITHUB_API}/releases?per_page=100" 2>/dev/null); then
|
| 265 |
+
if command -v jq >/dev/null 2>&1; then
|
| 266 |
+
url=$(echo "$json2" | jq -r --arg tag "$version" --arg platform "$platform" '.[] | select(.tag_name==$tag) | .assets[]?.browser_download_url | select(test($platform)) | select(endswith(".zip"))' | head -n1)
|
| 267 |
+
else
|
| 268 |
+
url=$(echo "$json2" \
|
| 269 |
+
| tr -d '\n\r\t' \
|
| 270 |
+
| sed -nE 's/.*\{([^}]*)\}.*/\{\1\}/gp' \
|
| 271 |
+
| grep -E '"tag_name"[[:space:]]*:[[:space:]]*"'"$version"'"' \
|
| 272 |
+
| sed -nE 's/.*("browser_download_url"[[[:space:]]]*:[[:space:]]*"[^"]+").*/\1/p' \
|
| 273 |
+
| sed -nE 's/.*"browser_download_url"[[[:space:]]]*:[[:space:]]*"([^"]+)".*/\1/p' \
|
| 274 |
+
| grep "$platform" \
|
| 275 |
+
| grep '\.zip$' -m 1)
|
| 276 |
+
fi
|
| 277 |
+
fi
|
| 278 |
+
debug "Matched asset URL from list endpoint: ${url:-<none>}"
|
| 279 |
+
fi
|
| 280 |
+
|
| 281 |
+
if [[ -z "$url" ]]; then
|
| 282 |
+
print_warning "API failed or no asset matched; trying patterned URL..."
|
| 283 |
+
local clean_version="$version"
|
| 284 |
+
clean_version="${clean_version##*:}"
|
| 285 |
+
clean_version="${clean_version#v}"
|
| 286 |
+
local filename="axonhub_${clean_version}_${platform}.zip"
|
| 287 |
+
local candidate="https://github.com/${REPO}/releases/download/${version}/${filename}"
|
| 288 |
+
debug "Trying candidate URL: $candidate"
|
| 289 |
+
if curl -fsI "$candidate" >/dev/null 2>&1; then
|
| 290 |
+
url="$candidate"
|
| 291 |
+
fi
|
| 292 |
+
fi
|
| 293 |
+
|
| 294 |
+
if [[ -z "$url" ]]; then
|
| 295 |
+
print_error "Could not find a matching .zip asset for platform ${platform} in release ${version}"
|
| 296 |
+
exit 1
|
| 297 |
+
fi
|
| 298 |
+
echo "$url"
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
download_and_extract() {
|
| 302 |
+
local version=$1
|
| 303 |
+
local platform=$2
|
| 304 |
+
local temp_dir=$(mktemp -d)
|
| 305 |
+
|
| 306 |
+
# Resolve exact asset URL from GitHub API
|
| 307 |
+
local download_url
|
| 308 |
+
download_url=$(get_asset_download_url "$version" "$platform")
|
| 309 |
+
local filename
|
| 310 |
+
filename=$(basename "$download_url")
|
| 311 |
+
|
| 312 |
+
print_info "Downloading AxonHub ${version} for ${platform}..."
|
| 313 |
+
|
| 314 |
+
if ! curl -fSL -o "${temp_dir}/${filename}" "$download_url"; then
|
| 315 |
+
print_error "Failed to download AxonHub asset"
|
| 316 |
+
rm -rf "$temp_dir"
|
| 317 |
+
exit 1
|
| 318 |
+
fi
|
| 319 |
+
|
| 320 |
+
print_info "Extracting archive..."
|
| 321 |
+
|
| 322 |
+
if ! command -v unzip >/dev/null 2>&1; then
|
| 323 |
+
print_error "unzip command not found. Please install unzip and rerun."
|
| 324 |
+
rm -rf "$temp_dir"
|
| 325 |
+
exit 1
|
| 326 |
+
fi
|
| 327 |
+
|
| 328 |
+
if ! unzip -q "${temp_dir}/${filename}" -d "$temp_dir"; then
|
| 329 |
+
print_error "Failed to extract archive"
|
| 330 |
+
rm -rf "$temp_dir"
|
| 331 |
+
exit 1
|
| 332 |
+
fi
|
| 333 |
+
|
| 334 |
+
# Find the extracted binary
|
| 335 |
+
local binary_path
|
| 336 |
+
binary_path=$(find "$temp_dir" -name "axonhub" -type f | head -1)
|
| 337 |
+
|
| 338 |
+
if [[ -z "$binary_path" ]]; then
|
| 339 |
+
print_error "Could not find axonhub binary in archive"
|
| 340 |
+
rm -rf "$temp_dir"
|
| 341 |
+
exit 1
|
| 342 |
+
fi
|
| 343 |
+
|
| 344 |
+
echo "$binary_path"
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
create_user() {
|
| 348 |
+
# No system user management per requirements
|
| 349 |
+
print_info "Skipping system user creation"
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
setup_directories() {
|
| 353 |
+
print_info "Setting up directories..."
|
| 354 |
+
|
| 355 |
+
# Create directories
|
| 356 |
+
mkdir -p "$CONFIG_DIR" "$DATA_DIR" "$LOG_DIR"
|
| 357 |
+
|
| 358 |
+
# Set ownership and permissions to invoking user
|
| 359 |
+
local target_user="${SUDO_USER:-$USER}"
|
| 360 |
+
local target_group
|
| 361 |
+
target_group="$(id -gn "$target_user" 2>/dev/null || echo "$target_user")"
|
| 362 |
+
chown -R "$target_user:$target_group" "$CONFIG_DIR" "$DATA_DIR" "$LOG_DIR" 2>/dev/null || true
|
| 363 |
+
chmod 755 "$CONFIG_DIR" "$DATA_DIR" "$LOG_DIR"
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
install_binary() {
|
| 367 |
+
local binary_path=$1
|
| 368 |
+
|
| 369 |
+
print_info "Installing AxonHub binary to $INSTALL_DIR..."
|
| 370 |
+
|
| 371 |
+
# Install binary
|
| 372 |
+
cp "$binary_path" "$INSTALL_DIR/axonhub"
|
| 373 |
+
chmod +x "$INSTALL_DIR/axonhub"
|
| 374 |
+
|
| 375 |
+
# Clean up temp directory only if it looks like a system temp path
|
| 376 |
+
local dir
|
| 377 |
+
dir="$(dirname "$binary_path")"
|
| 378 |
+
local tmp1="${TMPDIR:-/tmp}"
|
| 379 |
+
if [[ "$dir" == /tmp/* || "$dir" == /var/folders/* || "$dir" == /private/var/folders/* || "$dir" == "$tmp1"* ]]; then
|
| 380 |
+
rm -rf "$dir" 2>/dev/null || true
|
| 381 |
+
fi
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
create_default_config() {
|
| 385 |
+
local config_file="$CONFIG_DIR/config.yml"
|
| 386 |
+
|
| 387 |
+
if [[ ! -f "$config_file" ]]; then
|
| 388 |
+
print_info "Creating default configuration..."
|
| 389 |
+
|
| 390 |
+
cat > "$config_file" << EOF
|
| 391 |
+
server:
|
| 392 |
+
port: 8090
|
| 393 |
+
name: "AxonHub"
|
| 394 |
+
debug: false
|
| 395 |
+
|
| 396 |
+
db:
|
| 397 |
+
dialect: "sqlite3"
|
| 398 |
+
dsn: "${BASE_DIR}/axonhub.db?cache=shared&_fk=1&journal_mode=WAL"
|
| 399 |
+
|
| 400 |
+
cache:
|
| 401 |
+
mode: "memory"
|
| 402 |
+
cache:
|
| 403 |
+
expiration: "5s"
|
| 404 |
+
cleanup_interval: "5s"
|
| 405 |
+
|
| 406 |
+
log:
|
| 407 |
+
level: "info"
|
| 408 |
+
encoding: "json"
|
| 409 |
+
output: "file"
|
| 410 |
+
file:
|
| 411 |
+
path: "${BASE_DIR}/logs/axonhub.log"
|
| 412 |
+
max_size: 100
|
| 413 |
+
max_age: 30
|
| 414 |
+
max_backups: 10
|
| 415 |
+
local_time: true
|
| 416 |
+
EOF
|
| 417 |
+
|
| 418 |
+
local target_user="${SUDO_USER:-$USER}"
|
| 419 |
+
local target_group
|
| 420 |
+
target_group="$(id -gn "$target_user" 2>/dev/null || echo "$target_user")"
|
| 421 |
+
chown "$target_user:$target_group" "$config_file" 2>/dev/null || true
|
| 422 |
+
chmod 644 "$config_file"
|
| 423 |
+
|
| 424 |
+
print_success "Default configuration created at $config_file"
|
| 425 |
+
else
|
| 426 |
+
print_info "Configuration file already exists at $config_file"
|
| 427 |
+
fi
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
# Note: systemd service installation removed; use deploy/start.sh and deploy/stop.sh to manage AxonHub
|
| 431 |
+
|
| 432 |
+
main() {
|
| 433 |
+
print_info "Starting AxonHub installation..."
|
| 434 |
+
|
| 435 |
+
# Check if running as root
|
| 436 |
+
check_root
|
| 437 |
+
|
| 438 |
+
# Detect system architecture
|
| 439 |
+
local platform
|
| 440 |
+
platform=$(detect_architecture)
|
| 441 |
+
print_info "Detected platform: $platform"
|
| 442 |
+
|
| 443 |
+
# Determine target version (env AXONHUB_VERSION, positional arg, or latest)
|
| 444 |
+
local version version_arg
|
| 445 |
+
version="${AXONHUB_VERSION:-}"
|
| 446 |
+
|
| 447 |
+
# Parse CLI flags and optional version argument
|
| 448 |
+
if [[ -z "$version" ]]; then
|
| 449 |
+
while [[ $# -gt 0 ]]; do
|
| 450 |
+
case "$1" in
|
| 451 |
+
-b|--beta)
|
| 452 |
+
INCLUDE_BETA="true"; shift ;;
|
| 453 |
+
-r|--rc)
|
| 454 |
+
INCLUDE_RC="true"; shift ;;
|
| 455 |
+
-v|--verbose)
|
| 456 |
+
VERBOSE="true"; shift ;;
|
| 457 |
+
-h|--help)
|
| 458 |
+
usage; exit 0 ;;
|
| 459 |
+
--)
|
| 460 |
+
shift; break ;;
|
| 461 |
+
-*)
|
| 462 |
+
print_error "Unknown option: $1"; usage; exit 1 ;;
|
| 463 |
+
*)
|
| 464 |
+
if [[ -z "${version_arg:-}" ]]; then
|
| 465 |
+
version_arg="$1"; shift
|
| 466 |
+
else
|
| 467 |
+
break
|
| 468 |
+
fi ;;
|
| 469 |
+
esac
|
| 470 |
+
done
|
| 471 |
+
version="${version_arg:-}"
|
| 472 |
+
fi
|
| 473 |
+
|
| 474 |
+
if [[ -z "$version" ]]; then
|
| 475 |
+
version=$(get_latest_version "$INCLUDE_BETA" "$INCLUDE_RC")
|
| 476 |
+
fi
|
| 477 |
+
print_info "Using version: $version"
|
| 478 |
+
|
| 479 |
+
# Prefer local binary near this script; offer to update if newer is available
|
| 480 |
+
local binary_path
|
| 481 |
+
local script_dir
|
| 482 |
+
script_dir=$(cd "$(dirname "$0")" && pwd)
|
| 483 |
+
if [[ -x "$script_dir/axonhub" ]]; then
|
| 484 |
+
print_info "Found local binary: $script_dir/axonhub"
|
| 485 |
+
# Try to read local version from the binary
|
| 486 |
+
local local_version norm_local norm_target
|
| 487 |
+
if local_version=$("$script_dir/axonhub" version 2>/dev/null | head -n1 | tr -d '\r'); then
|
| 488 |
+
print_info "Local binary version: $local_version"
|
| 489 |
+
norm_local=$(normalize_version "$local_version")
|
| 490 |
+
else
|
| 491 |
+
print_warning "Could not determine local binary version"
|
| 492 |
+
norm_local=""
|
| 493 |
+
fi
|
| 494 |
+
norm_target=$(normalize_version "$version")
|
| 495 |
+
|
| 496 |
+
if [[ -n "$norm_local" && "$norm_local" != "dev" ]] && version_lt "$norm_local" "$norm_target"; then
|
| 497 |
+
echo -n "A newer version is available (local ${local_version}, latest ${version}). Download the latest now? [Y/n]: " 1>&2
|
| 498 |
+
read -r reply
|
| 499 |
+
if [[ -z "$reply" || "$reply" =~ ^[Yy]$ ]]; then
|
| 500 |
+
binary_path=$(download_and_extract "$version" "$platform")
|
| 501 |
+
else
|
| 502 |
+
print_info "Using existing local binary as requested."
|
| 503 |
+
binary_path="$script_dir/axonhub"
|
| 504 |
+
fi
|
| 505 |
+
else
|
| 506 |
+
print_info "Local binary is up-to-date. Using existing local binary."
|
| 507 |
+
binary_path="$script_dir/axonhub"
|
| 508 |
+
fi
|
| 509 |
+
else
|
| 510 |
+
# Download and extract
|
| 511 |
+
binary_path=$(download_and_extract "$version" "$platform")
|
| 512 |
+
fi
|
| 513 |
+
|
| 514 |
+
# Create system user
|
| 515 |
+
create_user
|
| 516 |
+
|
| 517 |
+
# Setup directories
|
| 518 |
+
setup_directories
|
| 519 |
+
|
| 520 |
+
# Install binary
|
| 521 |
+
install_binary "$binary_path"
|
| 522 |
+
|
| 523 |
+
# Create default configuration
|
| 524 |
+
create_default_config
|
| 525 |
+
|
| 526 |
+
print_success "AxonHub installation completed!"
|
| 527 |
+
echo
|
| 528 |
+
|
| 529 |
+
# Get configured port for display
|
| 530 |
+
local port=8090
|
| 531 |
+
if [[ -x "$INSTALL_DIR/axonhub" ]]; then
|
| 532 |
+
local config_port
|
| 533 |
+
config_port=$("$INSTALL_DIR/axonhub" config get server.port 2>/dev/null) || true
|
| 534 |
+
if [[ -n "$config_port" && "$config_port" =~ ^[0-9]+$ ]]; then
|
| 535 |
+
port="$config_port"
|
| 536 |
+
fi
|
| 537 |
+
fi
|
| 538 |
+
|
| 539 |
+
print_info "Next steps:"
|
| 540 |
+
echo " 1. Edit configuration: nano $CONFIG_DIR/config.yml"
|
| 541 |
+
echo " 2. Start AxonHub: ./start.sh"
|
| 542 |
+
echo " 3. Stop AxonHub: ./stop.sh"
|
| 543 |
+
echo " 4. View logs: tail -f $LOG_DIR/axonhub.log"
|
| 544 |
+
echo " 5. Access web interface: http://localhost:${port}"
|
| 545 |
+
echo
|
| 546 |
+
print_info "To start AxonHub now, run: ./start.sh"
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
# Run main function
|
| 550 |
+
main "$@"
|
deploy/nginx.conf
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AxonHub Nginx 配置文件
|
| 2 |
+
# 用于 Docker 部署的反向代理配置
|
| 3 |
+
|
| 4 |
+
user nginx;
|
| 5 |
+
worker_processes auto;
|
| 6 |
+
error_log /var/log/nginx/error.log warn;
|
| 7 |
+
pid /var/run/nginx.pid;
|
| 8 |
+
|
| 9 |
+
events {
|
| 10 |
+
worker_connections 1024;
|
| 11 |
+
use epoll;
|
| 12 |
+
multi_accept on;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
http {
|
| 16 |
+
include /etc/nginx/mime.types;
|
| 17 |
+
default_type application/octet-stream;
|
| 18 |
+
|
| 19 |
+
# 日志格式
|
| 20 |
+
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
| 21 |
+
'$status $body_bytes_sent "$http_referer" '
|
| 22 |
+
'"$http_user_agent" "$http_x_forwarded_for" '
|
| 23 |
+
'rt=$request_time uct="$upstream_connect_time" '
|
| 24 |
+
'uht="$upstream_header_time" urt="$upstream_response_time"';
|
| 25 |
+
|
| 26 |
+
access_log /var/log/nginx/access.log main;
|
| 27 |
+
|
| 28 |
+
# 基础配置
|
| 29 |
+
sendfile on;
|
| 30 |
+
tcp_nopush on;
|
| 31 |
+
tcp_nodelay on;
|
| 32 |
+
keepalive_timeout 65;
|
| 33 |
+
types_hash_max_size 2048;
|
| 34 |
+
server_tokens off;
|
| 35 |
+
|
| 36 |
+
# Gzip 压缩
|
| 37 |
+
gzip on;
|
| 38 |
+
gzip_vary on;
|
| 39 |
+
gzip_proxied any;
|
| 40 |
+
gzip_comp_level 6;
|
| 41 |
+
gzip_types
|
| 42 |
+
text/plain
|
| 43 |
+
text/css
|
| 44 |
+
text/xml
|
| 45 |
+
text/javascript
|
| 46 |
+
application/json
|
| 47 |
+
application/javascript
|
| 48 |
+
application/xml+rss
|
| 49 |
+
application/atom+xml
|
| 50 |
+
image/svg+xml;
|
| 51 |
+
|
| 52 |
+
# 上游服务器配置
|
| 53 |
+
upstream axonhub_backend {
|
| 54 |
+
server axonhub:8090;
|
| 55 |
+
keepalive 32;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
# 限流配置
|
| 59 |
+
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
| 60 |
+
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
|
| 61 |
+
|
| 62 |
+
# HTTP 服务器(重定向到 HTTPS)
|
| 63 |
+
server {
|
| 64 |
+
listen 80;
|
| 65 |
+
server_name _;
|
| 66 |
+
|
| 67 |
+
# 健康检查端点
|
| 68 |
+
location /health {
|
| 69 |
+
access_log off;
|
| 70 |
+
return 200 "healthy\n";
|
| 71 |
+
add_header Content-Type text/plain;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
# 重定向到 HTTPS
|
| 75 |
+
location / {
|
| 76 |
+
return 301 https://$host$request_uri;
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
# HTTPS 服务器
|
| 81 |
+
server {
|
| 82 |
+
listen 443 ssl http2;
|
| 83 |
+
server_name _;
|
| 84 |
+
|
| 85 |
+
# SSL 配置
|
| 86 |
+
ssl_certificate /etc/nginx/ssl/cert.pem;
|
| 87 |
+
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
| 88 |
+
|
| 89 |
+
# SSL 安全配置
|
| 90 |
+
ssl_protocols TLSv1.2 TLSv1.3;
|
| 91 |
+
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
|
| 92 |
+
ssl_prefer_server_ciphers off;
|
| 93 |
+
ssl_session_cache shared:SSL:10m;
|
| 94 |
+
ssl_session_timeout 10m;
|
| 95 |
+
|
| 96 |
+
# 安全头
|
| 97 |
+
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
| 98 |
+
add_header X-Frame-Options DENY always;
|
| 99 |
+
add_header X-Content-Type-Options nosniff always;
|
| 100 |
+
add_header X-XSS-Protection "1; mode=block" always;
|
| 101 |
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
| 102 |
+
|
| 103 |
+
# 主要代理配置
|
| 104 |
+
location / {
|
| 105 |
+
# 限流
|
| 106 |
+
limit_req zone=api burst=20 nodelay;
|
| 107 |
+
|
| 108 |
+
# 代理设置
|
| 109 |
+
proxy_pass http://axonhub_backend;
|
| 110 |
+
proxy_http_version 1.1;
|
| 111 |
+
proxy_set_header Upgrade $http_upgrade;
|
| 112 |
+
proxy_set_header Connection "upgrade";
|
| 113 |
+
proxy_set_header Host $host;
|
| 114 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 115 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 116 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 117 |
+
proxy_set_header X-Forwarded-Host $host;
|
| 118 |
+
proxy_set_header X-Forwarded-Port $server_port;
|
| 119 |
+
|
| 120 |
+
# 超时设置
|
| 121 |
+
proxy_connect_timeout 60s;
|
| 122 |
+
proxy_send_timeout 60s;
|
| 123 |
+
proxy_read_timeout 60s;
|
| 124 |
+
|
| 125 |
+
# 缓冲设置
|
| 126 |
+
proxy_buffering on;
|
| 127 |
+
proxy_buffer_size 4k;
|
| 128 |
+
proxy_buffers 8 4k;
|
| 129 |
+
proxy_busy_buffers_size 8k;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
# API 端点特殊配置
|
| 133 |
+
location /v1/ {
|
| 134 |
+
# API 限流
|
| 135 |
+
limit_req zone=api burst=50 nodelay;
|
| 136 |
+
|
| 137 |
+
proxy_pass http://axonhub_backend;
|
| 138 |
+
proxy_http_version 1.1;
|
| 139 |
+
proxy_set_header Connection "";
|
| 140 |
+
proxy_set_header Host $host;
|
| 141 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 142 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 143 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 144 |
+
|
| 145 |
+
# 长时间请求支持(AI 生成可能需要更长时间)
|
| 146 |
+
proxy_connect_timeout 120s;
|
| 147 |
+
proxy_send_timeout 120s;
|
| 148 |
+
proxy_read_timeout 600s;
|
| 149 |
+
|
| 150 |
+
# 禁用缓冲以支持流式响应
|
| 151 |
+
proxy_buffering off;
|
| 152 |
+
proxy_cache off;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
# GraphQL 端点
|
| 156 |
+
location /query {
|
| 157 |
+
limit_req zone=api burst=30 nodelay;
|
| 158 |
+
|
| 159 |
+
proxy_pass http://axonhub_backend;
|
| 160 |
+
proxy_http_version 1.1;
|
| 161 |
+
proxy_set_header Connection "";
|
| 162 |
+
proxy_set_header Host $host;
|
| 163 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 164 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 165 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 166 |
+
|
| 167 |
+
# GraphQL 通常需要较长的处理时间
|
| 168 |
+
proxy_connect_timeout 60s;
|
| 169 |
+
proxy_send_timeout 60s;
|
| 170 |
+
proxy_read_timeout 300s;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
# 登录端点限流
|
| 174 |
+
location /auth/login {
|
| 175 |
+
limit_req zone=login burst=5 nodelay;
|
| 176 |
+
|
| 177 |
+
proxy_pass http://axonhub_backend;
|
| 178 |
+
proxy_http_version 1.1;
|
| 179 |
+
proxy_set_header Connection "";
|
| 180 |
+
proxy_set_header Host $host;
|
| 181 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 182 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 183 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
# 静态文件缓存
|
| 187 |
+
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
| 188 |
+
proxy_pass http://axonhub_backend;
|
| 189 |
+
proxy_set_header Host $host;
|
| 190 |
+
|
| 191 |
+
# 缓存设置
|
| 192 |
+
expires 1y;
|
| 193 |
+
add_header Cache-Control "public, immutable";
|
| 194 |
+
add_header Vary Accept-Encoding;
|
| 195 |
+
|
| 196 |
+
# 压缩
|
| 197 |
+
gzip_static on;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
# 健康检查
|
| 201 |
+
location /health {
|
| 202 |
+
access_log off;
|
| 203 |
+
proxy_pass http://axonhub_backend/health;
|
| 204 |
+
proxy_set_header Host $host;
|
| 205 |
+
|
| 206 |
+
# 快速超时
|
| 207 |
+
proxy_connect_timeout 5s;
|
| 208 |
+
proxy_send_timeout 5s;
|
| 209 |
+
proxy_read_timeout 5s;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
# 监控端点(可选,需要认证)
|
| 213 |
+
location /metrics {
|
| 214 |
+
# 限制访问
|
| 215 |
+
allow 127.0.0.1;
|
| 216 |
+
allow 10.0.0.0/8;
|
| 217 |
+
allow 172.16.0.0/12;
|
| 218 |
+
allow 192.168.0.0/16;
|
| 219 |
+
deny all;
|
| 220 |
+
|
| 221 |
+
proxy_pass http://axonhub_backend/metrics;
|
| 222 |
+
proxy_set_header Host $host;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
# 禁止访问敏感文件
|
| 226 |
+
location ~ /\. {
|
| 227 |
+
deny all;
|
| 228 |
+
access_log off;
|
| 229 |
+
log_not_found off;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
location ~ \.(yml|yaml|json|env)$ {
|
| 233 |
+
deny all;
|
| 234 |
+
access_log off;
|
| 235 |
+
log_not_found off;
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
}
|
deploy/start.bat
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
|
| 4 |
+
REM AxonHub Windows start wrapper (.bat)
|
| 5 |
+
|
| 6 |
+
where powershell >NUL 2>NUL
|
| 7 |
+
if %ERRORLEVEL% NEQ 0 (
|
| 8 |
+
echo [ERROR] PowerShell is required to run this script.
|
| 9 |
+
exit /b 1
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
set "SCRIPT_DIR=%~dp0"
|
| 13 |
+
powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%start.ps1" %*
|
| 14 |
+
exit /b %ERRORLEVEL%
|
deploy/start.ps1
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param(
|
| 2 |
+
[Parameter(ValueFromRemainingArguments=$true)]
|
| 3 |
+
[string[]]$ArgsFromCmd
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
$ErrorActionPreference = 'Stop'
|
| 7 |
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
| 8 |
+
|
| 9 |
+
function Write-Info([string]$m){ Write-Host "[INFO] $m" -ForegroundColor Cyan }
|
| 10 |
+
function Write-Success([string]$m){ Write-Host "[SUCCESS] $m" -ForegroundColor Green }
|
| 11 |
+
function Write-Warn([string]$m){ Write-Host "[WARNING] $m" -ForegroundColor Yellow }
|
| 12 |
+
function Write-Err([string]$m){ Write-Host "[ERROR] $m" -ForegroundColor Red }
|
| 13 |
+
|
| 14 |
+
$ServiceName = 'axonhub'
|
| 15 |
+
$BaseDir = Join-Path $env:LOCALAPPDATA 'AxonHub'
|
| 16 |
+
$ConfigFile = Join-Path $BaseDir 'config.yml'
|
| 17 |
+
$BinaryPath = Join-Path $BaseDir 'axonhub.exe'
|
| 18 |
+
$PidFile = Join-Path $BaseDir 'axonhub.pid'
|
| 19 |
+
$LogFile = Join-Path $BaseDir 'axonhub.log'
|
| 20 |
+
$DefaultPort = 8090
|
| 21 |
+
|
| 22 |
+
function Show-Usage {
|
| 23 |
+
Write-Host @"
|
| 24 |
+
Usage: start.bat
|
| 25 |
+
|
| 26 |
+
This script starts AxonHub directly (no service manager).
|
| 27 |
+
Logs: $LogFile
|
| 28 |
+
PID file: $PidFile
|
| 29 |
+
"@
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
foreach($a in $ArgsFromCmd){
|
| 33 |
+
switch -Regex ($a){
|
| 34 |
+
'^(--help|-h)$' { Show-Usage; exit 0 }
|
| 35 |
+
default { Write-Warn "Unknown option: $a" }
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
function Ensure-Dirs([string]$path){ if(-not (Test-Path $path)){ New-Item -ItemType Directory -Force -Path $path | Out-Null } }
|
| 40 |
+
|
| 41 |
+
function Get-ConfiguredPort {
|
| 42 |
+
$port = $DefaultPort
|
| 43 |
+
if(Test-Path $BinaryPath){
|
| 44 |
+
try {
|
| 45 |
+
$configPort = $null
|
| 46 |
+
$configPort = & $BinaryPath config get server.port 2>$null
|
| 47 |
+
if($configPort -match '^[0-9]+$'){
|
| 48 |
+
$port = [int]$configPort
|
| 49 |
+
}
|
| 50 |
+
} catch {}
|
| 51 |
+
}
|
| 52 |
+
return $port
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
function Check-Port([int]$port){
|
| 56 |
+
try {
|
| 57 |
+
$lines = netstat -ano | Select-String -Pattern "^\s*TCP\s+[^:]+:$port\s+" -ErrorAction SilentlyContinue
|
| 58 |
+
if($lines){
|
| 59 |
+
Write-Warn "Port $port is already in use"
|
| 60 |
+
Write-Info "Processes using port ${port}:"
|
| 61 |
+
$lines | ForEach-Object { $_.Line } | Write-Host
|
| 62 |
+
return $false
|
| 63 |
+
}
|
| 64 |
+
} catch {}
|
| 65 |
+
return $true
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
$stdoutTempFile = Join-Path $env:TEMP ("axonhub-" + [guid]::NewGuid().ToString() + '-stdout.log')
|
| 69 |
+
$stderrTempFile = Join-Path $env:TEMP ("axonhub-" + [guid]::NewGuid().ToString() + '-stderr.log')
|
| 70 |
+
|
| 71 |
+
Write-Info 'Starting AxonHub...'
|
| 72 |
+
|
| 73 |
+
if(-not (Test-Path $BinaryPath)){
|
| 74 |
+
Write-Err "AxonHub binary not found at $BinaryPath"
|
| 75 |
+
Write-Info 'Please run the installer first: install.bat'
|
| 76 |
+
exit 1
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
Ensure-Dirs $BaseDir
|
| 80 |
+
|
| 81 |
+
# Already running?
|
| 82 |
+
if(Test-Path $PidFile){
|
| 83 |
+
try {
|
| 84 |
+
$pid = Get-Content -Path $PidFile -ErrorAction Stop
|
| 85 |
+
if($pid -and (Get-Process -Id $pid -ErrorAction SilentlyContinue)){
|
| 86 |
+
Write-Warn "AxonHub is already running (PID: $pid)"
|
| 87 |
+
exit 0
|
| 88 |
+
} else {
|
| 89 |
+
Write-Info 'Removing stale PID file'
|
| 90 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 91 |
+
}
|
| 92 |
+
} catch {
|
| 93 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
# Check configured port
|
| 98 |
+
$port = Get-ConfiguredPort
|
| 99 |
+
if(-not (Check-Port $port)){
|
| 100 |
+
Write-Err "Cannot start AxonHub: port $port is already in use"
|
| 101 |
+
exit 1
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
$ConfigArgs = @()
|
| 105 |
+
if(Test-Path $ConfigFile){
|
| 106 |
+
# Config exists, binary will auto-detect it from $HOME/.config/axonhub/
|
| 107 |
+
Write-Info "Configuration found at $ConfigFile, binary will auto-detect it"
|
| 108 |
+
} else {
|
| 109 |
+
Write-Warn "Configuration not found at $ConfigFile, starting with defaults"
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
Write-Info 'Starting AxonHub process...'
|
| 113 |
+
try {
|
| 114 |
+
if($ConfigArgs.Count -gt 0){
|
| 115 |
+
$p = Start-Process -FilePath $BinaryPath -ArgumentList $ConfigArgs -RedirectStandardOutput $stdoutTempFile -RedirectStandardError $stderrTempFile -PassThru -WindowStyle Hidden
|
| 116 |
+
} else {
|
| 117 |
+
$p = Start-Process -FilePath $BinaryPath -RedirectStandardOutput $stdoutTempFile -RedirectStandardError $stderrTempFile -PassThru -WindowStyle Hidden
|
| 118 |
+
}
|
| 119 |
+
Start-Sleep -Seconds 2
|
| 120 |
+
if($p -and (Get-Process -Id $p.Id -ErrorAction SilentlyContinue)){
|
| 121 |
+
$p.Id | Out-File -FilePath $PidFile -Encoding ascii -Force
|
| 122 |
+
Remove-Item -Force $stdoutTempFile,$stderrTempFile -ErrorAction SilentlyContinue
|
| 123 |
+
Write-Success "AxonHub started successfully (PID: $($p.Id))"
|
| 124 |
+
Write-Info 'Process information:'
|
| 125 |
+
Write-Host " • PID: $($p.Id)"
|
| 126 |
+
Write-Host " • Log file: $LogFile"
|
| 127 |
+
Write-Host " • Config: " -NoNewline; if(Test-Path $ConfigFile){ Write-Host $ConfigFile } else { Write-Host 'default' }
|
| 128 |
+
Write-Host " • Web interface: http://localhost:$port"
|
| 129 |
+
Write-Host ''
|
| 130 |
+
Write-Info 'To stop AxonHub: stop.bat'
|
| 131 |
+
Write-Info "To view logs: Get-Content -Path '$LogFile' -Tail 100 -Wait"
|
| 132 |
+
} else {
|
| 133 |
+
Write-Err 'AxonHub failed to start'
|
| 134 |
+
if(Test-Path $LogFile){
|
| 135 |
+
Write-Info 'Last few log lines:'
|
| 136 |
+
Get-Content -Path $LogFile -Tail 20
|
| 137 |
+
}
|
| 138 |
+
if(Test-Path $stdoutTempFile){
|
| 139 |
+
Write-Info 'Captured stdout:'
|
| 140 |
+
Get-Content -Path $stdoutTempFile -Tail 20
|
| 141 |
+
}
|
| 142 |
+
if(Test-Path $stderrTempFile){
|
| 143 |
+
Write-Info 'Captured stderr:'
|
| 144 |
+
Get-Content -Path $stderrTempFile -Tail 20
|
| 145 |
+
}
|
| 146 |
+
if(Test-Path $PidFile){ Remove-Item -Force $PidFile -ErrorAction SilentlyContinue }
|
| 147 |
+
Remove-Item -Force $stdoutTempFile,$stderrTempFile -ErrorAction SilentlyContinue
|
| 148 |
+
exit 1
|
| 149 |
+
}
|
| 150 |
+
} catch {
|
| 151 |
+
Write-Err $_.Exception.Message
|
| 152 |
+
if(Test-Path $PidFile){ Remove-Item -Force $PidFile -ErrorAction SilentlyContinue }
|
| 153 |
+
Remove-Item -Force $stdoutTempFile,$stderrTempFile -ErrorAction SilentlyContinue
|
| 154 |
+
exit 1
|
| 155 |
+
}
|
deploy/start.sh
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# AxonHub Start Script
|
| 4 |
+
# This script starts AxonHub directly (no systemd), with proper error handling and logging
|
| 5 |
+
|
| 6 |
+
set -e
|
| 7 |
+
|
| 8 |
+
# Colors for output
|
| 9 |
+
RED='\033[0;31m'
|
| 10 |
+
GREEN='\033[0;32m'
|
| 11 |
+
YELLOW='\033[1;33m'
|
| 12 |
+
BLUE='\033[0;34m'
|
| 13 |
+
NC='\033[0m' # No Color
|
| 14 |
+
|
| 15 |
+
# Configuration
|
| 16 |
+
SERVICE_NAME="axonhub"
|
| 17 |
+
# Resolve non-root user's HOME when running via sudo
|
| 18 |
+
if [[ -n "$SUDO_USER" && "$SUDO_USER" != "root" ]]; then
|
| 19 |
+
USER_HOME="$(eval echo ~${SUDO_USER})"
|
| 20 |
+
TARGET_USER="$SUDO_USER"
|
| 21 |
+
else
|
| 22 |
+
USER_HOME="$HOME"
|
| 23 |
+
TARGET_USER="$USER"
|
| 24 |
+
fi
|
| 25 |
+
TARGET_GROUP="$(id -gn "$TARGET_USER" 2>/dev/null || echo "$TARGET_USER")"
|
| 26 |
+
BASE_DIR="${USER_HOME}/.config/axonhub"
|
| 27 |
+
CONFIG_FILE="${BASE_DIR}/config.yml"
|
| 28 |
+
BINARY_PATH="/usr/local/bin/axonhub"
|
| 29 |
+
DEFAULT_PORT=8090
|
| 30 |
+
PID_FILE="${BASE_DIR}/axonhub.pid"
|
| 31 |
+
LOG_FILE="${BASE_DIR}/axonhub.log"
|
| 32 |
+
|
| 33 |
+
print_info() {
|
| 34 |
+
echo -e "${BLUE}[INFO]${NC} $1"
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
print_success() {
|
| 38 |
+
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
print_warning() {
|
| 42 |
+
echo -e "${YELLOW}[WARNING]${NC} $1"
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
print_error() {
|
| 46 |
+
echo -e "${RED}[ERROR]${NC} $1"
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
# Note: systemd-related logic removed for simplicity; this script always starts directly
|
| 50 |
+
|
| 51 |
+
start_directly() {
|
| 52 |
+
print_info "Starting AxonHub directly..."
|
| 53 |
+
|
| 54 |
+
# Check if already running
|
| 55 |
+
if [[ -f "$PID_FILE" ]]; then
|
| 56 |
+
local pid=$(cat "$PID_FILE")
|
| 57 |
+
if kill -0 "$pid" 2>/dev/null; then
|
| 58 |
+
print_warning "AxonHub is already running (PID: $pid)"
|
| 59 |
+
return 0
|
| 60 |
+
else
|
| 61 |
+
print_info "Removing stale PID file"
|
| 62 |
+
rm -f "$PID_FILE"
|
| 63 |
+
fi
|
| 64 |
+
fi
|
| 65 |
+
|
| 66 |
+
# Check if binary exists
|
| 67 |
+
if [[ ! -x "$BINARY_PATH" ]]; then
|
| 68 |
+
print_error "AxonHub binary not found at $BINARY_PATH"
|
| 69 |
+
print_info "Please run the install script first: ./deploy/install.sh"
|
| 70 |
+
return 1
|
| 71 |
+
fi
|
| 72 |
+
|
| 73 |
+
# Check if config exists
|
| 74 |
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
| 75 |
+
print_warning "Configuration file not found at $CONFIG_FILE"
|
| 76 |
+
print_info "Starting with default configuration..."
|
| 77 |
+
CONFIG_ARGS=""
|
| 78 |
+
else
|
| 79 |
+
# Config exists, binary will auto-detect it from $HOME/.config/axonhub/
|
| 80 |
+
CONFIG_ARGS=""
|
| 81 |
+
fi
|
| 82 |
+
|
| 83 |
+
# Ensure base directory exists and is owned by target user
|
| 84 |
+
mkdir -p "$BASE_DIR"
|
| 85 |
+
chown "$TARGET_USER:$TARGET_GROUP" "$BASE_DIR" 2>/dev/null || true
|
| 86 |
+
|
| 87 |
+
# Start AxonHub in background
|
| 88 |
+
print_info "Starting AxonHub process..."
|
| 89 |
+
|
| 90 |
+
if [[ $EUID -eq 0 ]]; then
|
| 91 |
+
# Running with sudo/root; start as invoking user so files live under their HOME
|
| 92 |
+
print_info "Running as user: $TARGET_USER"
|
| 93 |
+
sudo -u "$TARGET_USER" bash -c "mkdir -p '$BASE_DIR'; \"$BINARY_PATH\" $CONFIG_ARGS >> '$LOG_FILE' 2>&1 & echo \$! > '$PID_FILE'"
|
| 94 |
+
local pid
|
| 95 |
+
pid=$(cat "$PID_FILE" 2>/dev/null || true)
|
| 96 |
+
else
|
| 97 |
+
"$BINARY_PATH" $CONFIG_ARGS > "$LOG_FILE" 2>&1 &
|
| 98 |
+
local pid=$!
|
| 99 |
+
echo "$pid" > "$PID_FILE"
|
| 100 |
+
fi
|
| 101 |
+
|
| 102 |
+
# Wait a moment and check if process is still running
|
| 103 |
+
sleep 2
|
| 104 |
+
|
| 105 |
+
if kill -0 "$pid" 2>/dev/null; then
|
| 106 |
+
print_success "AxonHub started successfully (PID: $pid)"
|
| 107 |
+
print_info "Process information:"
|
| 108 |
+
echo " • PID: $pid"
|
| 109 |
+
echo " • Log file: $LOG_FILE"
|
| 110 |
+
echo " • Config: ${CONFIG_FILE:-"default"}"
|
| 111 |
+
local port
|
| 112 |
+
port=$(get_configured_port)
|
| 113 |
+
echo " • Web interface: http://localhost:${port}"
|
| 114 |
+
echo
|
| 115 |
+
print_info "To stop AxonHub: ./stop.sh"
|
| 116 |
+
print_info "To view logs: tail -f $LOG_FILE"
|
| 117 |
+
else
|
| 118 |
+
print_error "AxonHub failed to start"
|
| 119 |
+
if [[ -f "$LOG_FILE" ]]; then
|
| 120 |
+
print_info "Last few log lines:"
|
| 121 |
+
tail -n 10 "$LOG_FILE"
|
| 122 |
+
fi
|
| 123 |
+
rm -f "$PID_FILE"
|
| 124 |
+
return 1
|
| 125 |
+
fi
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
get_configured_port() {
|
| 129 |
+
local port="$DEFAULT_PORT"
|
| 130 |
+
|
| 131 |
+
# Try to get port from config using axonhub binary
|
| 132 |
+
if [[ -x "$BINARY_PATH" ]]; then
|
| 133 |
+
local config_port
|
| 134 |
+
config_port=$("$BINARY_PATH" config get server.port 2>/dev/null) || true
|
| 135 |
+
if [[ -n "$config_port" && "$config_port" =~ ^[0-9]+$ ]]; then
|
| 136 |
+
port="$config_port"
|
| 137 |
+
fi
|
| 138 |
+
fi
|
| 139 |
+
|
| 140 |
+
echo "$port"
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
check_port() {
|
| 144 |
+
local port=${1:-$DEFAULT_PORT}
|
| 145 |
+
|
| 146 |
+
if command -v netstat >/dev/null 2>&1; then
|
| 147 |
+
if netstat -tuln | grep -q ":$port "; then
|
| 148 |
+
print_warning "Port $port is already in use"
|
| 149 |
+
print_info "Processes using port $port:"
|
| 150 |
+
netstat -tulnp | grep ":$port " || true
|
| 151 |
+
return 1
|
| 152 |
+
fi
|
| 153 |
+
elif command -v ss >/dev/null 2>&1; then
|
| 154 |
+
if ss -tuln | grep -q ":$port "; then
|
| 155 |
+
print_warning "Port $port is already in use"
|
| 156 |
+
print_info "Processes using port $port:"
|
| 157 |
+
ss -tulnp | grep ":$port " || true
|
| 158 |
+
return 1
|
| 159 |
+
fi
|
| 160 |
+
fi
|
| 161 |
+
|
| 162 |
+
return 0
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
main() {
|
| 166 |
+
print_info "Starting AxonHub..."
|
| 167 |
+
|
| 168 |
+
# Get configured port
|
| 169 |
+
local port
|
| 170 |
+
port=$(get_configured_port)
|
| 171 |
+
|
| 172 |
+
# Check if port is available
|
| 173 |
+
if ! check_port "$port"; then
|
| 174 |
+
print_error "Cannot start AxonHub: port $port is already in use"
|
| 175 |
+
return 1
|
| 176 |
+
fi
|
| 177 |
+
|
| 178 |
+
# Always start directly
|
| 179 |
+
start_directly
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
# Handle script arguments
|
| 183 |
+
case "${1:-}" in
|
| 184 |
+
--help|-h)
|
| 185 |
+
echo "Usage: $0"
|
| 186 |
+
echo
|
| 187 |
+
echo "This script starts AxonHub directly (no systemd)."
|
| 188 |
+
echo "Logs: $LOG_FILE"
|
| 189 |
+
echo "PID file: $PID_FILE"
|
| 190 |
+
exit 0
|
| 191 |
+
;;
|
| 192 |
+
"")
|
| 193 |
+
main
|
| 194 |
+
;;
|
| 195 |
+
*)
|
| 196 |
+
print_error "Unknown option: $1"
|
| 197 |
+
print_info "Use --help for usage information"
|
| 198 |
+
exit 1
|
| 199 |
+
;;
|
| 200 |
+
esac
|
deploy/stop.bat
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
|
| 4 |
+
REM AxonHub Windows stop wrapper (.bat)
|
| 5 |
+
|
| 6 |
+
where powershell >NUL 2>NUL
|
| 7 |
+
if %ERRORLEVEL% NEQ 0 (
|
| 8 |
+
echo [ERROR] PowerShell is required to run this script.
|
| 9 |
+
exit /b 1
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
set "SCRIPT_DIR=%~dp0"
|
| 13 |
+
powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%stop.ps1" %*
|
| 14 |
+
exit /b %ERRORLEVEL%
|
deploy/stop.ps1
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param(
|
| 2 |
+
[Parameter(ValueFromRemainingArguments=$true)]
|
| 3 |
+
[string[]]$ArgsFromCmd
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
$ErrorActionPreference = 'Stop'
|
| 7 |
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
| 8 |
+
|
| 9 |
+
function Write-Info([string]$m){ Write-Host "[INFO] $m" -ForegroundColor Cyan }
|
| 10 |
+
function Write-Success([string]$m){ Write-Host "[SUCCESS] $m" -ForegroundColor Green }
|
| 11 |
+
function Write-Warn([string]$m){ Write-Host "[WARNING] $m" -ForegroundColor Yellow }
|
| 12 |
+
function Write-Err([string]$m){ Write-Host "[ERROR] $m" -ForegroundColor Red }
|
| 13 |
+
|
| 14 |
+
$ServiceName = 'axonhub'
|
| 15 |
+
$BaseDir = Join-Path $env:LOCALAPPDATA 'AxonHub'
|
| 16 |
+
$PidFile = Join-Path $BaseDir 'axonhub.pid'
|
| 17 |
+
$ProcessName = 'axonhub'
|
| 18 |
+
|
| 19 |
+
function Show-Usage {
|
| 20 |
+
Write-Host @"
|
| 21 |
+
Usage: stop.bat [--force]
|
| 22 |
+
|
| 23 |
+
This script stops AxonHub directly (no service manager).
|
| 24 |
+
Options:
|
| 25 |
+
--force Force kill all AxonHub processes
|
| 26 |
+
--help, -h Show this help message
|
| 27 |
+
"@
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
$Force = $false
|
| 31 |
+
foreach($a in $ArgsFromCmd){
|
| 32 |
+
switch -Regex ($a){
|
| 33 |
+
'^(--force)$' { $Force = $true; continue }
|
| 34 |
+
'^(--help|-h)$' { Show-Usage; exit 0 }
|
| 35 |
+
default { Write-Warn "Unknown option: $a" }
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
function Stop-ByPid(){
|
| 40 |
+
Write-Info 'Stopping AxonHub using PID file...'
|
| 41 |
+
if(-not (Test-Path $PidFile)){
|
| 42 |
+
Write-Warn "PID file not found at $PidFile"
|
| 43 |
+
return $false
|
| 44 |
+
}
|
| 45 |
+
try {
|
| 46 |
+
$pid = Get-Content -Path $PidFile -ErrorAction Stop
|
| 47 |
+
} catch {
|
| 48 |
+
Write-Warn 'Unable to read PID file'
|
| 49 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 50 |
+
return $false
|
| 51 |
+
}
|
| 52 |
+
if(-not ($pid -match '^[0-9]+$')){
|
| 53 |
+
Write-Err "Invalid PID in file: $pid"
|
| 54 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 55 |
+
return $false
|
| 56 |
+
}
|
| 57 |
+
$proc = Get-Process -Id $pid -ErrorAction SilentlyContinue
|
| 58 |
+
if(-not $proc){
|
| 59 |
+
Write-Warn "Process with PID $pid is not running"
|
| 60 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 61 |
+
return $false
|
| 62 |
+
}
|
| 63 |
+
Write-Info "Stopping process $pid ..."
|
| 64 |
+
try { Stop-Process -Id $pid -ErrorAction SilentlyContinue } catch {}
|
| 65 |
+
# Wait up to 10 seconds
|
| 66 |
+
$timeout = 10
|
| 67 |
+
for($i=0; $i -lt $timeout; $i++){
|
| 68 |
+
Start-Sleep -Seconds 1
|
| 69 |
+
if(-not (Get-Process -Id $pid -ErrorAction SilentlyContinue)){ break }
|
| 70 |
+
}
|
| 71 |
+
if(Get-Process -Id $pid -ErrorAction SilentlyContinue){
|
| 72 |
+
Write-Warn 'Process did not stop gracefully, forcing termination...'
|
| 73 |
+
try { Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue } catch {}
|
| 74 |
+
Start-Sleep -Seconds 2
|
| 75 |
+
}
|
| 76 |
+
if(-not (Get-Process -Id $pid -ErrorAction SilentlyContinue)){
|
| 77 |
+
Write-Success "AxonHub stopped successfully (PID: $pid)"
|
| 78 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 79 |
+
return $true
|
| 80 |
+
} else {
|
| 81 |
+
Write-Err 'Failed to stop AxonHub process'
|
| 82 |
+
return $false
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
function Stop-ByProcessName(){
|
| 87 |
+
Write-Info 'Stopping AxonHub by process name...'
|
| 88 |
+
$procs = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match '^axonhub' }
|
| 89 |
+
if(-not $procs){
|
| 90 |
+
Write-Warn 'No AxonHub processes found'
|
| 91 |
+
return $false
|
| 92 |
+
}
|
| 93 |
+
Write-Info ("Found AxonHub processes: " + ($procs.Id -join ' '))
|
| 94 |
+
foreach($p in $procs){
|
| 95 |
+
Write-Info ("Stopping process " + $p.Id + ' ...')
|
| 96 |
+
try { Stop-Process -Id $p.Id -ErrorAction SilentlyContinue } catch {}
|
| 97 |
+
# Wait up to 10 seconds each
|
| 98 |
+
$timeout = 10
|
| 99 |
+
for($i=0; $i -lt $timeout; $i++){
|
| 100 |
+
Start-Sleep -Seconds 1
|
| 101 |
+
if(-not (Get-Process -Id $p.Id -ErrorAction SilentlyContinue)){ break }
|
| 102 |
+
}
|
| 103 |
+
if(Get-Process -Id $p.Id -ErrorAction SilentlyContinue){
|
| 104 |
+
Write-Warn ("Process " + $p.Id + ' did not stop gracefully, forcing...')
|
| 105 |
+
try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {}
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
Start-Sleep -Seconds 2
|
| 109 |
+
$remaining = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match '^axonhub' }
|
| 110 |
+
if(-not $remaining){
|
| 111 |
+
Write-Success 'All AxonHub processes stopped successfully'
|
| 112 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 113 |
+
return $true
|
| 114 |
+
} else {
|
| 115 |
+
Write-Err ("Some AxonHub processes are still running: " + ($remaining.Id -join ' '))
|
| 116 |
+
return $false
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
function Check-Running(){
|
| 121 |
+
$procs = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match '^axonhub' }
|
| 122 |
+
if($procs){
|
| 123 |
+
Write-Info 'Running AxonHub processes:'
|
| 124 |
+
$procs | Select-Object Id,ProcessName,Path | Format-Table -AutoSize | Out-String | Write-Host
|
| 125 |
+
return $true
|
| 126 |
+
}
|
| 127 |
+
return $false
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
if($Force){
|
| 131 |
+
Write-Info 'Force stopping all AxonHub processes...'
|
| 132 |
+
$procs = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match '^axonhub' }
|
| 133 |
+
if($procs){
|
| 134 |
+
foreach($p in $procs){ try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {} }
|
| 135 |
+
Start-Sleep -Seconds 2
|
| 136 |
+
$still = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match '^axonhub' }
|
| 137 |
+
if(-not $still){
|
| 138 |
+
Write-Success 'All AxonHub processes force-stopped'
|
| 139 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 140 |
+
} else {
|
| 141 |
+
Write-Err 'Failed to force-stop some processes'
|
| 142 |
+
exit 1
|
| 143 |
+
}
|
| 144 |
+
} else {
|
| 145 |
+
Write-Info 'No AxonHub processes found'
|
| 146 |
+
}
|
| 147 |
+
exit 0
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
Write-Info 'Stopping AxonHub...'
|
| 151 |
+
$stopped = $false
|
| 152 |
+
if(Stop-ByPid){ $stopped = $true }
|
| 153 |
+
if(-not $stopped){ if(Stop-ByProcessName){ $stopped = $true } }
|
| 154 |
+
if(-not $stopped){
|
| 155 |
+
if(Check-Running){
|
| 156 |
+
Write-Err 'Failed to stop all AxonHub processes'
|
| 157 |
+
exit 1
|
| 158 |
+
} else {
|
| 159 |
+
Write-Info 'No AxonHub processes were running'
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
Remove-Item -Force $PidFile -ErrorAction SilentlyContinue
|
| 163 |
+
Write-Success 'AxonHub has been stopped'
|
deploy/stop.sh
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# AxonHub Stop Script
|
| 4 |
+
# This script stops AxonHub directly (no systemd), with proper error handling
|
| 5 |
+
|
| 6 |
+
set -e
|
| 7 |
+
|
| 8 |
+
# Colors for output
|
| 9 |
+
RED='\033[0;31m'
|
| 10 |
+
GREEN='\033[0;32m'
|
| 11 |
+
YELLOW='\033[1;33m'
|
| 12 |
+
BLUE='\033[0;34m'
|
| 13 |
+
NC='\033[0m' # No Color
|
| 14 |
+
|
| 15 |
+
# Configuration
|
| 16 |
+
SERVICE_NAME="axonhub"
|
| 17 |
+
# Resolve non-root user's HOME when running via sudo
|
| 18 |
+
if [[ -n "$SUDO_USER" && "$SUDO_USER" != "root" ]]; then
|
| 19 |
+
USER_HOME="$(eval echo ~${SUDO_USER})"
|
| 20 |
+
else
|
| 21 |
+
USER_HOME="$HOME"
|
| 22 |
+
fi
|
| 23 |
+
BASE_DIR="${USER_HOME}/.config/axonhub"
|
| 24 |
+
PID_FILE="${BASE_DIR}/axonhub.pid"
|
| 25 |
+
PROCESS_NAME="axonhub"
|
| 26 |
+
|
| 27 |
+
print_info() {
|
| 28 |
+
echo -e "${BLUE}[INFO]${NC} $1"
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
print_success() {
|
| 32 |
+
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
print_warning() {
|
| 36 |
+
echo -e "${YELLOW}[WARNING]${NC} $1"
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
print_error() {
|
| 40 |
+
echo -e "${RED}[ERROR]${NC} $1"
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
# Note: systemd-related logic removed for simplicity; this script always stops directly
|
| 44 |
+
|
| 45 |
+
stop_by_pid() {
|
| 46 |
+
print_info "Stopping AxonHub using PID file..."
|
| 47 |
+
|
| 48 |
+
if [[ ! -f "$PID_FILE" ]]; then
|
| 49 |
+
print_warning "PID file not found at $PID_FILE"
|
| 50 |
+
return 1
|
| 51 |
+
fi
|
| 52 |
+
|
| 53 |
+
local pid=$(cat "$PID_FILE")
|
| 54 |
+
|
| 55 |
+
if [[ ! "$pid" =~ ^[0-9]+$ ]]; then
|
| 56 |
+
print_error "Invalid PID in file: $pid"
|
| 57 |
+
rm -f "$PID_FILE"
|
| 58 |
+
return 1
|
| 59 |
+
fi
|
| 60 |
+
|
| 61 |
+
if ! kill -0 "$pid" 2>/dev/null; then
|
| 62 |
+
print_warning "Process with PID $pid is not running"
|
| 63 |
+
rm -f "$PID_FILE"
|
| 64 |
+
return 1
|
| 65 |
+
fi
|
| 66 |
+
|
| 67 |
+
print_info "Sending SIGTERM to process $pid..."
|
| 68 |
+
if kill -TERM "$pid" 2>/dev/null; then
|
| 69 |
+
# Wait for graceful shutdown
|
| 70 |
+
local timeout=10
|
| 71 |
+
local count=0
|
| 72 |
+
|
| 73 |
+
while kill -0 "$pid" 2>/dev/null && [[ $count -lt $timeout ]]; do
|
| 74 |
+
sleep 1
|
| 75 |
+
((count++))
|
| 76 |
+
done
|
| 77 |
+
|
| 78 |
+
if kill -0 "$pid" 2>/dev/null; then
|
| 79 |
+
print_warning "Process did not stop gracefully, sending SIGKILL..."
|
| 80 |
+
kill -KILL "$pid" 2>/dev/null || true
|
| 81 |
+
sleep 2
|
| 82 |
+
fi
|
| 83 |
+
|
| 84 |
+
if ! kill -0 "$pid" 2>/dev/null; then
|
| 85 |
+
print_success "AxonHub stopped successfully (PID: $pid)"
|
| 86 |
+
rm -f "$PID_FILE"
|
| 87 |
+
else
|
| 88 |
+
print_error "Failed to stop AxonHub process"
|
| 89 |
+
return 1
|
| 90 |
+
fi
|
| 91 |
+
else
|
| 92 |
+
print_error "Failed to send signal to process $pid"
|
| 93 |
+
return 1
|
| 94 |
+
fi
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
stop_by_process_name() {
|
| 98 |
+
print_info "Stopping AxonHub by process name..."
|
| 99 |
+
|
| 100 |
+
local pids
|
| 101 |
+
pids=$(pgrep -f "$PROCESS_NAME" 2>/dev/null || true)
|
| 102 |
+
|
| 103 |
+
if [[ -z "$pids" ]]; then
|
| 104 |
+
print_warning "No AxonHub processes found"
|
| 105 |
+
return 1
|
| 106 |
+
fi
|
| 107 |
+
|
| 108 |
+
print_info "Found AxonHub processes: $pids"
|
| 109 |
+
|
| 110 |
+
for pid in $pids; do
|
| 111 |
+
print_info "Stopping process $pid..."
|
| 112 |
+
|
| 113 |
+
if kill -TERM "$pid" 2>/dev/null; then
|
| 114 |
+
# Wait for graceful shutdown
|
| 115 |
+
local timeout=10
|
| 116 |
+
local count=0
|
| 117 |
+
|
| 118 |
+
while kill -0 "$pid" 2>/dev/null && [[ $count -lt $timeout ]]; do
|
| 119 |
+
sleep 1
|
| 120 |
+
((count++))
|
| 121 |
+
done
|
| 122 |
+
|
| 123 |
+
if kill -0 "$pid" 2>/dev/null; then
|
| 124 |
+
print_warning "Process $pid did not stop gracefully, sending SIGKILL..."
|
| 125 |
+
kill -KILL "$pid" 2>/dev/null || true
|
| 126 |
+
fi
|
| 127 |
+
fi
|
| 128 |
+
done
|
| 129 |
+
|
| 130 |
+
sleep 2
|
| 131 |
+
|
| 132 |
+
# Check if any processes are still running
|
| 133 |
+
local remaining_pids
|
| 134 |
+
remaining_pids=$(pgrep -f "$PROCESS_NAME" 2>/dev/null || true)
|
| 135 |
+
|
| 136 |
+
if [[ -z "$remaining_pids" ]]; then
|
| 137 |
+
print_success "All AxonHub processes stopped successfully"
|
| 138 |
+
rm -f "$PID_FILE"
|
| 139 |
+
else
|
| 140 |
+
print_error "Some AxonHub processes are still running: $remaining_pids"
|
| 141 |
+
return 1
|
| 142 |
+
fi
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
check_running_processes() {
|
| 146 |
+
local pids
|
| 147 |
+
pids=$(pgrep -f "$PROCESS_NAME" 2>/dev/null || true)
|
| 148 |
+
|
| 149 |
+
if [[ -n "$pids" ]]; then
|
| 150 |
+
print_info "Running AxonHub processes:"
|
| 151 |
+
ps -p $pids -o pid,ppid,cmd --no-headers 2>/dev/null || true
|
| 152 |
+
return 0
|
| 153 |
+
else
|
| 154 |
+
return 1
|
| 155 |
+
fi
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
main() {
|
| 159 |
+
print_info "Stopping AxonHub..."
|
| 160 |
+
|
| 161 |
+
local stopped=false
|
| 162 |
+
|
| 163 |
+
# Try PID file first
|
| 164 |
+
if stop_by_pid; then
|
| 165 |
+
stopped=true
|
| 166 |
+
fi
|
| 167 |
+
|
| 168 |
+
# If PID file method didn't work, try by process name
|
| 169 |
+
if [[ "$stopped" != true ]]; then
|
| 170 |
+
if stop_by_process_name; then
|
| 171 |
+
stopped=true
|
| 172 |
+
fi
|
| 173 |
+
fi
|
| 174 |
+
|
| 175 |
+
# Final check
|
| 176 |
+
if [[ "$stopped" != true ]]; then
|
| 177 |
+
if check_running_processes; then
|
| 178 |
+
print_error "Failed to stop all AxonHub processes"
|
| 179 |
+
return 1
|
| 180 |
+
else
|
| 181 |
+
print_info "No AxonHub processes were running"
|
| 182 |
+
fi
|
| 183 |
+
fi
|
| 184 |
+
|
| 185 |
+
# Clean up PID file
|
| 186 |
+
rm -f "$PID_FILE"
|
| 187 |
+
|
| 188 |
+
print_success "AxonHub has been stopped"
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
# Handle script arguments
|
| 192 |
+
case "${1:-}" in
|
| 193 |
+
--force)
|
| 194 |
+
print_info "Force stopping all AxonHub processes..."
|
| 195 |
+
if check_running_processes; then
|
| 196 |
+
pkill -KILL -f "$PROCESS_NAME" 2>/dev/null || true
|
| 197 |
+
sleep 2
|
| 198 |
+
if ! check_running_processes; then
|
| 199 |
+
print_success "All AxonHub processes force-stopped"
|
| 200 |
+
rm -f "$PID_FILE"
|
| 201 |
+
else
|
| 202 |
+
print_error "Failed to force-stop some processes"
|
| 203 |
+
exit 1
|
| 204 |
+
fi
|
| 205 |
+
else
|
| 206 |
+
print_info "No AxonHub processes found"
|
| 207 |
+
fi
|
| 208 |
+
;;
|
| 209 |
+
--help|-h)
|
| 210 |
+
echo "Usage: $0 [--force]"
|
| 211 |
+
echo
|
| 212 |
+
echo "This script stops AxonHub directly (no systemd)."
|
| 213 |
+
echo "Options:"
|
| 214 |
+
echo " --force Force kill all AxonHub processes"
|
| 215 |
+
echo " --help, -h Show this help message"
|
| 216 |
+
exit 0
|
| 217 |
+
;;
|
| 218 |
+
"")
|
| 219 |
+
main
|
| 220 |
+
;;
|
| 221 |
+
*)
|
| 222 |
+
print_error "Unknown option: $1"
|
| 223 |
+
print_info "Use --help for usage information"
|
| 224 |
+
exit 1
|
| 225 |
+
;;
|
| 226 |
+
esac
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AxonHub Docker Compose Configuration
|
| 2 |
+
#
|
| 3 |
+
# CONFIGURATION OPTIONS:
|
| 4 |
+
# 1. 环境变量 - 适用于敏感配置 (如数据库DSN、密码等),优先级高于配置文件
|
| 5 |
+
# 参考: https://github.com/looplj/axonhub/blob/main/docs/deployment/configuration.md#configuration-priority
|
| 6 |
+
#
|
| 7 |
+
# 2. 配置文件 - 适用于复杂配置 (如缓存、日志、监控等)
|
| 8 |
+
# 创建 config.yml 文件并取消 volumes 配置的注释
|
| 9 |
+
# 配置参考: https://github.com/looplj/axonhub/blob/main/docs/deployment/configuration.md
|
| 10 |
+
#
|
| 11 |
+
# 3. 简单部署 - 如果不需要复杂配置,可以注释掉 config 的 volumes 配置
|
| 12 |
+
# 仅使用环境变量进行基本配置即可运行
|
| 13 |
+
|
| 14 |
+
services:
|
| 15 |
+
# PostgreSQL 数据库
|
| 16 |
+
postgres:
|
| 17 |
+
image: postgres:16-alpine
|
| 18 |
+
container_name: axonhub-postgres
|
| 19 |
+
environment:
|
| 20 |
+
POSTGRES_DB: axonhub
|
| 21 |
+
POSTGRES_USER: axonhub
|
| 22 |
+
POSTGRES_PASSWORD: ${DB_PASSWORD:-axonhub_password}
|
| 23 |
+
volumes:
|
| 24 |
+
- postgres_data:/var/lib/postgresql/data
|
| 25 |
+
ports:
|
| 26 |
+
- "5432:5432"
|
| 27 |
+
networks:
|
| 28 |
+
- axonhub-network
|
| 29 |
+
restart: unless-stopped
|
| 30 |
+
healthcheck:
|
| 31 |
+
test: ["CMD-SHELL", "pg_isready -U axonhub"]
|
| 32 |
+
interval: 10s
|
| 33 |
+
timeout: 5s
|
| 34 |
+
retries: 5
|
| 35 |
+
|
| 36 |
+
# AxonHub 主服务
|
| 37 |
+
axonhub:
|
| 38 |
+
image: looplj/axonhub:latest
|
| 39 |
+
container_name: axonhub-app
|
| 40 |
+
environment:
|
| 41 |
+
# 数据库配置可以通过环境变量设置,优先级高于配置文件
|
| 42 |
+
AXONHUB_DB_DIALECT: postgres
|
| 43 |
+
AXONHUB_DB_DSN: postgres://axonhub:${DB_PASSWORD:-axonhub_password}@postgres:5432/axonhub?sslmode=disable
|
| 44 |
+
ports:
|
| 45 |
+
- "8090:8090"
|
| 46 |
+
volumes:
|
| 47 |
+
# 配置文件挂载 - 用于复杂配置,如缓存、日志、监控等
|
| 48 |
+
# 参考: https://github.com/looplj/axonhub/blob/main/docs/deployment/configuration.md
|
| 49 |
+
- ./config.yml:/app/config.yml:ro
|
| 50 |
+
# - axonhub_data:/data
|
| 51 |
+
networks:
|
| 52 |
+
- axonhub-network
|
| 53 |
+
depends_on:
|
| 54 |
+
postgres:
|
| 55 |
+
condition: service_healthy
|
| 56 |
+
restart: unless-stopped
|
| 57 |
+
healthcheck:
|
| 58 |
+
test:
|
| 59 |
+
[
|
| 60 |
+
"CMD",
|
| 61 |
+
"wget",
|
| 62 |
+
"--no-verbose",
|
| 63 |
+
"--tries=1",
|
| 64 |
+
"--spider",
|
| 65 |
+
"http://localhost:8090/health",
|
| 66 |
+
]
|
| 67 |
+
interval: 30s
|
| 68 |
+
timeout: 10s
|
| 69 |
+
retries: 3
|
| 70 |
+
start_period: 40s
|
| 71 |
+
|
| 72 |
+
# SQLite 配置示例 (取消注释以使用 SQLite)
|
| 73 |
+
# 注意: 此服务与上面的 'axonhub' 服务冲突。请只启用其中一个。
|
| 74 |
+
# 先执行
|
| 75 |
+
# mkdir data
|
| 76 |
+
# chmod 777 data
|
| 77 |
+
# axonhub-sqlite:
|
| 78 |
+
# image: looplj/axonhub:latest
|
| 79 |
+
# container_name: axonhub-sqlite-app
|
| 80 |
+
# environment:
|
| 81 |
+
# AXONHUB_DB_DIALECT: sqlite3
|
| 82 |
+
# AXONHUB_DB_DSN: file:/data/axonhub.db?cache=shared&_fk=1
|
| 83 |
+
# ports:
|
| 84 |
+
# - "8090:8090"
|
| 85 |
+
# volumes:
|
| 86 |
+
# # 配置文件挂载 - 用于复杂配置
|
| 87 |
+
# # 参考: https://github.com/looplj/axonhub/blob/main/docs/deployment/configuration.md
|
| 88 |
+
# - ./config.yml:/app/config.yml:ro
|
| 89 |
+
# - ./data:/data
|
| 90 |
+
# restart: unless-stopped
|
| 91 |
+
# healthcheck:
|
| 92 |
+
# test:
|
| 93 |
+
# [
|
| 94 |
+
# "CMD",
|
| 95 |
+
# "wget",
|
| 96 |
+
# "--no-verbose",
|
| 97 |
+
# "--tries=1",
|
| 98 |
+
# "--spider",
|
| 99 |
+
# "http://localhost:8090/health",
|
| 100 |
+
# ]
|
| 101 |
+
# interval: 30s
|
| 102 |
+
# timeout: 10s
|
| 103 |
+
# retries: 3
|
| 104 |
+
# start_period: 40s
|
| 105 |
+
|
| 106 |
+
# MySQL 配置示例 (取消注释以使用 MySQL)
|
| 107 |
+
# 注意: 此服务与上面的 'axonhub' 服务冲突。请只启用其中一个。
|
| 108 |
+
# mysql:
|
| 109 |
+
# image: mysql:8.0
|
| 110 |
+
# container_name: axonhub-mysql
|
| 111 |
+
# environment:
|
| 112 |
+
# MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-axonhub_root_password}
|
| 113 |
+
# MYSQL_DATABASE: axonhub
|
| 114 |
+
# MYSQL_USER: axonhub
|
| 115 |
+
# MYSQL_PASSWORD: ${MYSQL_PASSWORD:-axonhub_password}
|
| 116 |
+
# volumes:
|
| 117 |
+
# - mysql_data:/var/lib/mysql
|
| 118 |
+
# ports:
|
| 119 |
+
# - "3306:3306"
|
| 120 |
+
# networks:
|
| 121 |
+
# - axonhub-network
|
| 122 |
+
# restart: unless-stopped
|
| 123 |
+
# healthcheck:
|
| 124 |
+
# test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-axonhub_root_password}"]
|
| 125 |
+
# interval: 10s
|
| 126 |
+
# timeout: 5s
|
| 127 |
+
# retries: 5
|
| 128 |
+
#
|
| 129 |
+
# # AxonHub 主服务 (MySQL 版本)
|
| 130 |
+
# # 注意: 取消注释此服务时,请注释掉上面的 PostgreSQL 版本 axonhub 服务
|
| 131 |
+
# axonhub-mysql:
|
| 132 |
+
# image: looplj/axonhub:latest
|
| 133 |
+
# container_name: axonhub-mysql-app
|
| 134 |
+
# environment:
|
| 135 |
+
# AXONHUB_DB_DIALECT: mysql
|
| 136 |
+
# # DSN 格式参考: ${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(mysql:3306)/${MYSQL_DATABASE}?charset=utf8mb4&parseTime=True&loc=Local
|
| 137 |
+
# AXONHUB_DB_DSN: axonhub:${MYSQL_PASSWORD:-axonhub_password}@tcp(mysql:3306)/axonhub?charset=utf8mb4&parseTime=True&loc=Local
|
| 138 |
+
# ports:
|
| 139 |
+
# - "8090:8090"
|
| 140 |
+
# volumes:
|
| 141 |
+
# # 配置文件挂载 - 用于复杂配置
|
| 142 |
+
# # 参考: https://github.com/looplj/axonhub/blob/main/docs/deployment/configuration.md
|
| 143 |
+
# - ./config.yml:/app/config.yml:ro
|
| 144 |
+
# # - axonhub_data:/data
|
| 145 |
+
# networks:
|
| 146 |
+
# - axonhub-network
|
| 147 |
+
# depends_on:
|
| 148 |
+
# mysql:
|
| 149 |
+
# condition: service_healthy
|
| 150 |
+
# restart: unless-stopped
|
| 151 |
+
# healthcheck:
|
| 152 |
+
# test:
|
| 153 |
+
# [
|
| 154 |
+
# "CMD",
|
| 155 |
+
# "wget",
|
| 156 |
+
# "--no-verbose",
|
| 157 |
+
# "--tries=1",
|
| 158 |
+
# "--spider",
|
| 159 |
+
# "http://localhost:8090/health",
|
| 160 |
+
# ]
|
| 161 |
+
# interval: 30s
|
| 162 |
+
# timeout: 10s
|
| 163 |
+
# retries: 3
|
| 164 |
+
# start_period: 40s
|
| 165 |
+
|
| 166 |
+
volumes:
|
| 167 |
+
postgres_data:
|
| 168 |
+
mysql_data:
|
| 169 |
+
|
| 170 |
+
networks:
|
| 171 |
+
axonhub-network:
|
| 172 |
+
driver: bridge
|
docs/axonhub-architecture-light.svg
ADDED
|
|
docs/axonhub-architecture.svg
ADDED
|
|
docs/en/api-reference/anthropic-api.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Anthropic API Reference
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
AxonHub supports the native Anthropic Messages API for applications that prefer Anthropic's specific features and response format. You can use the Anthropic SDK to access not only Claude models but also OpenAI, Gemini, and other supported models.
|
| 6 |
+
|
| 7 |
+
## Key Benefits
|
| 8 |
+
|
| 9 |
+
- **API Interoperability**: Use Anthropic Messages API to call OpenAI, Gemini, and other supported models
|
| 10 |
+
- **Zero Code Changes**: Continue using your existing Anthropic client SDK without modification
|
| 11 |
+
- **Automatic Translation**: AxonHub automatically converts between API formats when needed
|
| 12 |
+
- **Provider Flexibility**: Access any supported AI provider using the Anthropic API format
|
| 13 |
+
|
| 14 |
+
## Supported Endpoints
|
| 15 |
+
|
| 16 |
+
**Endpoints:**
|
| 17 |
+
- `POST /anthropic/v1/messages` - Text generation
|
| 18 |
+
- `POST /v1/messages` - Text generation (alternative)
|
| 19 |
+
- `GET /anthropic/v1/models` - List available models
|
| 20 |
+
|
| 21 |
+
**Example Request:**
|
| 22 |
+
```go
|
| 23 |
+
import (
|
| 24 |
+
"github.com/anthropics/anthropic-sdk-go"
|
| 25 |
+
"github.com/anthropics/anthropic-sdk-go/option"
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
// Create Anthropic client with AxonHub configuration
|
| 29 |
+
client := anthropic.NewClient(
|
| 30 |
+
option.WithAPIKey("your-axonhub-api-key"),
|
| 31 |
+
option.WithBaseURL("http://localhost:8090/anthropic"),
|
| 32 |
+
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
// Call OpenAI model using Anthropic API format
|
| 36 |
+
messages := []anthropic.MessageParam{
|
| 37 |
+
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello, GPT!")),
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
response, err := client.Messages.New(ctx, anthropic.MessageNewParams{
|
| 41 |
+
Model: anthropic.Model("gpt-4o"),
|
| 42 |
+
Messages: messages,
|
| 43 |
+
MaxTokens: 1024,
|
| 44 |
+
})
|
| 45 |
+
if err != nil {
|
| 46 |
+
// Handle error appropriately
|
| 47 |
+
panic(err)
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// Extract text content from response
|
| 51 |
+
responseText := ""
|
| 52 |
+
for _, block := range response.Content {
|
| 53 |
+
if textBlock := block.AsText(); textBlock != nil {
|
| 54 |
+
responseText += textBlock.Text
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
fmt.Println(responseText)
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
## API Translation Capabilities
|
| 61 |
+
|
| 62 |
+
AxonHub automatically translates between API formats, enabling powerful scenarios:
|
| 63 |
+
|
| 64 |
+
### Use Anthropic SDK with OpenAI Models
|
| 65 |
+
```go
|
| 66 |
+
// Anthropic SDK calling OpenAI model
|
| 67 |
+
messages := []anthropic.MessageParam{
|
| 68 |
+
anthropic.NewUserMessage(anthropic.NewTextBlock("What is machine learning?")),
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
response, err := client.Messages.New(ctx, anthropic.MessageNewParams{
|
| 72 |
+
Model: anthropic.Model("gpt-4o"), // OpenAI model
|
| 73 |
+
Messages: messages,
|
| 74 |
+
MaxTokens: 1024,
|
| 75 |
+
})
|
| 76 |
+
|
| 77 |
+
// Access response
|
| 78 |
+
for _, block := range response.Content {
|
| 79 |
+
if textBlock := block.AsText(); textBlock != nil {
|
| 80 |
+
fmt.Println(textBlock.Text)
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
// AxonHub automatically translates Anthropic format → OpenAI format
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
### Use Anthropic SDK with Gemini Models
|
| 87 |
+
```go
|
| 88 |
+
// Anthropic SDK calling Gemini model
|
| 89 |
+
messages := []anthropic.MessageParam{
|
| 90 |
+
anthropic.NewUserMessage(anthropic.NewTextBlock("Explain quantum computing")),
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
response, err := client.Messages.New(ctx, anthropic.MessageNewParams{
|
| 94 |
+
Model: anthropic.Model("gemini-2.5"), // Gemini model
|
| 95 |
+
Messages: messages,
|
| 96 |
+
MaxTokens: 1024,
|
| 97 |
+
})
|
| 98 |
+
|
| 99 |
+
// Access response
|
| 100 |
+
for _, block := range response.Content {
|
| 101 |
+
if textBlock := block.AsText(); textBlock != nil {
|
| 102 |
+
fmt.Println(textBlock.Text)
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
// AxonHub automatically translates Anthropic format → Gemini format
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
## Authentication
|
| 109 |
+
|
| 110 |
+
The Anthropic API format uses the following authentication:
|
| 111 |
+
|
| 112 |
+
- **Header**: `X-API-Key: <your-api-key>`
|
| 113 |
+
|
| 114 |
+
The API keys are managed through AxonHub's API Key management system and provide the same permissions regardless of which API format you use.
|
| 115 |
+
|
| 116 |
+
## Streaming Support
|
| 117 |
+
|
| 118 |
+
Anthropic API format supports streaming responses:
|
| 119 |
+
|
| 120 |
+
```go
|
| 121 |
+
// Anthropic SDK streaming
|
| 122 |
+
messages := []anthropic.MessageParam{
|
| 123 |
+
anthropic.NewUserMessage(anthropic.NewTextBlock("Count to five")),
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
stream := client.Messages.NewStreaming(ctx, anthropic.MessageNewParams{
|
| 127 |
+
Model: anthropic.Model("gpt-4o"),
|
| 128 |
+
Messages: messages,
|
| 129 |
+
MaxTokens: 1024,
|
| 130 |
+
})
|
| 131 |
+
|
| 132 |
+
// Collect streamed content
|
| 133 |
+
var content string
|
| 134 |
+
for stream.Next() {
|
| 135 |
+
event := stream.Current()
|
| 136 |
+
switch event := event.(type) {
|
| 137 |
+
case anthropic.ContentBlockDeltaEvent:
|
| 138 |
+
if event.Type == "content_block_delta" {
|
| 139 |
+
content += event.Delta.Text
|
| 140 |
+
fmt.Print(event.Delta.Text) // Print as it streams
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
if err := stream.Err(); err != nil {
|
| 146 |
+
panic(err)
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
fmt.Println("\nComplete response:", content)
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
## Error Handling
|
| 153 |
+
|
| 154 |
+
Anthropic format error responses:
|
| 155 |
+
|
| 156 |
+
```json
|
| 157 |
+
{
|
| 158 |
+
"type": "error",
|
| 159 |
+
"error": {
|
| 160 |
+
"type": "invalid_request_error",
|
| 161 |
+
"message": "Invalid API key"
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
## Tool Support
|
| 167 |
+
|
| 168 |
+
AxonHub supports **function tools** (custom function calling) through the Anthropic API format. However, provider-specific tools are **not supported**:
|
| 169 |
+
|
| 170 |
+
| Tool Type | Support Status | Notes |
|
| 171 |
+
| --------- | -------------- | ----- |
|
| 172 |
+
| **Function Tools** | �� Supported | Custom function definitions work across all providers |
|
| 173 |
+
| **Web Search** | ❌ Not Supported | Provider-specific (OpenAI, Anthropic, etc.) |
|
| 174 |
+
| **Code Interpreter** | ❌ Not Supported | Provider-specific (OpenAI, Anthropic, etc.) |
|
| 175 |
+
| **File Search** | ❌ Not Supported | Provider-specific |
|
| 176 |
+
| **Computer Use** | ❌ Not Supported | Anthropic-specific |
|
| 177 |
+
|
| 178 |
+
> **Note**: Only generic function tools that can be translated across providers are supported. Provider-specific tools like web search, code interpreter, and computer use require direct access to the provider's infrastructure and cannot be proxied through AxonHub.
|
| 179 |
+
|
| 180 |
+
## Best Practices
|
| 181 |
+
|
| 182 |
+
1. **Use Tracing Headers**: Include `AH-Trace-Id` and `AH-Thread-Id` headers for better observability
|
| 183 |
+
2. **Model Selection**: Specify the target model explicitly in your requests
|
| 184 |
+
3. **Error Handling**: Implement proper error handling for API responses
|
| 185 |
+
4. **Streaming**: Use streaming for better user experience with long responses
|
| 186 |
+
5. **Use Function Tools**: For tool calling, use generic function tools instead of provider-specific tools
|
| 187 |
+
|
| 188 |
+
## Migration Guide
|
| 189 |
+
|
| 190 |
+
### From Anthropic to AxonHub
|
| 191 |
+
```go
|
| 192 |
+
// Before: Direct Anthropic
|
| 193 |
+
client := anthropic.NewClient(
|
| 194 |
+
option.WithAPIKey("anthropic-key"),
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
// After: AxonHub with Anthropic API
|
| 198 |
+
client := anthropic.NewClient(
|
| 199 |
+
option.WithAPIKey("axonhub-api-key"),
|
| 200 |
+
option.WithBaseURL("http://localhost:8090/anthropic"),
|
| 201 |
+
)
|
| 202 |
+
// Your existing code continues to work!
|
| 203 |
+
```
|
docs/en/api-reference/embedding-api.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Embedding API Reference
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
AxonHub provides comprehensive support for text and multimodal embedding generation through OpenAI-compatible and Jina AI-specific APIs.
|
| 6 |
+
|
| 7 |
+
## Key Benefits
|
| 8 |
+
|
| 9 |
+
- **OpenAI Compatibility**: Use existing OpenAI SDKs without modification
|
| 10 |
+
- **Jina AI Support**: Native Jina embedding format support for specialized use cases
|
| 11 |
+
- **Multiple Input Types**: Support for single text, text arrays, token arrays, and multiple token arrays
|
| 12 |
+
- **Flexible Output Formats**: Choose between float arrays or base64-encoded embeddings
|
| 13 |
+
|
| 14 |
+
## Supported Endpoints
|
| 15 |
+
|
| 16 |
+
**Endpoints:**
|
| 17 |
+
- `POST /v1/embeddings` - OpenAI-compatible embedding API
|
| 18 |
+
- `POST /jina/v1/embeddings` - Jina AI-specific embedding API
|
| 19 |
+
|
| 20 |
+
## Request Format
|
| 21 |
+
|
| 22 |
+
```json
|
| 23 |
+
{
|
| 24 |
+
"input": "The text to embed",
|
| 25 |
+
"model": "text-embedding-3-small",
|
| 26 |
+
"encoding_format": "float",
|
| 27 |
+
"dimensions": 1536,
|
| 28 |
+
"user": "user-id"
|
| 29 |
+
}
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
**Parameters:**
|
| 33 |
+
|
| 34 |
+
| Parameter | Type | Required | Description |
|
| 35 |
+
|-----------|------|----------|-------------|
|
| 36 |
+
| `input` | string \| string[] \| number[] \| number[][] | ✅ | The text(s) to embed. Can be a single string, array of strings, token array, or multiple token arrays. |
|
| 37 |
+
| `model` | string | ✅ | The model to use for embedding generation. |
|
| 38 |
+
| `encoding_format` | string | ❌ | Format to return embeddings in. Either `float` or `base64`. Default: `float`. |
|
| 39 |
+
| `dimensions` | integer | ❌ | Number of dimensions for the output embeddings. |
|
| 40 |
+
| `user` | string | ❌ | Unique identifier for the end-user. |
|
| 41 |
+
|
| 42 |
+
**Jina-Specific Parameters:**
|
| 43 |
+
|
| 44 |
+
| Parameter | Type | Required | Description |
|
| 45 |
+
|-----------|------|----------|-------------|
|
| 46 |
+
| `task` | string | ❌ | Task type for Jina embeddings. Options: `text-matching`, `retrieval.query`, `retrieval.passage`, `separation`, `classification`, `none`. |
|
| 47 |
+
|
| 48 |
+
## Response Format
|
| 49 |
+
|
| 50 |
+
```json
|
| 51 |
+
{
|
| 52 |
+
"object": "list",
|
| 53 |
+
"data": [
|
| 54 |
+
{
|
| 55 |
+
"object": "embedding",
|
| 56 |
+
"embedding": [0.123, 0.456, ...],
|
| 57 |
+
"index": 0
|
| 58 |
+
}
|
| 59 |
+
],
|
| 60 |
+
"model": "text-embedding-3-small",
|
| 61 |
+
"usage": {
|
| 62 |
+
"prompt_tokens": 4,
|
| 63 |
+
"total_tokens": 4
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
## Examples
|
| 69 |
+
|
| 70 |
+
### OpenAI SDK (Python)
|
| 71 |
+
|
| 72 |
+
```python
|
| 73 |
+
import openai
|
| 74 |
+
|
| 75 |
+
client = openai.OpenAI(
|
| 76 |
+
api_key="your-axonhub-api-key",
|
| 77 |
+
base_url="http://localhost:8090/v1"
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
response = client.embeddings.create(
|
| 81 |
+
input="Hello, world!",
|
| 82 |
+
model="text-embedding-3-small"
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
print(response.data[0].embedding[:5]) # First 5 dimensions
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### OpenAI SDK (Go)
|
| 89 |
+
|
| 90 |
+
```go
|
| 91 |
+
package main
|
| 92 |
+
|
| 93 |
+
import (
|
| 94 |
+
"context"
|
| 95 |
+
"fmt"
|
| 96 |
+
"log"
|
| 97 |
+
|
| 98 |
+
"github.com/openai/openai-go"
|
| 99 |
+
"github.com/openai/openai-go/option"
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
func main() {
|
| 103 |
+
client := openai.NewClient(
|
| 104 |
+
option.WithAPIKey("your-axonhub-api-key"),
|
| 105 |
+
option.WithBaseURL("http://localhost:8090/v1"),
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
embedding, err := client.Embeddings.New(context.TODO(), openai.EmbeddingNewParams{
|
| 109 |
+
Input: openai.Union[string](openai.String("Hello, world!")),
|
| 110 |
+
Model: openai.String("text-embedding-3-small"),
|
| 111 |
+
option.WithHeader("AH-Trace-Id", "trace-example-123"),
|
| 112 |
+
option.WithHeader("AH-Thread-Id", "thread-example-abc"),
|
| 113 |
+
})
|
| 114 |
+
if err != nil {
|
| 115 |
+
log.Fatal(err)
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
fmt.Printf("Embedding dimensions: %d\n", len(embedding.Data[0].Embedding))
|
| 119 |
+
fmt.Printf("First 5 values: %v\n", embedding.Data[0].Embedding[:5])
|
| 120 |
+
}
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
### Multiple Texts
|
| 124 |
+
|
| 125 |
+
```python
|
| 126 |
+
response = client.embeddings.create(
|
| 127 |
+
input=["Hello, world!", "How are you?"],
|
| 128 |
+
model="text-embedding-3-small"
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
for i, data in enumerate(response.data):
|
| 132 |
+
print(f"Text {i}: {data.embedding[:3]}...")
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
### Jina-Specific Task
|
| 136 |
+
|
| 137 |
+
```python
|
| 138 |
+
import requests
|
| 139 |
+
|
| 140 |
+
response = requests.post(
|
| 141 |
+
"http://localhost:8090/jina/v1/embeddings",
|
| 142 |
+
headers={
|
| 143 |
+
"Authorization": "Bearer your-axonhub-api-key",
|
| 144 |
+
"Content-Type": "application/json"
|
| 145 |
+
},
|
| 146 |
+
json={
|
| 147 |
+
"input": "What is machine learning?",
|
| 148 |
+
"model": "jina-embeddings-v2-base-en",
|
| 149 |
+
"task": "retrieval.query"
|
| 150 |
+
}
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
result = response.json()
|
| 154 |
+
print(result["data"][0]["embedding"][:5])
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
## Authentication
|
| 158 |
+
|
| 159 |
+
The Embedding API uses Bearer token authentication:
|
| 160 |
+
|
| 161 |
+
- **Header**: `Authorization: Bearer <your-api-key>`
|
| 162 |
+
|
| 163 |
+
The API keys are managed through AxonHub's API Key management system.
|
| 164 |
+
|
| 165 |
+
## Best Practices
|
| 166 |
+
|
| 167 |
+
1. **Use Tracing Headers**: Include `AH-Trace-Id` and `AH-Thread-Id` headers for better observability
|
| 168 |
+
2. **Batch Requests**: When embedding multiple texts, send them in a single request for better performance
|
| 169 |
+
3. **Choose Appropriate Dimensions**: Use the `dimensions` parameter to reduce embedding size if full dimensionality isn't needed
|
| 170 |
+
4. **Select Proper Encoding**: Use `base64` encoding if you need to transmit embeddings over the network to reduce payload size
|
| 171 |
+
5. **Jina Task Types**: When using Jina embeddings, select the appropriate `task` type for your use case to optimize retrieval quality
|
| 172 |
+
|
| 173 |
+
## Related Resources
|
| 174 |
+
|
| 175 |
+
- [OpenAI API](openai-api.md)
|
| 176 |
+
- [Rerank API](rerank-api.md)
|
docs/en/api-reference/gemini-api.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gemini API Reference
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
AxonHub provides native support for the Gemini API, enabling access to Gemini's powerful multi-modal capabilities. You can use the Gemini SDK to access not only Gemini models but also OpenAI, Anthropic, and other supported models.
|
| 6 |
+
|
| 7 |
+
## Key Benefits
|
| 8 |
+
|
| 9 |
+
- **API Interoperability**: Use Gemini API to call OpenAI, Anthropic, and other supported models
|
| 10 |
+
- **Zero Code Changes**: Continue using your existing Gemini client SDK without modification
|
| 11 |
+
- **Automatic Translation**: AxonHub automatically converts between API formats when needed
|
| 12 |
+
- **Multi-modal Support**: Access text and image capabilities through the Gemini API format
|
| 13 |
+
|
| 14 |
+
## Supported Endpoints
|
| 15 |
+
|
| 16 |
+
**Endpoints:**
|
| 17 |
+
- `POST /gemini/v1beta/models/{model}:generateContent` - Text and multi-modal content generation
|
| 18 |
+
- `POST /v1beta/models/{model}:generateContent` - Text and multi-modal content generation (alternative)
|
| 19 |
+
- `GET /gemini/v1beta/models` - List available models
|
| 20 |
+
- `GET /v1beta/models` - List available models (alternative)
|
| 21 |
+
|
| 22 |
+
**Example Request:**
|
| 23 |
+
```go
|
| 24 |
+
import (
|
| 25 |
+
"context"
|
| 26 |
+
"google.golang.org/genai"
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
// Create Gemini client with AxonHub configuration
|
| 30 |
+
ctx := context.Background()
|
| 31 |
+
client, err := genai.NewClient(ctx, &genai.ClientConfig{
|
| 32 |
+
APIKey: "your-axonhub-api-key",
|
| 33 |
+
Backend: genai.Backend(genai.APIBackendUnspecified), // Use default backend
|
| 34 |
+
HTTPOptions: genai.HTTPOptions{
|
| 35 |
+
BaseURL: "http://localhost:8090/gemini",
|
| 36 |
+
},
|
| 37 |
+
})
|
| 38 |
+
if err != nil {
|
| 39 |
+
// Handle error appropriately
|
| 40 |
+
panic(err)
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// Call OpenAI model using Gemini API format
|
| 44 |
+
modelName := "gpt-4o" // OpenAI model accessed via Gemini API format
|
| 45 |
+
content := &genai.Content{
|
| 46 |
+
Parts: []*genai.Part{
|
| 47 |
+
{Text: genai.Ptr("Hello, GPT!")},
|
| 48 |
+
},
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
// Optional: Configure generation parameters
|
| 52 |
+
config := &genai.GenerateContentConfig{
|
| 53 |
+
Temperature: genai.Ptr(float32(0.7)),
|
| 54 |
+
MaxOutputTokens: genai.Ptr(int32(1024)),
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
response, err := client.Models.GenerateContent(ctx, modelName, []*genai.Content{content}, config)
|
| 58 |
+
if err != nil {
|
| 59 |
+
// Handle error appropriately
|
| 60 |
+
panic(err)
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// Extract text from response
|
| 64 |
+
if len(response.Candidates) > 0 &&
|
| 65 |
+
len(response.Candidates[0].Content.Parts) > 0 {
|
| 66 |
+
responseText := response.Candidates[0].Content.Parts[0].Text
|
| 67 |
+
fmt.Println(*responseText)
|
| 68 |
+
}
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**Example: Multi-turn Conversation**
|
| 72 |
+
```go
|
| 73 |
+
// Create a chat session with conversation history
|
| 74 |
+
modelName := "claude-3-5-sonnet"
|
| 75 |
+
config := &genai.GenerateContentConfig{
|
| 76 |
+
Temperature: genai.Ptr(float32(0.5)),
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
chat, err := client.Chats.Create(ctx, modelName, config, nil)
|
| 80 |
+
if err != nil {
|
| 81 |
+
panic(err)
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// First message
|
| 85 |
+
response1, err := chat.SendMessage(ctx, genai.Part{Text: genai.Ptr("My name is Alice")})
|
| 86 |
+
if err != nil {
|
| 87 |
+
panic(err)
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// Follow-up message (model remembers context)
|
| 91 |
+
response2, err := chat.SendMessage(ctx, genai.Part{Text: genai.Ptr("What is my name?")})
|
| 92 |
+
if err != nil {
|
| 93 |
+
panic(err)
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// Extract response
|
| 97 |
+
if len(response2.Candidates) > 0 {
|
| 98 |
+
text := response2.Candidates[0].Content.Parts[0].Text
|
| 99 |
+
fmt.Println(*text) // Should contain "Alice"
|
| 100 |
+
}
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
## API Translation Capabilities
|
| 104 |
+
|
| 105 |
+
AxonHub automatically translates between API formats, enabling powerful scenarios:
|
| 106 |
+
|
| 107 |
+
### Use Gemini SDK with OpenAI Models
|
| 108 |
+
```go
|
| 109 |
+
// Gemini SDK calling OpenAI model
|
| 110 |
+
content := &genai.Content{
|
| 111 |
+
Parts: []*genai.Part{
|
| 112 |
+
{Text: genai.Ptr("Explain neural networks")},
|
| 113 |
+
},
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
response, err := client.Models.GenerateContent(
|
| 117 |
+
ctx,
|
| 118 |
+
"gpt-4o", // OpenAI model
|
| 119 |
+
[]*genai.Content{content},
|
| 120 |
+
nil,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
// Access response
|
| 124 |
+
if len(response.Candidates) > 0 &&
|
| 125 |
+
len(response.Candidates[0].Content.Parts) > 0 {
|
| 126 |
+
text := response.Candidates[0].Content.Parts[0].Text
|
| 127 |
+
fmt.Println(*text)
|
| 128 |
+
}
|
| 129 |
+
// AxonHub automatically translates Gemini format → OpenAI format
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
### Use Gemini SDK with Anthropic Models
|
| 133 |
+
```go
|
| 134 |
+
// Gemini SDK calling Anthropic model
|
| 135 |
+
content := &genai.Content{
|
| 136 |
+
Parts: []*genai.Part{
|
| 137 |
+
{Text: genai.Ptr("What is artificial intelligence?")},
|
| 138 |
+
},
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
response, err := client.Models.GenerateContent(
|
| 142 |
+
ctx,
|
| 143 |
+
"claude-3-5-sonnet", // Anthropic model
|
| 144 |
+
[]*genai.Content{content},
|
| 145 |
+
nil,
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
// Access response
|
| 149 |
+
if len(response.Candidates) > 0 &&
|
| 150 |
+
len(response.Candidates[0].Content.Parts) > 0 {
|
| 151 |
+
text := response.Candidates[0].Content.Parts[0].Text
|
| 152 |
+
fmt.Println(*text)
|
| 153 |
+
}
|
| 154 |
+
// AxonHub automatically translates Gemini format → Anthropic format
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
## Authentication
|
| 158 |
+
|
| 159 |
+
The Gemini API format uses the following authentication:
|
| 160 |
+
|
| 161 |
+
- **Header**: `X-Goog-API-Key: <your-api-key>`
|
| 162 |
+
|
| 163 |
+
The API keys are managed through AxonHub's API Key management system and provide the same permissions regardless of which API format you use.
|
| 164 |
+
|
| 165 |
+
## Streaming Support
|
| 166 |
+
|
| 167 |
+
Gemini API format supports streaming responses for real-time content generation.
|
| 168 |
+
|
| 169 |
+
## Error Handling
|
| 170 |
+
|
| 171 |
+
Gemini format error responses follow the standard Gemini API error format.
|
| 172 |
+
|
| 173 |
+
## Tool Support
|
| 174 |
+
|
| 175 |
+
AxonHub supports **function tools** (custom function calling) through the Gemini API format. However, provider-specific tools are **not supported**:
|
| 176 |
+
|
| 177 |
+
| Tool Type | Support Status | Notes |
|
| 178 |
+
| --------- | -------------- | ----- |
|
| 179 |
+
| **Function Tools** | ✅ Supported | Custom function definitions work across all providers |
|
| 180 |
+
| **Web Search** | ❌ Not Supported | Provider-specific |
|
| 181 |
+
| **Code Interpreter** | ❌ Not Supported | Provider-specific |
|
| 182 |
+
| **File Search** | ❌ Not Supported | Provider-specific |
|
| 183 |
+
| **Computer Use** | ❌ Not Supported | Anthropic-specific |
|
| 184 |
+
|
| 185 |
+
> **Note**: Only generic function tools that can be translated across providers are supported. Provider-specific tools require direct access to the provider's infrastructure and cannot be proxied through AxonHub.
|
| 186 |
+
|
| 187 |
+
## Best Practices
|
| 188 |
+
|
| 189 |
+
1. **Use Tracing Headers**: Include `AH-Trace-Id` and `AH-Thread-Id` headers for better observability
|
| 190 |
+
2. **Model Selection**: Specify the target model explicitly in your requests
|
| 191 |
+
3. **Error Handling**: Implement proper error handling for API responses
|
| 192 |
+
4. **Streaming**: Use streaming for better user experience with long responses
|
| 193 |
+
5. **Multi-modal Content**: Leverage Gemini API's multi-modal capabilities when working with images
|
| 194 |
+
|
| 195 |
+
## Migration Guide
|
| 196 |
+
|
| 197 |
+
### From Gemini to AxonHub
|
| 198 |
+
```go
|
| 199 |
+
// Before: Direct Gemini
|
| 200 |
+
ctx := context.Background()
|
| 201 |
+
client, err := genai.NewClient(ctx, &genai.ClientConfig{
|
| 202 |
+
APIKey: "gemini-api-key",
|
| 203 |
+
})
|
| 204 |
+
|
| 205 |
+
// After: AxonHub with Gemini API
|
| 206 |
+
ctx := context.Background()
|
| 207 |
+
client, err := genai.NewClient(ctx, &genai.ClientConfig{
|
| 208 |
+
APIKey: "your-axonhub-api-key",
|
| 209 |
+
HTTPOptions: genai.HTTPOptions{
|
| 210 |
+
BaseURL: "http://localhost:8090/gemini",
|
| 211 |
+
},
|
| 212 |
+
})
|
| 213 |
+
// Your existing code continues to work!
|
| 214 |
+
```
|