updated gitignore ignored docs by error..

moved docs appropriately added default registry value
This commit is contained in:
AndReicscs
2026-06-18 17:45:40 +00:00
parent 4a697afee2
commit 52c4f7a9e2
11 changed files with 485 additions and 307 deletions
+4 -2
View File
@@ -14,8 +14,10 @@ yarn-debug.log*
yarn-error.log*
# hub binary
hub
hub.exe
/hub
/Hub/hub
/hub.exe
/Hub/hub.exe
# =========================
# OS & IDE Clutter
-303
View File
@@ -1,303 +0,0 @@
# HoneyWire Hub Deployment Architecture
## Overview
HoneyWire uses a centralized Hub to manage node provisioning, sensor configuration, deployment generation, and configuration reconciliation.
Each managed infrastructure node receives:
- a unique Node ID
- a generated Node API Key
- a dynamic deployment bundle generated by the Hub
The Hub acts as the single source of truth for all deployed sensors and runtime configuration state.
---
# Node Provisioning
## Create Node
### Endpoint
```http
POST /api/v1/nodes
```
### Purpose
Creates a new logical infrastructure node inside the HoneyWire fleet.
### Request
```json
{
"alias": "smb-gateway",
"tags": ["production", "gateway"]
}
```
### Response
```json
{
"nodeId": "node-e0d6d6ca",
"apiKey": "hw_key_xxxxxxxxx",
"alias": "smb-gateway"
}
```
### Result
The Hub creates:
- a persistent node identity
- a unique API key
- an empty deployment state
No sensors are deployed at this stage.
---
# Sensor Deployment
## Add Sensor to Node
### Endpoint
```http
POST /api/v1/nodes/{id}/sensors
```
### Purpose
Assigns a sensor configuration to a node.
### Request
```json
{
"sensorId": "hw-sensor-tcp-tarpit",
"customName": "Credential Trap",
"configValues": {
"HW_DECOY_PORTS": "2222,3306",
"HW_TARPIT_MODE": "hold"
}
}
```
### Result
The Hub:
- stores the sensor configuration
- links the sensor to the node
- marks the node as requiring synchronization
---
# Configuration State
## Pending Configuration
Whenever sensors are:
- added
- removed
- edited
the Hub sets:
```text
nodes.pending_config = true
```
This indicates the deployed infrastructure configuration no longer matches the desired configuration stored in the Hub.
---
## Configuration Revisions
Every generated deployment bundle receives a unique configuration revision:
```text
HW_CONFIG_REV=rev_xxxxxxxx
```
rev_xxxxxxxx is a deterministic 8-char string for tracking config state.
This revision is embedded into all deployed sensors.
### Purpose
Configuration revisions allow the Hub to:
- detect deployment drift
- verify successful synchronization
- reconcile desired vs active state
---
## Active Revision
Once deployed sensors begin reporting heartbeats with the expected revision:
```text
nodes.active_revision = desired_revision
nodes.pending_config = false
```
The node is considered synchronized.
---
# Deployment Generation
## Canonical Deployment Endpoint
### Endpoint
```http
GET /api/v1/nodes/compose
```
### Authentication
```http
Authorization: Bearer <NODE_API_KEY>
```
### Purpose
Generates the canonical deployment bundle for the authenticated node.
The Hub:
- authenticates the node via API key
- loads installed sensors from the database
- loads manifests from the sensor catalog
- merges persisted sensor configuration
- injects runtime variables
- injects the current configuration revision
- generates the final Docker Compose deployment
---
## Generated Deployment Characteristics
Generated deployments include:
- hardened container defaults
- read-only filesystems
- dropped Linux capabilities
- sensor-specific privileges only
- logging policies
- network configuration
- deployment dependencies
- runtime configuration variables
The deployment endpoint does not mark a node as synchronized.
Synchronization only occurs after sensors successfully reconnect and report the expected configuration revision through heartbeats.
---
# Authentication Model
## Node Authentication
HoneyWire uses API-key-based node authentication.
Nodes authenticate using:
```http
Authorization: Bearer <NODE_API_KEY>
```
The Hub derives node identity internally from the API key.
Sensors never provide or control their own Node ID.
---
## Sensor Runtime Variables
Sensors only require the following runtime variables:
```text
HW_HUB_ENDPOINT
HW_HUB_KEY
HW_SENSOR_ID
HW_CONFIG_REV
HW_TEST_MODE
```
Node identity is resolved server-side by the Hub.
---
# Manual Deployment Workflow
## Typical Operator Flow
### 1. Create Node
The operator creates a node through the Fleet UI.
### 2. Configure Sensors
Sensors are selected from the Sensor Catalog and assigned to the node.
### 3. Generate Deployment
The node retrieves its deployment bundle from:
```http
GET /api/v1/nodes/compose
```
### 4. Deploy Compose Stack
The generated compose file is deployed to the target infrastructure host.
### 5. Reconciliation
Sensors begin sending authenticated heartbeats containing:
```text
HW_CONFIG_REV
```
The Hub validates the revision and marks the node as synchronized.
---
# Preview vs Deployment
| Feature | Preview Generator | Node Deployment |
|---|---|---|
| Endpoint | `POST /api/v1/compose/generate` | `GET /api/v1/nodes/compose` |
| Purpose | UI compose preview | Production deployment generation |
| Authentication | UI Session | Node API Key |
| State Tracking | No | Yes |
| Revision Injection | No | Yes |
| Database-backed | No | Yes |
---
# Architecture Summary
HoneyWire follows a centralized reconciliation architecture:
- the Hub stores desired infrastructure state
- nodes authenticate using API keys
- deployments are generated dynamically
- sensors report active runtime revisions
- synchronization is verified through telemetry
This ensures:
- deterministic deployments
- drift detection
- centralized configuration management
- secure node authentication
- reproducible infrastructure state
@@ -0,0 +1,69 @@
# Read-Models & Projections
To keep dashboard widgets fast and lightweight, the backend employs a CQRS-style (Command Query Responsibility Segregation) Read/Analytics Layer. This offloads all heavy processing, aggregation, and filtering from the frontend, ensuring it never has to traverse massive arrays of raw events.
## Architectural Goals
- **Backend Authority:** Eliminate frontend traversal of raw event arrays.
- **Contextual Filtering:** Push all filtering (timeframe, node, sensor, archive mode) to the database and projection builders.
- **Immutable Snapshots:** Serve pre-aggregated, flat data structures (DTOs) that represent an immutable analytics snapshot.
- **Lightweight UI:** Keep Vue components strictly rendering-focused.
## Projection Pipeline
The backend projection architecture operates in four distinct phases:
```text
Raw Events
Backend Filtering (timeframe/node/sensor/archive)
Backend Aggregation (Calculator)
Flat Projection DTO
```
### Components
1. **DTO Layer (`dto.go`):** Defines the flat API contract returned to the frontend. There are no nested properties—only simple, primitive values (e.g., `Total`, `Critical`, `High`).
2. **Calculator Layer (`calculator.go`):** Contains pure aggregation logic. It iterates over the filtered events, performs counting, and returns the aggregated data. It has **no** database, HTTP, or frontend concerns.
3. **Projection Layer (`projection.go`):** Orchestrates the pipeline. It accepts filtering context, fetches the relevant subset of events from the storage layer, passes them to the calculator, and returns the composed DTO.
4. **API Handler (`analytics.go`):** A thin HTTP adapter that parses query parameters and invokes the projection builder.
## Example: Severity Analytics
The Severity Projection provides a comprehensive breakdown of event severities based on active filters.
### API Endpoint
`GET /api/v1/events/severity`
**Query Parameters:**
- `timeframe`: The time window (`alltime`, `24H`, etc.) (default: `alltime`)
- `node`: Filter by node ID (optional)
- `sensor`: Filter by sensor ID (optional)
- `viewingArchive`: Include archived events (`true` or `false`) (default: `false`)
**Example Response:**
```json
{
"timeframe": "24H",
"total": 150,
"critical": 10,
"high": 40,
"medium": 50,
"low": 30,
"info": 20
}
```
## WebSocket Flow & Invalidation
To maintain real-time reactivity without frontend recomputation:
1. A new event triggers a WebSocket broadcast (`NEW_EVENT`).
2. The frontend Vue store receives the event and checks if it falls within the currently active filter context.
3. If relevant, the frontend simply **invalidates** the current projection and re-fetches `/api/v1/events/severity`.
4. The Vue Store replaces the snapshot reference, and the chart re-renders effortlessly.
This guarantees that the backend always remains the single source of truth for analytics.
@@ -0,0 +1,161 @@
# HoneyWire Hub Deployment Architecture
## Overview
HoneyWire uses a centralized Hub to manage node provisioning, sensor configuration, deployment generation, and configuration reconciliation.
Each managed infrastructure node receives:
- a unique Node ID
- a generated Node API Key
- a dynamic deployment bundle generated by the Hub
The Hub acts as the single source of truth for all deployed sensors and runtime configuration state.
---
# Configuration State
## Pending Configuration
Whenever sensors are:
- added
- removed
- edited
the Hub sets:
```text
nodes.pendingConfig = true
```
This indicates the deployed infrastructure configuration no longer matches the desired configuration stored in the Hub.
---
## Configuration Revisions
Every generated deployment bundle receives a unique configuration revision:
```text
HW_CONFIG_REV=rev_xxxxxxxx
```
rev_xxxxxxxx is a deterministic 12-char string for tracking config state.
This revision is embedded into all deployed sensors.
### Purpose
Configuration revisions allow the Hub to:
- detect deployment drift
- verify successful synchronization
- reconcile desired vs active state
---
## Active Revision
Once the last deployed sensor begins reporting heartbeats with the expected revision:
```text
nodes.activeRevision = desiredRevision
nodes.pendingConfig = false
```
The node is considered synchronized.
---
# Authentication Model
## Node Authentication
HoneyWire uses API-key-based node authentication.
Nodes authenticate using:
```http
Authorization: Bearer <NODE_API_KEY>
```
The Hub derives node identity internally from the API key.
Sensors never provide or control their own Node ID.
---
## Sensor Runtime Variables
Sensors only require the following runtime variables:
```text
HW_HUB_ENDPOINT
HW_HUB_KEY
HW_SENSOR_ID
HW_CONFIG_REV
HW_TEST_MODE
```
Node identity is resolved server-side by the Hub.
---
# Manual Deployment Workflow
## Typical Operator Flow
### 1. Create Node
The operator creates a node through the Fleet UI.
### 2. Configure Sensors
Sensors are selected from the Sensor Catalog and assigned to the node.
### 3. Generate Deployment
The node retrieves its deployment bundle from the deployment generation API endpoint.
### 4. Deploy Compose Stack
The generated compose file is deployed to the target infrastructure host.
### 5. Reconciliation
Sensors begin sending authenticated heartbeats containing:
```text
HW_CONFIG_REV
```
The Hub validates the revision and marks the node as synchronized.
---
# Preview vs Deployment
The Hub distinguishes between preview generation and production deployment:
- **Preview Generator** is used by the UI to show operators the compose preview. It is authenticated via the UI session and does not inject state tracking revisions.
- **Node Deployment** is used by the nodes to fetch their production configuration. It is authenticated via the Node API Key, tracks state, and injects revisions into the environment for synchronization.
---
# Architecture Summary
HoneyWire follows a centralized reconciliation architecture:
- the Hub stores desired infrastructure state
- nodes authenticate using API keys
- deployments are generated dynamically
- sensors report active runtime revisions
- synchronization is verified through telemetry
This ensures:
- deterministic deployments
- drift detection
- centralized configuration management
- secure node authentication
- reproducible infrastructure state
+48
View File
@@ -0,0 +1,48 @@
# Domain Services & Orchestration
The `internal/services` layer is the brain of the HoneyWire Hub. It contains the actual "verbs" of the system and is strictly framework-agnostic. Services define their own dependencies via narrow interfaces, ensuring high testability and clear boundaries.
## Background Workers
Workers are decoupled from the HTTP transport layer and managed exclusively via the `context.Context` instantiated in `main.go`.
| Worker | Location | Purpose |
|---|---|---|
| **Health Monitor** | `services/sensor` | Polls every 30s. If a sensor misses heartbeats > 60s, updates status to `down` and broadcasts a WS update. |
| **Event Retention** | `services/event` | Wakes hourly to delete/archive events older than configured thresholds to prevent DB bloat. |
| **Chart Sync** | `services/websocket` | Emits an empty payload every 30s telling the UI to tick its time-series charts forward smoothly. |
| **Auth Sweeper** | `services/auth` | Cleans up expired sessions and brute-force IP lockout maps to prevent memory leaks. |
| **SIEM Forwarder** | `services/siem` | Drains the in-memory event channel over TCP/UDP to external log aggregators. |
| **Notifier** | `services/notify` | Drains the webhook channel to Slack/Discord/Gotify, preventing external API latency from blocking HoneyWire HTTP responses. |
## Data Flow Example (Event Processing)
To understand how the layers interact, consider the lifecycle of an intrusion event when a sensor POSTs to `/api/v1/event`:
1. **Router (`internal/api/router.go`):** Matches the route and invokes `AgentAuthMiddleware`.
2. **Middleware:**
- Reads the `X-Api-Key` or `Bearer` token.
- Calls `authService.AuthenticateNodeRequest()`.
- Injects the authenticated `NodeID` into the request `Context`.
3. **API Handler (`internal/api/events.go`):**
- Parses the JSON body into a `models.Event`.
- Extracts `NodeID` from the context.
- Calls `eventService.ProcessEvent(event, nodeID)`.
4. **Event Service (`internal/services/event/service.go`):**
- Verifies version compatibility.
- Calls `store.InsertEvent()`.
- Calls `store.UpdateNodeLastHeartbeat()`.
- Checks `store.IsSensorSilenced()`. If false, triggers `notifyService.Dispatch()`.
- Sends the event to the SIEM via `siemService.QueueEvent()`.
- Broadcasts the update to the UI via `broadcaster.Broadcast("NEW_EVENT")`.
5. **API Handler:** Returns a clean `HTTP 200 OK`.
## Developer Guide: Adding a New Feature
When extending the backend, strict adherence to the established flow is required:
1. **Models:** Define your data structures in `models/`.
2. **Store:** Write the SQL queries in `store/`. Update the interface definition at the top of your target Service.
3. **Service:** Write the business logic in `services/<domain>/service.go`. Handle all errors, business validation, and side effects here.
4. **API Handler:** Write a thin HTTP wrapper in `api/<domain>.go`. Use `api.RespondError` and `api.SendJSON`.
5. **Router & Main:** Register the route in `api/router.go` and wire the dependencies in `cmd/hub/main.go`.
@@ -0,0 +1,36 @@
# Frontend Components
The HoneyWire frontend enforces a strict separation of concerns. UI rendering, layout orchestration, and ephemeral UI state belong in the component layer, while all business logic, filtering, and data normalization are pushed down to the Pinia stores.
## Component Categories
Components are divided into functional categories to maximize reusability and maintainability:
- **Dashboard Widgets (`components/dashboard/` `components/nodedetails/` ...):** Data-driven, presentation-focused charts and tables. They are generally stateless and reactive to projection updates (e.g., `EventTable`, `SeverityChart`).
- **UI Primitives (`components/ui/`):** Reusable design-system elements built strictly around our OKLCH token system. They are grouped into:
- **Branding:** Logos and theme elements.
- **Feedback:** Alerts, modals, toasts, and status indicators.
- **Forms:** Inputs, buttons, and drop-down selectors.
- **Layout:** Cards, widgets, and page shells.
- **Navigation:** Menus, sidebars, and top navigation.
## Component Rules
Our core architectural principle is that the component layer must never bypass the store layer.
### Components
| ✅ Do | ❌ Don't |
|---|---|
| Stay presentation-focused | Import the API client (`client.ts`) |
| Receive data via props or stores | Call `fetch()` or `api.get()` |
| Emit actions upward | Own domain/business logic |
| Remain highly reusable | Normalize or map backend JSON payloads |
### Views
Views act as orchestrators for pages (e.g., `Dashboard.vue`, `NodeDetails.vue`).
| ✅ Do | ❌ Don't |
|---|---|
| Own ephemeral UI state (e.g., "is the modal open?") | Perform backend mutations directly |
| Delegate heavy logic to Pinia stores | Duplicate error rollback logic |
| Orchestrate layout and pass down props | Normalize API responses |
+113
View File
@@ -0,0 +1,113 @@
# Hub Frontend Architecture & Design
The HoneyWire Hub frontend is a Vue 3 application built on a strict, layered architecture. It prioritizes predictable state management, real-time synchronization, and deterministic rendering. It strictly separates raw operational state from analytical dashboards.
## Technology Stack
The HoneyWire frontend leverages a modern, highly reactive stack optimized for real-time telemetry and predictable state management:
- **Vue 3 (Composition API):** Chosen for its lightweight and granular reactivity system, which is crucial for handling high-frequency WebSocket deltas without triggering unnecessary component re-renders.
- **TypeScript:** Adopted to ensure type safety across complex Domain objects (Events, Nodes, Sensors) and to prevent runtime errors when consuming dynamic backend schemas.
- **Pinia:** The official Vue state management library. It provides central, deterministic state ownership and robust support for TypeScript, making it ideal for managing our dual state models (Entity vs. Projection).
- **Vite:** Selected for its fast cold starts and Hot Module Replacement (HMR), significantly improving the developer experience.
- **TailwindCSS:** Provides a utility-first styling approach, allowing us to enforce a strict, semantic design system.
## Layered Architecture
The application enforces unidirectional dependencies:
```text
┌─────────────────────────────────────────┐
│ Views & Components │
│ (UI rendering + ephemeral state) │
├─────────────────────────────────────────┤
│ Stores (Pinia) │
│ (Business logic + state ownership) │
├─────────────────────────────────────────┤
│ API Client · WebSocket Service │
│ (Transport + error handling) │
├─────────────────────────────────────────┤
│ Utils & Helpers │
│ (Shared functions) │
└─────────────────────────────────────────┘
```
**Key Rules:**
- Views never call APIs directly.
- Stores never import Vue or manage UI state.
- Services never touch component state.
## Core Principles
1. **Deterministic Rendering:** The same state always produces the same UI output.
2. **Centralized State Ownership:** Pinia Stores are the single source of truth.
3. **Selective Optimistic Responsiveness:** Lightweight toggles (e.g., sensor silence, mark-as-read) update the UI immediately with asynchronous confirmation. Most standard mutations (e.g., deploying sensors, archiving events) wait for the API to confirm before updating state.
4. **Rollback Safety:** Where optimistic updates are used, there is a clear undo path on failure.
5. **Reactive Identity Stability:** Array and object references are preserved; they are mutated in-place to ensure Vue watchers do not break.
6. **Normalized Boundaries:** Data is normalized once at the store entry point.
7. **Backend-Owned Analytics:** Aggregations and projections are computed server-side, never derived from raw frontend entity traversal. *(See [Exception regarding events](#known-architectural-exception-event-pagination))*
## State Models
The frontend maintains two fundamentally different state models:
### 1. Entity State (Mutable)
Examples: `nodes[]`, `events[]`
- **Characteristics:** Long-lived, incrementally mutated, identity-preserving.
- **Rules:** Never replace array references. Merge data in-place using `splice` or `push`.
- **Purpose:** Efficient real-time synchronization of raw operational data via WebSocket deltas.
### 2. Projection State (Immutable)
Examples: `severityProjection`, `threatVelocityProjection`
- **Characteristics:** Backend-generated flat DTO snapshots. Dependent on the active UI filter context.
- **Rules:** Reference replacement is intentional. No deep watchers, local aggregation, or array traversal.
- **Purpose:** Efficient rendering of authoritative backend analytics.
## Data Flow Lifecycle
Data flows predictably through the system:
**1. User Action (Optimistic Update)**
```text
View clicks action → Store saves previous state → Store updates state optimistically → Store calls API
↳ If API succeeds: Do nothing (UI already updated).
↳ If API fails: Rollback state to previous, show error toast.
```
**2. API Fetch**
```text
Component requests data → Store calls API → Normalize payload → Merge with existing state (in-place) → UI re-renders.
```
## Realtime & WebSocket Integration
The system balances authoritative REST API data with real-time WebSocket deltas:
- **API Data (Authoritative):** Represents the complete backend state at fetch time. It is used for cold boots, manual refreshes, and critical mutations.
- **WebSocket Data (Realtime Delta):** Incremental and event-driven. The WebSocket layer is entirely decoupled from Vue/Pinia via the `HoneyWireWS` class. `App.vue` registers handlers that route parsed messages into the respective Pinia store (`handleWsEvent` or `handleWsUpdate`).
### Projection Invalidation Strategy
Because projections are immutable snapshots, they cannot be updated incrementally via WebSocket deltas like entity arrays. When a `NEW_EVENT` arrives via WebSocket:
1. The Store evaluates if the event falls within the current active filter context (timeframe, node, sensor).
2. If relevant, the Store aborts any in-flight requests (using `AbortController`) and re-fetches the updated projection snapshot from the API.
3. The projection reference is replaced, and the chart re-renders.
This design prevents frontend aggregation drift and ensures the backend remains the sole authority on analytics.
## Persistence Boundaries
To understand state durability:
- **Session Cookie:** Authentication (Persistent, HTTP-Only).
- **Frontend Store (Ephemeral):** Fleet state, events, and projections are rehydrated via API bootstrap on page load.
- **Local Storage:** Used exclusively for saving the user's theme preference (Light/Dark mode). It is explicitly *not* used for storing events, fleet data, timeframe selections, or sidebar states.
## Known Architectural Exception: Event Pagination
HoneyWire adheres to the principle of **Backend-Owned Analytics**, meaning heavy filtering and aggregation should always occur server-side (as seen with our immutable Projection DTOs).
**Exception:** Currently, **event pagination is handled entirely on the frontend**.
This decision was explicitly chosen to prioritize initial simplicity and speed of development. By pulling events and paginating them in the Vue layer, we avoided writing complex cursor or offset-based pagination in the initial iteration of the SQLite persistence layer.
**Future Migration Path:**
This is considered technical debt. If frontend performance bottlenecks arise due to memory overhead (e.g., traversing thousands of events in the browser), or if HoneyWire adopters request it for scale, the event pagination logic will be migrated to the backend to match the architecture of our dashboard projections.
+52
View File
@@ -0,0 +1,52 @@
# State Management & Stores
HoneyWire uses three primary Pinia stores to manage its domain state. Components delegate all business logic, filtering, and network interactions to these stores.
## 1. App Store (`app.ts`)
**Ownership:** UI navigation, authentication, and overall system state.
- **State:** `isAuthenticated`, `requiresSetup`, `currentView`, `sidebarOpen`, `isArmed`, `activeTimeframe`.
- **Actions:** Login/Logout, completing the initial setup, toggling the system arming state.
- **Note:** `isAuthenticated` acts as a final gatekeeper. During a cold boot, it is only set to `true` *after* all underlying data (fleet, events) has finished loading.
## 2. Fleet Store (`fleet.ts`)
**Ownership:** Infrastructure state (Nodes, sensors, uptime, deployment metadata).
- **Structure:** Contains a normalized array of nodes, each containing its respective `installedSensors`.
- **Computed Maps:** Maintains O(1) lookup maps (e.g., `getNode(nodeId)`).
- **Composite Keys:** Sensors are only unique within a node. The store strictly uses the composite key `nodeId + sensorId`.
- **Actions:** Fetches node lists, handles sensor creation/deletion, and optimistic updates for toggling sensor silence.
## 3. Events Store (`events.ts`)
**Ownership:** Telemetry state, intrusion events, and unread tracking.
- **Structure:** Array of normalized event objects and unread counters.
- **Filtering:** All filtering by archive mode, selected node, or selected sensor occurs *reactively inside the store*. Components do not implement filtering logic.
- **Actions:** Fetching events based on context, marking as read, and appending new incoming WebSocket events.
## Reactive Identity Preservation
A critical pattern in the HoneyWire frontend is preserving Vue 3's reactive object identity.
**Rule:** Never reassign arrays or objects in the store. Always mutate in-place.
```typescript
// ❌ WRONG: Breaks watchers and computed properties
nodes.value = incomingNodes.map(normalizeNode)
// ✅ CORRECT: Mutates in-place, preserving identity
nodes.value.splice(0, nodes.value.length)
incomingNodes.forEach(n => nodes.value.push(normalizeNode(n)))
```
## Error Handling & Rollback
Because HoneyWire relies on Optimistic Updates for a snappy UI, errors must be handled gracefully:
1. **Save State:** `const previous = sensor.isSilenced`
2. **Optimistic Update:** `sensor.isSilenced = targetState` (UI updates instantly)
3. **API Call:** `await api.patch(...)`
4. **Rollback on Error:** In the `catch` block, `sensor.isSilenced = previous`
+1 -1
View File
@@ -63,7 +63,7 @@ func initializeDefaultConfigTx(tx *sql.Tx) error {
"auto_purge_days": "0",
"siem_address": "",
"siem_protocol": "tcp",
// TODO: Add "registry_url": "https://raw.githubusercontent.com/andreicscs/HoneyWire/registry-pages" default
"registry_url": "https://raw.githubusercontent.com/andreicscs/HoneyWire/registry-pages",
}
stmt, err := tx.Prepare("INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)")
+1 -1
View File
@@ -1,6 +1,6 @@
.PHONY: tag-sensors
# Usage: make tag-sensors VERSION=v2.1.0
# Release all sensors at once by running: make tag-sensors VERSION=v2.1.0
tag-sensors:
@if [ -z "$(VERSION)" ]; then \
echo "❌ Error: VERSION is not set. Usage: make tag-sensors VERSION=v2.1.0"; \