An Auto Configuration Server (ACS) for managing CPE equipment via the TR-069 (CWMP) protocol. It allows provisioning, monitoring, and executing remote tasks on routers and modems from any manufacturer that implements the TR-181 or TR-098 data models.
Complete CPE parameters (after summon) are persisted in PostgreSQL (queryable history); MongoDB stores the device document; Redis queues CWMP tasks.
- Overview
- Features
- Architecture
- Prerequisites
- Configuration
- Running
- Web interface
- REST API
- CWMP Tasks
- Data models
- Parameter schemas
- Device drivers (YAML)
- Development
Login
Dashboard
Devices
Device details
Task creation
System health
Helix ACS operates as the server side of the TR-069 protocol. When a router or modem (CPE) is powered on, it contacts the ACS via HTTP/SOAP. The server then registers the device, applies pending configurations, and collects statistics — all transparently to the end user.
Two HTTP servers run simultaneously:
- CWMP Server on port
7547: receives CPE connections (Digest authentication) - API and web interface server on port
8080: used by administrators (JWT authentication)
Device management
- Automatic CPE registration on first contact (Inform)
- Dynamic discovery of TR-181 and TR-098 instance numbers (with optional hints per model in
driver.yaml) - Automatic data model detection (TR-181 or TR-098)
- Automatic schema resolution by manufacturer (e.g., Huawei, ZTE, TP-Link) with fallback to the generic schema
- YAML drivers per manufacturer/model: provisioning flows (WAN/WiFi),
default_paramsapplied after full summon, and WiFi mapping (e.g., SSID ↔ band whenLowerLayersdoes not exist) - Summon: full parameter update with throttle (~2 min); when a WiFi/WAN task is pending within the throttle window, summon can be targeted (
Device.WiFi.,Device.IP.Interface., etc.) for a faster round trip - Filtering and pagination on device listing
- Tag and metadata editing
Remote tasks
- Wi-Fi configuration (SSID, password, channel, 2.4 GHz and 5 GHz bands)
- WAN configuration (PPPoE, DHCP, static IP, VLAN, MTU)
- LAN and DHCP server configuration
- Device web interface password change
- Firmware update via URL
- Port forwarding (add, remove, list)
- Reboot and factory reset
- Set/Get of arbitrary TR-069 parameters
Diagnostics
- Ping test with detailed results (minimum, average, maximum RTT and packet loss)
- Traceroute with hop listing
- Speed test (download)
- Connected device listing (DHCP hosts)
- CPE statistics (uptime, RAM, WAN counters)
Web interface
- Dashboard with device summary and system status
CPE (router/modem)
|
| HTTP/SOAP (TR-069 / CWMP)
|
v
+---------------------+ +----------+
| CWMP Server | | MongoDB |
| port 7547 +------->+ (devices)|
| | +----------+
| Digest Auth | +----------+
+---------------------+ |PostgreSQL|
| (params) |
+----------+
+---------------------+ +----------+
| REST API + Web UI +------->+ Redis |
| port 8080 | | (tasks) |
| | +----------+
| JWT Auth |
+---------------------+
^
|
Administrator (browser / API client)
Main packages:
| Package | Responsibility |
|---|---|
cmd/api |
Entry point, dependency composition, and server initialization |
internal/cwmp |
CWMP protocol: SOAP parsing, Inform session, task execution |
internal/api |
HTTP routing, REST handlers, middlewares (CORS, JWT, rate limit, logging) |
internal/device |
Device model, MongoDB repository, and service |
internal/task |
Task types, payloads, Redis queue, and executor |
internal/datamodel |
Mapper interface, TR-181 and TR-098 mappers with dynamic instance discovery |
internal/schema |
YAML schema registry, SchemaMapper, driver registry (driver.yaml + provisions) |
internal/parameter |
TR-069 parameter persistence and history (PostgreSQL, optional Redis cache) |
internal/auth |
JWT and Digest Auth |
internal/config |
Configuration loading and validation (Viper) |
web |
Web interface embedded in the binary (HTML, CSS, JS) |
- Go 1.25 or higher
- MongoDB 7
- PostgreSQL 16+ (parameter storage / history; see
application.postgresqlandscripts/schema-postgresql.sqlin Compose) - Redis 7
- Docker and Docker Compose (optional, for containerized execution)
Copy the example file and adjust the values:
cp configs/config.example.yml configs/config.ymlThe required fields that must be changed before the first run are:
| Field | Description |
|---|---|
application.jwt.secret |
Secret for signing JWT tokens. Use openssl rand -base64 32 to generate a secure value. |
application.acs.password |
Password that CPEs use to authenticate with the ACS. |
application.acs.url |
Public ACS URL provisioned in CPEs (must be accessible from the CPE network). |
databases.cache.uri |
Redis connection URI. |
databases.storage.uri |
MongoDB connection URI. |
application.postgresql.* |
Host, port, user, password, and database used by the parameter repository. |
application.parameters.* |
Parameter backend (postgresql), cache, history, and daily snapshots. |
See the configs/config.example.yml file for the full description of each field.
application
| Field | Type | Description |
|---|---|---|
name |
string | Name displayed in the startup banner |
log_level |
string | Log level: debug, info, warn, error |
jwt.secret |
string | Secret key for JWT tokens |
jwt.expires_in |
duration | Access token validity (e.g., 24h) |
jwt.refresh_expires_in |
duration | Refresh token validity (e.g., 168h) |
application.acs
| Field | Type | Description |
|---|---|---|
listen_port |
int | CWMP server port (TR-069 default: 7547) |
username |
string | Username for CPE Digest authentication |
password |
string | Password for CPE Digest authentication |
url |
string | ACS URL provisioned in CPEs |
inform_interval |
int | Inform interval in minutes |
schemas_dir |
string | Path to the YAML schemas directory (default: ./schemas) |
application.web
| Field | Type | Description |
|---|---|---|
listen_port |
int | API and web interface port (default: 8080) |
use_ssl |
bool | Enable TLS directly in the application |
crt |
string | Path to the PEM certificate |
key |
string | Path to the PEM private key |
application.tasks.queue
| Field | Type | Description |
|---|---|---|
max_attempts |
int | Maximum attempts before marking the task as failed |
interval |
duration | Queue polling interval |
databases.storage (MongoDB)
| Field | Type | Description |
|---|---|---|
uri |
string | Connection URI (e.g., mongodb://localhost:27017) |
name |
string | Database name |
log_level |
string | Driver log level |
databases.cache (Redis)
| Field | Type | Description |
|---|---|---|
uri |
string | Connection URI (e.g., redis://localhost:6379) |
ttl |
duration | Task queue TTL (e.g., 168h) |
# Install dependencies and build
go build -o helix ./cmd/api
# Start with the default configuration file
./helix
# Start with a custom configuration path
./helix -config /etc/helix/config.ymlThe simplest way to bring up the entire environment:
# Configure before starting
cp configs/config.example.yml configs/config.yml
# edit configs/config.yml with your credentials
# Start the services (MongoDB, Redis, and application)
docker compose up -d
# Follow the logs
docker compose logs -f app
# Stop
docker compose downThe docker-compose.yml exposes ports 7547 (CWMP) and 8080 (API/UI) on the host. MongoDB and Redis data are persisted in named volumes.
# Build the image
docker build -t helix-acs .
# Run with a mounted configuration file
docker run -d \
-p 7547:7547 \
-p 8080:8080 \
-v $(pwd)/configs:/helix/configs \
--name helix-acs \
helix-acsAccess http://localhost:8080 in your browser. The login credentials are the same as those defined in application.acs.username and application.acs.password in the configuration file.
Available pages:
| Page | Description |
|---|---|
| Dashboard | Device counter (total, online, offline), recent tasks |
| Devices | Listing with filters, details of each CPE, TR-069 parameters, and task history |
| System health | Connectivity status with MongoDB and Redis |
On the device details page, you can create tasks, edit tags, and view all parameters returned by the CPE in the last Inform.
All protected routes require the Authorization: Bearer <token> header.
| Method | Route | Description |
|---|---|---|
| POST | /api/v1/auth/login |
Authenticates and returns access token and refresh token |
| POST | /api/v1/auth/refresh |
Renews the access token with a valid refresh token |
Login:
POST /api/v1/auth/login
{
"username": "acs",
"password": "your_password"
}Response:
{
"token": "eyJ...",
"refresh_token": "eyJ...",
"expires_in": 86400
}| Method | Route | Description |
|---|---|---|
| GET | /api/v1/devices |
Lists devices (paginated, with filters) |
| GET | /api/v1/devices/{serial} |
Returns a device by serial number |
| PUT | /api/v1/devices/{serial} |
Updates metadata (tags, alias) |
| DELETE | /api/v1/devices/{serial} |
Removes a device |
| GET | /api/v1/devices/{serial}/parameters |
Returns all TR-069 parameters of the CPE |
| GET | /api/v1/devices/{serial}/traffic |
Average WAN rate series (bps) derived from Δbytes/Δt between samples; query hours (default 24, max 168), limit (max 5000) |
Available filters on GET /api/v1/devices:
| Parameter | Type | Description |
|---|---|---|
page |
int | Page (default: 1) |
limit |
int | Items per page (default: 20) |
manufacturer |
string | Filter by manufacturer |
model |
string | Filter by model |
online |
bool | Filter by online/offline status |
tag |
string | Filter by tag |
wan_ip |
string | Filter by WAN IP |
| Method | Route | Description |
|---|---|---|
| GET | /api/v1/devices/{serial}/tasks |
Lists tasks for a device |
| POST | /api/v1/devices/{serial}/tasks/{type} |
Creates a new task |
| GET | /api/v1/tasks/{task_id} |
Returns a task by ID |
| DELETE | /api/v1/tasks/{task_id} |
Cancels a pending task |
| Method | Route | Description |
|---|---|---|
| GET | /health |
System status (no authentication required) |
Tasks are queued in Redis and delivered to the CPE in the next Inform session. Each task has a maximum of max_attempts execution attempts.
Possible states: pending, executing, done, failed, cancelled
Configuration
| Type | Route | Main payload |
|---|---|---|
| Wi-Fi | POST .../tasks/wifi |
band, ssid, password, channel, enabled |
| WAN | POST .../tasks/wan |
connection_type (pppoe/dhcp/static), username, password, ip_address, vlan, mtu |
| LAN / DHCP | POST .../tasks/lan |
dhcp_enabled, ip_address, subnet_mask, dhcp_start, dhcp_end |
| Web password | POST .../tasks/web-admin |
password |
| Set Parameters | POST .../tasks/parameters |
parameters (map of TR-069 path to value) |
| Firmware | POST .../tasks/firmware |
url, version, file_type |
| Port forwarding | POST .../tasks/port-forwarding |
action (add/remove/list), protocol, external_port, internal_ip, internal_port |
Maintenance
| Type | Route | Payload |
|---|---|---|
| Reboot | POST .../tasks/reboot |
none |
| Factory reset | POST .../tasks/factory-reset |
none |
Diagnostics
| Type | Route | Main payload |
|---|---|---|
| Ping | POST .../tasks/ping |
host, count, packet_size, timeout |
| Traceroute | POST .../tasks/traceroute |
host, max_hops, timeout |
| Speed test | POST .../tasks/speed-test |
download_url |
| Connected devices | POST .../tasks/connected-devices |
none |
| CPE statistics | POST .../tasks/cpe-stats |
none |
Example: configure Wi-Fi
curl -X POST http://localhost:8080/api/v1/devices/AABBCC123456/tasks/wifi \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"band": "2.4",
"ssid": "MyNetwork",
"password": "password12345",
"enabled": true
}'Example: ping test
curl -X POST http://localhost:8080/api/v1/devices/AABBCC123456/tasks/ping \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"host": "8.8.8.8",
"count": 4
}'Helix ACS supports the two most widely used TR-069 data models in the market.
TR-181 (prefix Device.): modern model, adopted in equipment manufactured from 2010 onwards. Supported by most current routers.
TR-098 (prefix InternetGatewayDevice.): legacy model, common in older equipment and in part of the installed base in Brazil.
The model is automatically detected on the first Inform, based on the root object reported by the CPE.
Different CPEs may assign different instance numbers to interfaces. For example, the WAN may be at Device.IP.Interface.1 or Device.IP.Interface.3, depending on the manufacturer.
On each Inform, the system runs DiscoverInstances (and variants with driver hints) which scans the received parameters and identifies the actual indices of:
- WAN and LAN interfaces (by public/private IP classification)
- PPP interface, VLAN terminations, and Ethernet links (TR-181)
- Wi-Fi radios, SSIDs, and Access Points (by
OperatingFrequencyBandandLowerLayers; for CPEs withoutLowerLayerson the SSID, the YAML driver can define strategies such aspair_block_mod2orexplicitmaps by SSID index) - WAN devices and TR-098 connections
This way, tasks are always sent to the correct path, regardless of the manufacturer.
For TR-181 devices, the default path is Device.Users.User.1.Password. Manufacturers like Huawei use proprietary paths (e.g., Device.X_HW_Security.AdminPassword) — these cases are covered by vendor-specific schemas in schemas/vendors/. For TR-098 devices without a registered vendor schema, use the set_parameters task specifying the path directly.
All TR-069 parameter paths are defined in YAML files in the schemas/ directory. No paths are hardcoded in the application code.
schemas/
├── tr181/ # Standard TR-181 paths
│ ├── wifi.yaml
│ ├── wan.yaml
│ ├── lan.yaml
│ ├── system.yaml
│ ├── management.yaml
│ ├── diagnostics.yaml
│ ├── hosts.yaml
│ ├── port_forwarding.yaml
│ └── change_password.yaml
├── tr098/ # Standard TR-098 paths
│ └── ... # same structure
└── vendors/
├── tplink/
│ ├── tr181/
│ │ ├── driver.yaml # driver + default_params + provisions
│ │ ├── provision_wan.yaml
│ │ └── …
│ └── models/
│ └── XC220-G3/
│ └── tr181/
│ └── driver.yaml # per-model overrides
├── huawei/
│ └── tr181/
│ └── change_password.yaml
└── zte/
└── tr098/
└── change_password.yaml
id: change_password
model: tr181
vendor: huawei
description: Admin password for Huawei TR-181 devices
parameters:
- name: admin.password
path: "Device.X_HW_Security.AdminPassword"
type: stringOn each Inform, the system identifies the manufacturer reported by the CPE and resolves the schema to use:
- Normalizes the manufacturer name to a slug (e.g.,
"Huawei Technologies Co., Ltd."→"huawei") - Checks if
vendors/<slug>/<model>/exists in the schemas directory - If it exists, loads the generic model schema as a base and overlays only the parameters defined in the vendor-specific schema
- If it doesn't exist, uses only the generic schema (
tr181ortr098)
The resolved schema name (e.g., "vendor/huawei/tr181" or "tr181") is persisted in the device document in MongoDB.
Create a YAML file with only the parameters that differ from the default:
mkdir -p schemas/vendors/myManufacturer/tr181
cat > schemas/vendors/mymanufacturer/tr181/change_password.yaml << 'EOF'
id: change_password
model: tr181
vendor: mymanufacturer
description: Admin password
parameters:
- name: admin.password
path: "Device.X_VENDOR_AdminPassword"
type: string
EOFRestart the application. No code changes are required.
In addition to parameter schemas (wifi.yaml, wan.yaml, …), the schemas/vendors/ directory can contain a driver.yaml per manufacturer or per model. The registry loads these files at startup; changes to the YAML require a restart of the process (no recompilation needed).
vendors/<vendor>/models/<productClass>/<tr181|tr098>/driver.yaml— model-specific (highest priority)vendors/<vendor>/<tr181|tr098>/driver.yaml— manufacturer default
The default_params fields from the vendor file are merged with those from the model driver (the model overrides matching keys).
| Area | Description |
|---|---|
features, config, security_modes, wifi |
Vendor-specific behavior and paths (e.g., TP-Link band steering) |
discovery |
Paths for WAN type, GPON, service type, and WiFi options without LowerLayers (wifi_ssid_band_without_lower_layers) |
default_params |
Map of absolute TR-069 path → value. After each full summon, the ACS compares with the values obtained from the CPE and sends SetParameterValues only where they differ |
provisions |
YAML step files (WAN PPPoE, WiFi update, etc.) referenced by name |
- The full summon (GetParameterNames + batches of GetParameterValues) is rate-limited (~2 min per device) to avoid overloading the CPE.
- The
default_paramsare evaluated at the end of a full summon (when the parameter map is complete). - When a WiFi or WAN task is pending within the throttle window, the ACS can perform a targeted summon (subtrees like
Device.WiFi.orDevice.IP.Interface.) to update indices before dispatching the task, without waiting for the full summon.
go test ./...go build -o helix ./cmd/apiThe helix binary is not versioned in Git (see .gitignore). Use any name you prefer; the example above matches the documentation in Running.
docker build -t helix-acs .The Dockerfile uses a multi-stage build: compiles in golang:1.25-alpine and produces a minimal final image based on alpine:3.22, running as a non-root user.
.
+-- cmd/api/ Application entry point
+-- configs/ Configuration files
+-- schemas/ TR-069 parameter YAML schemas
| +-- tr181/ Standard TR-181 paths
| +-- tr098/ Standard TR-098 paths
| +-- vendors/ Per-manufacturer overlays
+-- internal/
| +-- api/ Routing and REST handlers
| +-- auth/ JWT and Digest Auth
| +-- config/ Configuration structures and loading
| +-- cwmp/ CWMP server and handler (TR-069 / SOAP)
| +-- datamodel/ Mapper interface, TR-181 and TR-098, instance discovery
| +-- device/ Model, MongoDB repository, and device service
| +-- logger/ Logger wrapper
| +-- parameter/ TR-069 parameters: PostgreSQL, cache, history
| +-- schema/ Schema registry, YAML drivers, SchemaMapper
| +-- task/ Task types, Redis queue, and executor
+-- web/ Web interface (HTML, CSS, JS) embedded in the binary
+-- examples/ CPE simulator for local testing
+-- docker-compose.yml Complete environment with MongoDB and Redis
+-- Dockerfile Build and production image
Contributions to helix-acs are welcome! Here are some ways you can help improve the project:
- Report bugs and feature suggestions by opening issues on GitHub
- Submit pull requests with bug fixes or new features
- Improve the documentation to help other users and developers
- Share your custom strategies with the community
helix-acs is distributed under the MIT License.
For the full license terms and conditions, see the LICENSE file in the repository.
For support, collaboration, or questions about helix-acs:
Email: raykavin.meireles@gmail.com
LinkedIn: @raykavin.dev
GitHub: @raykavin











