spindb 0.32.2 → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +105 -857
- package/cli/commands/create.ts +5 -1
- package/cli/commands/engines.ts +78 -1
- package/cli/commands/menu/backup-handlers.ts +9 -0
- package/cli/commands/menu/container-handlers.ts +2 -0
- package/cli/commands/menu/engine-handlers.ts +4 -0
- package/cli/commands/menu/settings-handlers.ts +3 -0
- package/cli/commands/menu/shell-handlers.ts +43 -1
- package/cli/constants.ts +4 -0
- package/cli/helpers.ts +73 -0
- package/config/backup-formats.ts +14 -0
- package/config/engine-defaults.ts +13 -0
- package/config/engines.json +17 -0
- package/core/config-manager.ts +10 -0
- package/core/dependency-manager.ts +2 -0
- package/core/docker-exporter.ts +13 -0
- package/engines/index.ts +4 -0
- package/engines/influxdb/README.md +180 -0
- package/engines/influxdb/api-client.ts +64 -0
- package/engines/influxdb/backup.ts +160 -0
- package/engines/influxdb/binary-manager.ts +110 -0
- package/engines/influxdb/binary-urls.ts +69 -0
- package/engines/influxdb/hostdb-releases.ts +23 -0
- package/engines/influxdb/index.ts +1227 -0
- package/engines/influxdb/restore.ts +417 -0
- package/engines/influxdb/version-maps.ts +75 -0
- package/engines/influxdb/version-validator.ts +128 -0
- package/package.json +2 -1
- package/types/index.ts +9 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# InfluxDB Engine Implementation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
InfluxDB 3.x is a time-series database rewritten in Rust. It uses a REST API for all operations (no CLI client). InfluxDB 3.x supports SQL queries via its HTTP API, unlike earlier versions which used InfluxQL/Flux.
|
|
6
|
+
|
|
7
|
+
## Platform Support
|
|
8
|
+
|
|
9
|
+
| Platform | Architecture | Status | Notes |
|
|
10
|
+
|----------|--------------|--------|-------|
|
|
11
|
+
| darwin | x64 | Supported | Uses hostdb binaries |
|
|
12
|
+
| darwin | arm64 | Supported | Uses hostdb binaries (Apple Silicon) |
|
|
13
|
+
| linux | x64 | Supported | Uses hostdb binaries |
|
|
14
|
+
| linux | arm64 | Supported | Uses hostdb binaries |
|
|
15
|
+
| win32 | x64 | Supported | Uses hostdb binaries |
|
|
16
|
+
|
|
17
|
+
## Binary Packaging
|
|
18
|
+
|
|
19
|
+
### Archive Format
|
|
20
|
+
- **Unix (macOS/Linux)**: `tar.gz`
|
|
21
|
+
- **Windows**: `zip`
|
|
22
|
+
|
|
23
|
+
### Archive Structure
|
|
24
|
+
```text
|
|
25
|
+
influxdb/
|
|
26
|
+
├── influxdb3 # Server binary
|
|
27
|
+
├── python/ # Bundled Python runtime
|
|
28
|
+
│ └── lib/
|
|
29
|
+
│ └── libpython3.13.dylib
|
|
30
|
+
├── LICENSE-APACHE
|
|
31
|
+
└── LICENSE-MIT
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Binary + Python Runtime
|
|
35
|
+
InfluxDB 3.x ships as a single `influxdb3` binary that acts as the server, bundled with a Python runtime. The binary uses `@executable_path/python/lib/libpython3.13.dylib`, so the `python/` directory must be co-located with the binary. The custom `moveExtractedEntries` override ensures both end up in `bin/`. There is no separate CLI client — all interactions use the REST API.
|
|
36
|
+
|
|
37
|
+
### Version Map Sync
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
export const INFLUXDB_VERSION_MAP: Record<string, string> = {
|
|
41
|
+
'3': '3.8.0',
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Implementation Details
|
|
46
|
+
|
|
47
|
+
### Binary Manager
|
|
48
|
+
|
|
49
|
+
InfluxDB uses `BaseBinaryManager` since it's a server-based engine with single-digit major versions:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
class InfluxDBBinaryManager extends BaseBinaryManager {
|
|
53
|
+
protected readonly config = {
|
|
54
|
+
engine: Engine.InfluxDB,
|
|
55
|
+
engineName: 'influxdb',
|
|
56
|
+
displayName: 'InfluxDB',
|
|
57
|
+
serverBinary: 'influxdb3',
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### REST API Engine
|
|
63
|
+
|
|
64
|
+
InfluxDB is a **REST API engine**:
|
|
65
|
+
- `spindb run` is **NOT applicable** (scriptFileLabel is `null`)
|
|
66
|
+
- `spindb connect` opens the health endpoint info in terminal
|
|
67
|
+
- All operations use HTTP REST API
|
|
68
|
+
|
|
69
|
+
### Default Configuration
|
|
70
|
+
|
|
71
|
+
- **Default Port**: 8086
|
|
72
|
+
- **Health Endpoint**: `GET /health`
|
|
73
|
+
- **SQL Query Endpoint**: `POST /api/v3/query_sql`
|
|
74
|
+
- **Write Endpoint**: `POST /api/v3/write_lp`
|
|
75
|
+
- **No Authentication**: InfluxDB 3.x local dev has no auth by default
|
|
76
|
+
- **PID File**: `influxdb.pid` in container directory
|
|
77
|
+
|
|
78
|
+
### Database Creation
|
|
79
|
+
|
|
80
|
+
InfluxDB 3.x creates databases **implicitly on first write**. There is no explicit `CREATE DATABASE` command. When you write data with a database name, it's auto-created.
|
|
81
|
+
|
|
82
|
+
### Connection String Format
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
http://127.0.0.1:{port}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Backup & Restore
|
|
89
|
+
|
|
90
|
+
### Backup Formats
|
|
91
|
+
|
|
92
|
+
| Format | Extension | Method | Notes |
|
|
93
|
+
|--------|-----------|--------|-------|
|
|
94
|
+
| sql | `.sql` | REST API | SQL dump with CREATE TABLE + INSERT statements |
|
|
95
|
+
|
|
96
|
+
### Backup Method
|
|
97
|
+
|
|
98
|
+
Uses InfluxDB's SQL query API to export data:
|
|
99
|
+
1. `SHOW TABLES` — lists all tables/measurements
|
|
100
|
+
2. `SELECT * FROM {table}` — exports all data per table
|
|
101
|
+
3. Generates SQL INSERT statements for restore
|
|
102
|
+
|
|
103
|
+
### Restore Method
|
|
104
|
+
|
|
105
|
+
Parses SQL dump file and executes statements via `POST /api/v3/query_sql`.
|
|
106
|
+
|
|
107
|
+
## Integration Test Notes
|
|
108
|
+
|
|
109
|
+
### REST API Testing
|
|
110
|
+
|
|
111
|
+
Integration tests use `fetch()` to interact with InfluxDB REST API.
|
|
112
|
+
|
|
113
|
+
### Test Fixtures
|
|
114
|
+
|
|
115
|
+
Located in `tests/fixtures/influxdb/seeds/`:
|
|
116
|
+
- `README.md` documenting the API-based approach
|
|
117
|
+
|
|
118
|
+
## Known Issues & Gotchas
|
|
119
|
+
|
|
120
|
+
### 1. No CLI Client
|
|
121
|
+
|
|
122
|
+
InfluxDB 3.x has no bundled CLI client. All operations use the HTTP REST API. The `clientTools` array in engine-defaults is empty.
|
|
123
|
+
|
|
124
|
+
### 2. Implicit Database Creation
|
|
125
|
+
|
|
126
|
+
Databases are created on first write, not via explicit commands. `createDatabase()` verifies server health but doesn't create anything.
|
|
127
|
+
|
|
128
|
+
### 3. SQL Query Support
|
|
129
|
+
|
|
130
|
+
InfluxDB 3.x supports SQL queries (not InfluxQL or Flux from v1/v2). Query via:
|
|
131
|
+
```bash
|
|
132
|
+
curl -X POST http://localhost:8086/api/v3/query_sql \
|
|
133
|
+
-H "Content-Type: application/json" \
|
|
134
|
+
-d '{"db":"mydb","q":"SELECT * FROM measurement","format":"json"}'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 4. Write via Line Protocol
|
|
138
|
+
|
|
139
|
+
Data writes use InfluxDB line protocol format:
|
|
140
|
+
```bash
|
|
141
|
+
curl -X POST "http://localhost:8086/api/v3/write_lp?db=mydb" \
|
|
142
|
+
-H "Content-Type: text/plain" \
|
|
143
|
+
-d 'measurement,tag=value field=123'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 5. Windows PID Handling
|
|
147
|
+
|
|
148
|
+
On Windows, uses `platformService.findProcessByPort(port)` after startup to find the real PID, similar to QuestDB/TypeDB pattern.
|
|
149
|
+
|
|
150
|
+
## REST API Quick Reference
|
|
151
|
+
|
|
152
|
+
### Health
|
|
153
|
+
```bash
|
|
154
|
+
GET /health
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Query (SQL)
|
|
158
|
+
```bash
|
|
159
|
+
POST /api/v3/query_sql
|
|
160
|
+
Content-Type: application/json
|
|
161
|
+
{"db":"mydb","q":"SELECT 1","format":"json"}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Write (Line Protocol)
|
|
165
|
+
```bash
|
|
166
|
+
POST /api/v3/write_lp?db=mydb
|
|
167
|
+
Content-Type: text/plain
|
|
168
|
+
measurement,tag=value field=123
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Show Tables
|
|
172
|
+
```bash
|
|
173
|
+
POST /api/v3/query_sql
|
|
174
|
+
{"db":"mydb","q":"SHOW TABLES","format":"json"}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### List Databases
|
|
178
|
+
```bash
|
|
179
|
+
GET /api/v3/configure/database?format=json
|
|
180
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared InfluxDB REST API client utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Make an HTTP request to InfluxDB REST API
|
|
7
|
+
*
|
|
8
|
+
* @param port - The HTTP port InfluxDB is listening on
|
|
9
|
+
* @param method - HTTP method (GET, POST, PUT, DELETE)
|
|
10
|
+
* @param path - API path (e.g., '/health', '/api/v3/query_sql')
|
|
11
|
+
* @param body - Optional body: object for JSON, string for text/plain (line protocol)
|
|
12
|
+
* @param timeoutMs - Request timeout in milliseconds (default: 30s)
|
|
13
|
+
*/
|
|
14
|
+
export async function influxdbApiRequest(
|
|
15
|
+
port: number,
|
|
16
|
+
method: string,
|
|
17
|
+
path: string,
|
|
18
|
+
body?: Record<string, unknown> | string,
|
|
19
|
+
timeoutMs = 30000,
|
|
20
|
+
): Promise<{ status: number; data: unknown }> {
|
|
21
|
+
const url = `http://127.0.0.1:${port}${path}`
|
|
22
|
+
|
|
23
|
+
const controller = new AbortController()
|
|
24
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
|
25
|
+
|
|
26
|
+
const options: RequestInit = {
|
|
27
|
+
method,
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (body !== undefined) {
|
|
32
|
+
if (typeof body === 'string') {
|
|
33
|
+
options.headers = { 'Content-Type': 'text/plain' }
|
|
34
|
+
options.body = body
|
|
35
|
+
} else {
|
|
36
|
+
options.headers = { 'Content-Type': 'application/json' }
|
|
37
|
+
options.body = JSON.stringify(body)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(url, options)
|
|
43
|
+
|
|
44
|
+
// Try to parse as JSON, fall back to text for endpoints like /health
|
|
45
|
+
let data: unknown
|
|
46
|
+
const contentType = response.headers.get('content-type') || ''
|
|
47
|
+
if (contentType.includes('application/json')) {
|
|
48
|
+
data = await response.json()
|
|
49
|
+
} else {
|
|
50
|
+
data = await response.text()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { status: response.status, data }
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`InfluxDB API request timed out after ${timeoutMs / 1000}s: ${method} ${path}`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
throw error
|
|
61
|
+
} finally {
|
|
62
|
+
clearTimeout(timeoutId)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluxDB backup module
|
|
3
|
+
* Supports SQL-based backup using InfluxDB's REST API to export data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { mkdir, stat, writeFile } from 'fs/promises'
|
|
7
|
+
import { existsSync } from 'fs'
|
|
8
|
+
import { dirname } from 'path'
|
|
9
|
+
import { logDebug } from '../../core/error-handler'
|
|
10
|
+
import { influxdbApiRequest } from './api-client'
|
|
11
|
+
import type { ContainerConfig, BackupOptions, BackupResult } from '../../types'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create an SQL backup using InfluxDB's REST API
|
|
15
|
+
* Queries all tables and exports data as SQL INSERT statements
|
|
16
|
+
*/
|
|
17
|
+
export async function createBackup(
|
|
18
|
+
container: ContainerConfig,
|
|
19
|
+
outputPath: string,
|
|
20
|
+
options: BackupOptions,
|
|
21
|
+
): Promise<BackupResult> {
|
|
22
|
+
const { port } = container
|
|
23
|
+
const database = options.database || container.database
|
|
24
|
+
|
|
25
|
+
// Ensure output directory exists
|
|
26
|
+
const outputDir = dirname(outputPath)
|
|
27
|
+
if (!existsSync(outputDir)) {
|
|
28
|
+
await mkdir(outputDir, { recursive: true })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
logDebug(
|
|
32
|
+
`Creating InfluxDB SQL backup via REST API on port ${port} for database "${database}"`,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
// Get list of tables in the database
|
|
36
|
+
const tablesResponse = await influxdbApiRequest(
|
|
37
|
+
port,
|
|
38
|
+
'POST',
|
|
39
|
+
'/api/v3/query_sql',
|
|
40
|
+
{
|
|
41
|
+
db: database,
|
|
42
|
+
q: 'SHOW TABLES',
|
|
43
|
+
format: 'json',
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if (tablesResponse.status !== 200) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Failed to list tables: ${JSON.stringify(tablesResponse.data)}`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const tablesData = tablesResponse.data as Array<Record<string, unknown>>
|
|
54
|
+
const tables: string[] = []
|
|
55
|
+
|
|
56
|
+
// Extract user table names: include rows with 'iox' schema or no schema field,
|
|
57
|
+
// skip system schemas (information_schema, system, etc.)
|
|
58
|
+
if (Array.isArray(tablesData)) {
|
|
59
|
+
for (const row of tablesData) {
|
|
60
|
+
const schema = row.table_schema as string | undefined
|
|
61
|
+
if (schema && schema !== 'iox') continue
|
|
62
|
+
const tableName =
|
|
63
|
+
(row.table_name as string) ||
|
|
64
|
+
(row.name as string) ||
|
|
65
|
+
(Object.values(row)[0] as string)
|
|
66
|
+
if (tableName) {
|
|
67
|
+
tables.push(tableName)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logDebug(`Found ${tables.length} tables: ${tables.join(', ')}`)
|
|
73
|
+
|
|
74
|
+
// Build SQL dump
|
|
75
|
+
let sqlContent = `-- InfluxDB SQL Backup\n`
|
|
76
|
+
sqlContent += `-- Database: ${database}\n`
|
|
77
|
+
sqlContent += `-- Created: ${new Date().toISOString()}\n\n`
|
|
78
|
+
|
|
79
|
+
for (const table of tables) {
|
|
80
|
+
logDebug(`Exporting table: ${table}`)
|
|
81
|
+
|
|
82
|
+
// Query column metadata to identify tag columns
|
|
83
|
+
// Tags use Dictionary(Int32, Utf8) type in InfluxDB 3.x
|
|
84
|
+
const tagColumns: string[] = []
|
|
85
|
+
try {
|
|
86
|
+
const colResponse = await influxdbApiRequest(
|
|
87
|
+
port,
|
|
88
|
+
'POST',
|
|
89
|
+
'/api/v3/query_sql',
|
|
90
|
+
{
|
|
91
|
+
db: database,
|
|
92
|
+
q: `SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${table.replace(/'/g, "''")}'`,
|
|
93
|
+
format: 'json',
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
if (colResponse.status === 200 && Array.isArray(colResponse.data)) {
|
|
97
|
+
for (const col of colResponse.data as Array<Record<string, unknown>>) {
|
|
98
|
+
const dataType = String(col.data_type || '')
|
|
99
|
+
if (dataType.includes('Dictionary')) {
|
|
100
|
+
tagColumns.push(String(col.column_name))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
logDebug(`Warning: Could not query column metadata for ${table}`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Query all data from the table
|
|
109
|
+
const dataResponse = await influxdbApiRequest(
|
|
110
|
+
port,
|
|
111
|
+
'POST',
|
|
112
|
+
'/api/v3/query_sql',
|
|
113
|
+
{
|
|
114
|
+
db: database,
|
|
115
|
+
q: `SELECT * FROM "${table.replace(/"/g, '""')}"`,
|
|
116
|
+
format: 'json',
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if (dataResponse.status !== 200) {
|
|
121
|
+
logDebug(
|
|
122
|
+
`Warning: Failed to export table ${table}: ${JSON.stringify(dataResponse.data)}`,
|
|
123
|
+
)
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const rows = dataResponse.data as Array<Record<string, unknown>>
|
|
128
|
+
|
|
129
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
130
|
+
sqlContent += `-- Table: ${table}\n`
|
|
131
|
+
if (tagColumns.length > 0) {
|
|
132
|
+
sqlContent += `-- Tags: ${tagColumns.join(', ')}\n`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const row of rows) {
|
|
136
|
+
const columns = Object.keys(row)
|
|
137
|
+
const values = columns.map((col) => {
|
|
138
|
+
const val = row[col]
|
|
139
|
+
if (val === null || val === undefined) return 'NULL'
|
|
140
|
+
if (typeof val === 'number') return String(val)
|
|
141
|
+
if (typeof val === 'boolean') return val ? 'true' : 'false'
|
|
142
|
+
return `'${String(val).replace(/'/g, "''")}'`
|
|
143
|
+
})
|
|
144
|
+
sqlContent += `INSERT INTO "${table.replace(/"/g, '""')}" (${columns.map((c) => `"${c.replace(/"/g, '""')}"`).join(', ')}) VALUES (${values.join(', ')});\n`
|
|
145
|
+
}
|
|
146
|
+
sqlContent += '\n'
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Write SQL content to file
|
|
151
|
+
await writeFile(outputPath, sqlContent, 'utf-8')
|
|
152
|
+
|
|
153
|
+
const stats = await stat(outputPath)
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
path: outputPath,
|
|
157
|
+
format: 'sql',
|
|
158
|
+
size: stats.size,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluxDB Binary Manager
|
|
3
|
+
*
|
|
4
|
+
* Handles downloading, extracting, and managing InfluxDB binaries from hostdb.
|
|
5
|
+
* Extends BaseBinaryManager for shared download/extraction logic.
|
|
6
|
+
*
|
|
7
|
+
* InfluxDB 3.x archives extract to a flat `influxdb/` directory:
|
|
8
|
+
* influxdb/
|
|
9
|
+
* ├── influxdb3 (server binary)
|
|
10
|
+
* ├── python/ (bundled Python runtime)
|
|
11
|
+
* │ └── lib/
|
|
12
|
+
* │ └── libpython3.13.dylib
|
|
13
|
+
* ├── LICENSE-APACHE
|
|
14
|
+
* └── LICENSE-MIT
|
|
15
|
+
*
|
|
16
|
+
* The binary uses @executable_path/python/lib/libpython3.13.dylib, so python/
|
|
17
|
+
* must be in the same directory as the binary. We reorganize to:
|
|
18
|
+
* bin/
|
|
19
|
+
* ├── influxdb3
|
|
20
|
+
* └── python/ (co-located for @executable_path resolution)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { mkdir, readdir } from 'fs/promises'
|
|
24
|
+
import { join } from 'path'
|
|
25
|
+
import {
|
|
26
|
+
BaseBinaryManager,
|
|
27
|
+
type BinaryManagerConfig,
|
|
28
|
+
} from '../../core/base-binary-manager'
|
|
29
|
+
import { moveEntry } from '../../core/fs-error-utils'
|
|
30
|
+
import { logDebug } from '../../core/error-handler'
|
|
31
|
+
import { getBinaryUrl } from './binary-urls'
|
|
32
|
+
import { normalizeVersion } from './version-maps'
|
|
33
|
+
import { Engine, type Platform, type Arch } from '../../types'
|
|
34
|
+
|
|
35
|
+
class InfluxDBBinaryManager extends BaseBinaryManager {
|
|
36
|
+
protected readonly config: BinaryManagerConfig = {
|
|
37
|
+
engine: Engine.InfluxDB,
|
|
38
|
+
engineName: 'influxdb',
|
|
39
|
+
displayName: 'InfluxDB',
|
|
40
|
+
serverBinary: 'influxdb3',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected getBinaryUrlFromModule(
|
|
44
|
+
version: string,
|
|
45
|
+
platform: Platform,
|
|
46
|
+
arch: Arch,
|
|
47
|
+
): string {
|
|
48
|
+
return getBinaryUrl(version, platform, arch)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected normalizeVersionFromModule(version: string): string {
|
|
52
|
+
return normalizeVersion(version)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected parseVersionFromOutput(stdout: string): string | null {
|
|
56
|
+
// Extract version from output like "influxdb3 3.8.0" or "InfluxDB 3 Edge v3.8.0"
|
|
57
|
+
const match = stdout.match(/v?(\d+\.\d+\.\d+)/)
|
|
58
|
+
return match?.[1] ?? null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Override moveExtractedEntries to co-locate python/ with the binary.
|
|
63
|
+
*
|
|
64
|
+
* The influxdb3 binary references @executable_path/python/lib/libpython3.13.dylib,
|
|
65
|
+
* so the python/ directory must be inside bin/ alongside the binary.
|
|
66
|
+
* The default flat-structure handler would put python/ at binPath/python/ instead
|
|
67
|
+
* of binPath/bin/python/, causing a dylib load failure.
|
|
68
|
+
*/
|
|
69
|
+
protected override async moveExtractedEntries(
|
|
70
|
+
extractDir: string,
|
|
71
|
+
binPath: string,
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
const entries = await readdir(extractDir, { withFileTypes: true })
|
|
74
|
+
|
|
75
|
+
// Find the influxdb directory (e.g., "influxdb" or "influxdb-3.8.0")
|
|
76
|
+
const influxDir = entries.find(
|
|
77
|
+
(e) =>
|
|
78
|
+
e.isDirectory() &&
|
|
79
|
+
(e.name === 'influxdb' || e.name.startsWith('influxdb-')),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const sourceDir = influxDir ? join(extractDir, influxDir.name) : extractDir
|
|
83
|
+
const sourceEntries = influxDir
|
|
84
|
+
? await readdir(sourceDir, { withFileTypes: true })
|
|
85
|
+
: entries
|
|
86
|
+
|
|
87
|
+
// Create bin/ directory
|
|
88
|
+
const destBinDir = join(binPath, 'bin')
|
|
89
|
+
await mkdir(destBinDir, { recursive: true })
|
|
90
|
+
|
|
91
|
+
for (const entry of sourceEntries) {
|
|
92
|
+
const sourcePath = join(sourceDir, entry.name)
|
|
93
|
+
|
|
94
|
+
if (entry.name === 'influxdb3' || entry.name === 'influxdb3.exe') {
|
|
95
|
+
// Server binary → bin/
|
|
96
|
+
await moveEntry(sourcePath, join(destBinDir, entry.name))
|
|
97
|
+
} else if (entry.name === 'python') {
|
|
98
|
+
// Python runtime → bin/python/ (must be co-located with binary for @executable_path)
|
|
99
|
+
await moveEntry(sourcePath, join(destBinDir, 'python'))
|
|
100
|
+
} else {
|
|
101
|
+
// Licenses, metadata, etc. → binPath root
|
|
102
|
+
await moveEntry(sourcePath, join(binPath, entry.name))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
logDebug('InfluxDB binaries reorganized with python/ co-located in bin/')
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const influxdbBinaryManager = new InfluxDBBinaryManager()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { normalizeVersion } from './version-maps'
|
|
2
|
+
import { buildHostdbUrl } from '../../core/hostdb-client'
|
|
3
|
+
import { Engine, Platform, type Arch } from '../../types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Supported platform identifiers for hostdb downloads.
|
|
7
|
+
* hostdb uses standard Node.js platform naming - this set validates
|
|
8
|
+
* that a platform/arch combination is supported, not transforms it.
|
|
9
|
+
*/
|
|
10
|
+
const SUPPORTED_PLATFORMS = new Set([
|
|
11
|
+
'darwin-arm64',
|
|
12
|
+
'darwin-x64',
|
|
13
|
+
'linux-arm64',
|
|
14
|
+
'linux-x64',
|
|
15
|
+
'win32-x64',
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get the hostdb platform identifier
|
|
20
|
+
*
|
|
21
|
+
* hostdb uses standard platform naming that matches Node.js identifiers directly.
|
|
22
|
+
* This function validates the platform/arch combination is supported.
|
|
23
|
+
*
|
|
24
|
+
* @param platform - Node.js platform (e.g., 'darwin', 'linux', 'win32')
|
|
25
|
+
* @param arch - Node.js architecture (e.g., 'arm64', 'x64')
|
|
26
|
+
* @returns hostdb platform identifier or null if unsupported
|
|
27
|
+
*/
|
|
28
|
+
export function getHostdbPlatform(
|
|
29
|
+
platform: Platform,
|
|
30
|
+
arch: Arch,
|
|
31
|
+
): string | null {
|
|
32
|
+
const key = `${platform}-${arch}`
|
|
33
|
+
return SUPPORTED_PLATFORMS.has(key) ? key : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build the download URL for InfluxDB binaries from hostdb
|
|
38
|
+
*
|
|
39
|
+
* Format: https://github.com/robertjbass/hostdb/releases/download/influxdb-{version}/influxdb-{version}-{platform}-{arch}.{ext}
|
|
40
|
+
*
|
|
41
|
+
* @param version - InfluxDB version (e.g., '3', '3.8.0')
|
|
42
|
+
* @param platform - Platform identifier (e.g., 'darwin', 'linux', 'win32')
|
|
43
|
+
* @param arch - Architecture identifier (e.g., 'arm64', 'x64')
|
|
44
|
+
* @returns Download URL for the binary
|
|
45
|
+
*/
|
|
46
|
+
export function getBinaryUrl(
|
|
47
|
+
version: string,
|
|
48
|
+
platform: Platform,
|
|
49
|
+
arch: Arch,
|
|
50
|
+
): string {
|
|
51
|
+
const platformKey = `${platform}-${arch}`
|
|
52
|
+
const hostdbPlatform = getHostdbPlatform(platform, arch)
|
|
53
|
+
if (!hostdbPlatform) {
|
|
54
|
+
const supported = Array.from(SUPPORTED_PLATFORMS).join(', ')
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Unsupported platform: ${platformKey}. Supported platforms: ${supported}`,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Normalize version (handles major version lookup and X.Y -> X.Y.Z conversion)
|
|
61
|
+
const fullVersion = normalizeVersion(version)
|
|
62
|
+
const ext = platform === Platform.Win32 ? 'zip' : 'tar.gz'
|
|
63
|
+
|
|
64
|
+
return buildHostdbUrl(Engine.InfluxDB, {
|
|
65
|
+
version: fullVersion,
|
|
66
|
+
hostdbPlatform,
|
|
67
|
+
extension: ext,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hostdb Releases Module for InfluxDB
|
|
3
|
+
*
|
|
4
|
+
* Fetches InfluxDB binary information from the hostdb repository at
|
|
5
|
+
* https://github.com/robertjbass/hostdb
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createHostdbReleases } from '../../core/hostdb-releases-factory'
|
|
9
|
+
import { INFLUXDB_VERSION_MAP, SUPPORTED_MAJOR_VERSIONS } from './version-maps'
|
|
10
|
+
import { influxdbBinaryManager } from './binary-manager'
|
|
11
|
+
import { Engine } from '../../types'
|
|
12
|
+
|
|
13
|
+
const hostdbReleases = createHostdbReleases({
|
|
14
|
+
engine: Engine.InfluxDB,
|
|
15
|
+
displayName: 'InfluxDB',
|
|
16
|
+
versionMap: INFLUXDB_VERSION_MAP,
|
|
17
|
+
supportedMajorVersions: SUPPORTED_MAJOR_VERSIONS,
|
|
18
|
+
groupingStrategy: 'single-digit',
|
|
19
|
+
listInstalled: () => influxdbBinaryManager.listInstalled(),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const fetchAvailableVersions = hostdbReleases.fetchAvailableVersions
|
|
23
|
+
export const getLatestVersion = hostdbReleases.getLatestVersion
|