Repository navigation
Development
TL;DR
- What: Local development setup with Go backend + Vite frontend dev server
- Who: Contributors and developers working on Veyport
- Why: Hot-reload frontend, API proxy, full-stack local testing
- Where: Two terminals:
make dev-hub(Go on :8080) +make dev-web(Vite on :5173)- When: After cloning the repo and installing prerequisites (Go 1.26+, Node 25+, Make)
- How: Vite proxies
/apito Go backend;make buildfor production binary;make testfor test suite
- Go 1.26+
- Node.js 25+
- Make
- Git
git clone https://github.com/lorconksu/veyport.git
cd veyport
# Install frontend dependencies
cd web && npm install && cd ..The agent-hub gRPC contract is defined in proto/veyport/v1/agent.proto. After modifying the proto file, regenerate the Go bindings.
Prerequisites:
# Install protoc (Protocol Buffers compiler)
# On Ubuntu/Debian:
apt install - y protobuf-compiler
# Install the Go plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestGenerate:
make protoThis runs protoc with the --go_out and --go-grpc_out flags, writing generated files alongside the proto source.
The development environment runs two processes - open two terminal windows.
Terminal 1 - Hub (HTTP on :8080, gRPC on :9090):
make dev-hubThis runs go run ./cmd/veyport/ --dev --addr :8080 --grpc-addr :9090 from the hub/ directory. The --dev flag enables permissive CORS so the Vite dev server can make cross-origin API requests, and disables serving the embedded frontend (Vite handles that instead). Both the HTTP API and gRPC agent listener start together.
Terminal 2 - Vite dev server (UI on :5173):
make dev-webThis runs npm run dev from the web/ directory. Navigate to http://localhost:5173 in your browser.
When testing from another machine on the LAN, use HTTPS instead of plain HTTP:
make dev-web-lanThe Hub always sets Secure auth cookies. Browsers will not persist those cookies over http://<lan-ip>:5173, which makes login appear to loop even when the password is correct. The dev-web-lan target starts Vite on 0.0.0.0:5173 with a local self-signed certificate and proxies API requests to the running Hub. If a Hub is already listening on 127.0.0.1:8081, that target is used; otherwise it falls back to 127.0.0.1:8080. Override it explicitly with VITE_API_PROXY_TARGET=http://127.0.0.1:<port> make dev-web-lan.
Open the https://<lan-ip>:5173/ URL shown by the command and accept the browser warning for the self-signed certificate.
On first run, you'll be directed to the setup page to create your admin account and configure TOTP.
The Vite config in web/vite.config.ts includes a proxy rule:
server: {
proxy: {
'/api': 'http://localhost:8080',
},
},Any request the browser makes to /api/* is forwarded by Vite to the Go Hub on port 8080. This means the browser always talks to localhost:5173 (no CORS issues from the browser's perspective), and Vite silently proxies the API calls. In production there is no proxy - the Hub serves both the SPA and the API on the same port.
make buildOutput: bin/veyport - a single self-contained binary.
make testThis runs the Go test suites for both the Hub and the Agent:
-
auth/- JWT generation, validation, token type enforcement, bcrypt, TOTP -
store/- All store methods against a real in-memory SQLite database (including notification preferences and log) -
server/- HTTP handler tests usinghttptest(including SMTP, notification, and hub config handlers) -
notify/- SMTP client tests, template rendering, CRLF injection prevention, debounce logic -
grpcserver/- HeartbeatCoalescer tests, handler tests
make test-agentRuns go test ./... from the agent/ directory.
cd web && npx vitest runThe CI pipeline also runs integration, smoke, and e2e suites on pull requests.
The agent is cross-compiled for the two supported targets:
make build-agentThis produces:
bin/veyport-agent-linux-amd64bin/veyport-agent-linux-arm64
Place these in the Hub's --agent-bin-dir so they are served via /install/{os}/{arch}.
To exercise the full Hub + Agent stack locally:
Terminal 1 - Start the Hub:
./bin/veyport \
--addr :8080 \
--grpc-addr :9090 \
--db test.db \
--devIn the UI:
- Create a server record (Admin → Servers → Add Server).
- Copy the registration token shown on the server detail page.
Terminal 2 - Run the agent:
./bin/veyport-agent-linux-amd64 \
--hub localhost:9090 \
--token <REGISTRATION_TOKEN>The agent connects over insecure gRPC (no TLS - detected automatically because the hub address is an IP/localhost). The server status in the UI will change to online within a few seconds.
veyport/
├── Makefile # Build orchestration
├── proto/
│ └── veyport/
│ └── v1/
│ └── agent.proto # gRPC service definition for Hub-Agent communication
├── hub/
│ ├── embed.go # go:embed directive pointing at web/dist
│ ├── go.mod / go.sum
│ ├── cmd/
│ │ └── veyport/
│ │ ├── main.go # Flag parsing, wiring, graceful shutdown
│ │ └── admin.go # CLI admin subcommands
│ └── internal/
│ ├── auth/
│ │ ├── jwt.go # Token generation and validation
│ │ ├── password.go # bcrypt helpers + password policy
│ │ └── totp.go # TOTP secret generation + code verification
│ ├── ca/ # Hub Certificate Authority for mTLS (ECDSA P-256)
│ ├── connmgr/ # Agent connection manager (active streams + SendMu)
│ ├── grpcserver/ # gRPC server, Connect handler, PendingRequests, LogSessions, HeartbeatCoalescer
│ ├── migrate/
│ │ ├── migrate.go # Migration runner
│ │ └── migrations/ # Numbered .sql files (001_, 002_, ...)
│ ├── model/
│ │ ├── user.go # User, request/response types
│ │ ├── server.go # Server, request/response types
│ │ ├── audit.go # AuditEntry, action constants
│ │ └── notification.go # Notification types, SMTP config, alert event definitions
│ ├── notify/
│ │ ├── notifier.go # Notification dispatcher (debounce, priority queue, background worker)
│ │ ├── smtp.go # SMTP client with TLS support and CRLF injection prevention
│ │ └── templates.go # Email HTML templates and context rendering
│ ├── server/
│ │ ├── server.go # Server struct, Init, SPA handler
│ │ ├── router.go # Route registration
│ │ ├── middleware.go # Auth middleware, rate limiter, CORS, logging
│ │ ├── respond.go # respondJSON / respondError helpers
│ │ ├── handlers_auth.go
│ │ ├── handlers_servers.go
│ │ ├── handlers_users.go
│ │ ├── handlers_audit.go
│ │ ├── handlers_notifications.go # SMTP config, notification preferences, notification log
│ │ ├── handlers_terminal.go # Browser terminal session lifecycle
│ │ ├── ldap_auth.go # LDAP bind/search authentication and group mapping
│ │ ├── handlers_ldap_config.go # LDAP directory settings API (GET/PUT /api/settings/ldap, POST .../test)
│ │ └── handlers_hub_config.go # Hub settings (gRPC external address)
│ └── store/
│ ├── store.go # Store struct, New(), DB pragma setup
│ ├── users.go # User CRUD
│ ├── servers.go # Server CRUD
│ ├── audit.go # Audit log writes and queries
│ ├── config.go # Key-value config store
│ └── notifications.go # Notification preferences and delivery log queries
├── agent/
│ ├── go.mod / go.sum
│ ├── cmd/
│ │ └── veyport-agent/
│ │ └── main.go # Entry point: flags, config load/save, wiring
│ └── internal/
│ ├── client/ # gRPC stream client with reconnect backoff
│ ├── dropzone/ # Chunked file upload receiver
│ ├── filebrowser/ # Directory listing and file reading
│ ├── heartbeat/ # Periodic heartbeat sender (10s interval)
│ ├── logtailer/ # Poll-based file tailing with grep support
│ ├── terminal/ # PTY lifecycle, execution-user resolution, resize/input handling
│ └── sysinfo/ # CPU, memory, disk, uptime collection
└── web/
├── vite.config.ts
├── package.json
└── src/
├── main.tsx # React root
├── App.tsx # Router setup
├── pages/ # One file per route
├── components/ # Shared UI components
├── hooks/ # Custom React hooks (API calls via TanStack Query)
├── layouts/ # Layout wrappers
├── lib/ # Utilities
└── types/ # TypeScript type definitions
A typical full-stack feature (e.g. adding a new resource type) involves these steps:
Create a new numbered SQL file in hub/internal/migrate/migrations/:
007_create_my_resource.sql
CREATE TABLE my_resources (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);Migrations run automatically on the next startup. They are applied in filename order and never re-run.
Add request/response structs to hub/internal/model/. Keep this package free of business logic - plain structs only.
// hub/internal/model/my_resource.go
type MyResource struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
}
type CreateMyResourceRequest struct {
Name string `json:"name"`
}Add a new file to hub/internal/store/ with the SQL queries:
// hub/internal/store/my_resources.go
func (s *Store) CreateMyResource(name string) (*model.MyResource, error) { ... }
func (s *Store) GetMyResource(id string) (*model.MyResource, error) { ... }
func (s *Store) ListMyResources() ([]model.MyResource, error) { ... }
func (s *Store) DeleteMyResource(id string) error { ... }Write tests in my_resources_test.go using the testStore() helper pattern from existing test files.
Create hub/internal/server/handlers_my_resource.go:
func (s *Server) handleListMyResources(w http.ResponseWriter, r *http.Request) {
resources, err := s.store.ListMyResources()
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to list resources")
return
}
respondJSON(w, http.StatusOK, resources)
}Add the routes to hub/internal/server/router.go:
mux.Handle("GET /api/my-resources", loggingMiddleware(
s.authMiddleware(auth.TokenTypeAccess, http.HandlerFunc(s.handleListMyResources)),
))Create a new page in web/src/pages/ and add the route in web/src/App.tsx. Use TanStack Query for data fetching (see existing pages for the pattern).
hub/internal/server.AuthorizeTerminalExecution is shared by browser terminal
handlers and hub/internal/sshgw. Both send TerminalOpenRequest.run_as_user
through the existing agent stream to agent/internal/terminal.Manager.
An empty execution user means inherit the agent's OS account, used for local
admins; the standard installed agent runs as root. LDAP accounts resolve to their
mapped host username and execute with that account's UID, GID, and groups.
Do not silently fall back to the agent identity when a mapped account is missing.
The standalone CLI is in cli/; it uses HTTPS for API requests and certificate
issuance, then invokes the system SSH client against the Hub gateway. Terminal
authorization is independent of whether the transport is browser SSE or SSH.
The Hub records session idle activity through authenticated HTTP requests. The
signed-in app shell uses useSessionActivity to send HEAD /api/auth/me after
recent visible-tab interaction, independently of React Query polling. Preserve
its throttling, hidden-tab suppression, request cancellation, and bounded token
refresh when changing queries or session policy. Background polling still counts
as activity; Architecture documents the behavior and limits.
- Standard
gofmtformatting. Rungo fmt ./...before committing. - Errors are wrapped with
fmt.Errorf("context: %w", err)and returned to callers. HTTP handlers convert errors to HTTP responses viarespondError. - No global state. Dependencies are injected via the
Serverstruct andConfig. - All SQL queries are in the
storepackage. Handlers must not construct SQL. - Audit log entries should be written for all user-facing state changes. See
model.Audit*constants for the naming pattern (resource.action). - HTTP terminal endpoints and SSH certificate issuance require an interactive login; API tokens are refused. Keep the HTTP middleware checks and the shared terminal authorization decision consistent with SSH. LDAP non-admins also require terminal access and a per-server
/assignment.
- Strict TypeScript throughout (
strict: truein tsconfig). - TanStack Query manages all server state. Do not use
useEffect+useStatefor data fetching. - Components live in
src/components/, pages insrc/pages/. Pages are thin - extract reusable UI to components. - Use the
@/path alias for all imports withinsrc/.
Use conventional commit prefixes: feat:, fix:, refactor:, test:, docs:, chore:.
Veyport User Guide
Getting Started
Using Veyport
Administration
Setup & Deployment
- Architecture
- Deployment
- Deployment On-Prem
- Deployment AWS
- Deployment Azure
- Deployment GCP
- Proxy Configuration
- Development
- API Reference
Help