spindb 0.35.4 → 0.36.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/README.md +20 -9
- package/cli/commands/engines.ts +89 -435
- package/cli/commands/menu/backup-handlers.ts +5 -0
- package/cli/commands/menu/settings-handlers.ts +3 -0
- package/cli/commands/menu/shell-handlers.ts +40 -0
- package/cli/constants.ts +4 -0
- package/cli/helpers.ts +74 -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/credential-manager.ts +2 -0
- package/core/dependency-manager.ts +2 -0
- package/core/docker-exporter.ts +7 -0
- package/engines/index.ts +5 -0
- package/engines/weaviate/README.md +302 -0
- package/engines/weaviate/api-client.ts +61 -0
- package/engines/weaviate/backup.ts +145 -0
- package/engines/weaviate/binary-manager.ts +80 -0
- package/engines/weaviate/binary-urls.ts +115 -0
- package/engines/weaviate/cli-utils.ts +43 -0
- package/engines/weaviate/hostdb-releases.ts +23 -0
- package/engines/weaviate/index.ts +1139 -0
- package/engines/weaviate/restore.ts +235 -0
- package/engines/weaviate/version-maps.ts +78 -0
- package/engines/weaviate/version-validator.ts +128 -0
- package/package.json +2 -1
- package/types/index.ts +9 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Weaviate Engine Implementation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Weaviate is an AI-native vector database with REST and gRPC APIs. Like Qdrant and Meilisearch, it uses HTTP for all operations. Uses classes/collections instead of traditional databases.
|
|
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
|
+
weaviate/
|
|
26
|
+
└── bin/
|
|
27
|
+
└── weaviate # Server binary
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Version Map Sync
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
export const WEAVIATE_VERSION_MAP: Record<string, string> = {
|
|
34
|
+
'1': '1.35.7',
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Implementation Details
|
|
39
|
+
|
|
40
|
+
### Binary Manager
|
|
41
|
+
|
|
42
|
+
Weaviate uses `BaseBinaryManager` with a custom `verify()` override:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Weaviate doesn't support --version (as of v1.35.x)
|
|
46
|
+
// Verification just checks binary existence
|
|
47
|
+
async verify(): Promise<boolean> {
|
|
48
|
+
return existsSync(binaryPath)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
See: https://github.com/weaviate/weaviate/issues/6571
|
|
53
|
+
|
|
54
|
+
### Version Parsing
|
|
55
|
+
|
|
56
|
+
Not applicable for current version (no `--version` flag). The `parseVersionFromOutput` method is implemented for forward compatibility when the flag is added:
|
|
57
|
+
- **Parse pattern**: `/(?:weaviate\s+)?v?(\d+\.\d+\.\d+)/`
|
|
58
|
+
|
|
59
|
+
### REST API Engine
|
|
60
|
+
|
|
61
|
+
Weaviate is a **REST API engine** - it doesn't have a CLI shell:
|
|
62
|
+
- `spindb run` is **NOT applicable**
|
|
63
|
+
- `spindb connect` opens the web dashboard in browser
|
|
64
|
+
- All data operations use HTTP REST API
|
|
65
|
+
|
|
66
|
+
### Dual Ports
|
|
67
|
+
|
|
68
|
+
Weaviate uses two ports:
|
|
69
|
+
- **HTTP Port** (default 8080): REST API
|
|
70
|
+
- **gRPC Port** (default 8081): gRPC API (typically HTTP + 1)
|
|
71
|
+
|
|
72
|
+
### Default Configuration
|
|
73
|
+
|
|
74
|
+
- **Default HTTP Port**: 8080 (auto-increments on conflict)
|
|
75
|
+
- **gRPC Port**: HTTP port + 1
|
|
76
|
+
- **Health Endpoint**: `/v1/.well-known/ready`
|
|
77
|
+
- **Schema Endpoint**: `/v1/schema`
|
|
78
|
+
- **PID File**: `weaviate.pid` in container directory
|
|
79
|
+
|
|
80
|
+
### Environment Variable Configuration
|
|
81
|
+
|
|
82
|
+
Weaviate uses environment variables (not a config file):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
PERSISTENCE_DATA_PATH=/path/to/data
|
|
86
|
+
BACKUP_FILESYSTEM_PATH=/path/to/data/backups
|
|
87
|
+
QUERY_DEFAULTS_LIMIT=25
|
|
88
|
+
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true
|
|
89
|
+
DEFAULT_VECTORIZER_MODULE=none
|
|
90
|
+
ENABLE_MODULES=backup-filesystem
|
|
91
|
+
GRPC_PORT=8081
|
|
92
|
+
CLUSTER_HOSTNAME=node-{port} # Must be unique per container
|
|
93
|
+
CLUSTER_GOSSIP_BIND_PORT={port+100} # Memberlist gossip (default 7946)
|
|
94
|
+
CLUSTER_DATA_BIND_PORT={port+101} # Memberlist data (default 7947)
|
|
95
|
+
RAFT_PORT={port+200} # Raft consensus (default 8300)
|
|
96
|
+
RAFT_INTERNAL_RPC_PORT={port+201} # Raft internal RPC (default 8301)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Internal Cluster Ports
|
|
100
|
+
|
|
101
|
+
Weaviate uses 4 internal cluster ports in addition to HTTP and gRPC. These **must be unique per container** or Weaviate will fail to start (or silently conflict with other instances):
|
|
102
|
+
|
|
103
|
+
| Port | Default | SpinDB Formula | Purpose |
|
|
104
|
+
|------|---------|----------------|---------|
|
|
105
|
+
| HTTP | 8080 | `{port}` | REST API |
|
|
106
|
+
| gRPC | 8081 | `{port}+1` | gRPC API |
|
|
107
|
+
| Gossip | 7946 | `{port}+100` | Memberlist gossip |
|
|
108
|
+
| Data | 7947 | `{port}+101` | Memberlist data |
|
|
109
|
+
| Raft | 8300 | `{port}+200` | Raft consensus |
|
|
110
|
+
| Raft RPC | 8301 | `{port}+201` | Raft internal RPC |
|
|
111
|
+
|
|
112
|
+
### Connection String Format
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
http://127.0.0.1:{port}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Web Dashboard
|
|
119
|
+
|
|
120
|
+
The `connect` command opens the root URL in the default browser:
|
|
121
|
+
```text
|
|
122
|
+
http://localhost:{port}/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Backup & Restore
|
|
126
|
+
|
|
127
|
+
### Backup Formats
|
|
128
|
+
|
|
129
|
+
| Format | Extension | Tool | Notes |
|
|
130
|
+
|--------|-----------|------|-------|
|
|
131
|
+
| snapshot | `.snapshot` | REST API | Weaviate filesystem backup |
|
|
132
|
+
|
|
133
|
+
### Backup API
|
|
134
|
+
|
|
135
|
+
Backup and restore use Weaviate's filesystem backup endpoints:
|
|
136
|
+
- `POST /v1/backups/filesystem` - Create backup (with status polling)
|
|
137
|
+
- `GET /v1/backups/filesystem/{id}` - Check backup status
|
|
138
|
+
- `POST /v1/backups/filesystem/{id}/restore` - Restore backup
|
|
139
|
+
|
|
140
|
+
### Backup Flow
|
|
141
|
+
|
|
142
|
+
1. `BACKUP_FILESYSTEM_PATH` env var points to `{dataDir}/backups`
|
|
143
|
+
2. `ENABLE_MODULES=backup-filesystem` must be set (or backup API returns 404)
|
|
144
|
+
3. Trigger backup via `POST /v1/backups/filesystem` with `{ id: "spindb-backup-{ts}" }`
|
|
145
|
+
4. Poll status via `GET /v1/backups/filesystem/{id}` until `SUCCESS`
|
|
146
|
+
5. Copy backup **directory** (not a single file) from `{backupsDir}/{id}/` to output path
|
|
147
|
+
|
|
148
|
+
### Restore Flow
|
|
149
|
+
|
|
150
|
+
1. Copy backup directory into target container's `{backupsDir}/{backupId}/`
|
|
151
|
+
2. **The directory name MUST match the internal backup ID** stored in `backup_config.json` inside the backup. Weaviate validates this and returns 422 on mismatch.
|
|
152
|
+
3. Start Weaviate
|
|
153
|
+
4. Trigger restore via `POST /v1/backups/filesystem/{backupId}/restore`
|
|
154
|
+
5. If restoring to a container with a different `CLUSTER_HOSTNAME`, pass `node_mapping` in the request body:
|
|
155
|
+
```json
|
|
156
|
+
{ "node_mapping": { "node-8080": "node-9090" } }
|
|
157
|
+
```
|
|
158
|
+
6. Poll restore status until `SUCCESS`
|
|
159
|
+
|
|
160
|
+
## Integration Test Notes
|
|
161
|
+
|
|
162
|
+
### REST API Testing
|
|
163
|
+
|
|
164
|
+
Integration tests use `fetch()` for operations, not CLI tools.
|
|
165
|
+
|
|
166
|
+
### Test Ports
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
weaviate: { base: 8090, clone: 8092, renamed: 8091 }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Docker E2E Test Notes
|
|
173
|
+
|
|
174
|
+
Weaviate Docker E2E uses `curl` for all operations:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Health check
|
|
178
|
+
curl http://localhost:8080/v1/.well-known/ready
|
|
179
|
+
|
|
180
|
+
# Create class
|
|
181
|
+
curl -X POST http://localhost:8080/v1/schema \
|
|
182
|
+
-H 'Content-Type: application/json' \
|
|
183
|
+
-d '{"class":"TestVectors","vectorizer":"none","properties":[...]}'
|
|
184
|
+
|
|
185
|
+
# Insert objects (batch)
|
|
186
|
+
curl -X POST http://localhost:8080/v1/batch/objects \
|
|
187
|
+
-H 'Content-Type: application/json' \
|
|
188
|
+
-d '{"objects":[...]}'
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Known Issues & Gotchas
|
|
192
|
+
|
|
193
|
+
### 1. No --version Flag
|
|
194
|
+
|
|
195
|
+
Weaviate binary doesn't support `--version` as of v1.35.x. Tracked in [weaviate/weaviate#6571](https://github.com/weaviate/weaviate/issues/6571). Binary verification only checks file existence. Same pattern as CouchDB.
|
|
196
|
+
|
|
197
|
+
### 2. No CLI Shell
|
|
198
|
+
|
|
199
|
+
`spindb run` does nothing for Weaviate. Use the REST API or web dashboard.
|
|
200
|
+
|
|
201
|
+
### 3. Vector Database Semantics
|
|
202
|
+
|
|
203
|
+
Weaviate uses "classes" (or "collections") instead of "databases". Operations are vector-centric:
|
|
204
|
+
- Create classes with property schemas
|
|
205
|
+
- Insert objects with optional vectors
|
|
206
|
+
- Search by vector similarity or filters
|
|
207
|
+
|
|
208
|
+
### 4. Internal Cluster Ports Must Be Unique
|
|
209
|
+
|
|
210
|
+
Weaviate binds 4 internal ports (gossip 7946, data 7947, raft 8300, raft RPC 8301) by default. Running multiple Weaviate containers without unique ports causes silent conflicts or startup failures. SpinDB derives unique ports from the HTTP port (see "Internal Cluster Ports" above).
|
|
211
|
+
|
|
212
|
+
### 5. ENABLE_MODULES Required for Backup
|
|
213
|
+
|
|
214
|
+
`ENABLE_MODULES=backup-filesystem` must be set at startup or the backup/restore API endpoints return 404.
|
|
215
|
+
|
|
216
|
+
### 6. Backup Directory Name Must Match Internal ID
|
|
217
|
+
|
|
218
|
+
Weaviate backups are directories (not single files). The backup directory name **must match** the internal backup ID in `backup_config.json`. When copying a backup to a new location, `restore.ts` reads `backup_config.json` to discover the real ID and names the target directory accordingly.
|
|
219
|
+
|
|
220
|
+
### 7. Node Mapping for Cross-Container Restore
|
|
221
|
+
|
|
222
|
+
When restoring a backup to a container with a different `CLUSTER_HOSTNAME`, the Weaviate restore API requires a `node_mapping` parameter. Without it, restore fails with "cannot resolve hostname" (422).
|
|
223
|
+
|
|
224
|
+
### 8. Windows Backup Fails (LSM File Locking)
|
|
225
|
+
|
|
226
|
+
Weaviate on Windows holds exclusive locks on LSM storage files, preventing `fsync` during backup while the server is running. The backup API returns "Access is denied" errors. Integration tests skip the backup/restore clone test on Windows. Same pattern as Meilisearch.
|
|
227
|
+
|
|
228
|
+
### 9. gRPC Port
|
|
229
|
+
|
|
230
|
+
The gRPC port is separate from HTTP (HTTP + 1). Ensure both ports are available if using gRPC clients.
|
|
231
|
+
|
|
232
|
+
### 10. Snapshot Format
|
|
233
|
+
|
|
234
|
+
Snapshots are Weaviate's native backup format and are not compatible with other databases.
|
|
235
|
+
|
|
236
|
+
### 11. Health Check Endpoint
|
|
237
|
+
|
|
238
|
+
Use `/v1/.well-known/ready` for health checks (returns 200 when ready).
|
|
239
|
+
|
|
240
|
+
### 12. Class/Collection Naming
|
|
241
|
+
|
|
242
|
+
Weaviate class names must start with an uppercase letter (PascalCase). Container names with dashes are auto-converted (e.g., `my-app` becomes class `My_app`).
|
|
243
|
+
|
|
244
|
+
## CI/CD Notes
|
|
245
|
+
|
|
246
|
+
### curl-Based Testing
|
|
247
|
+
|
|
248
|
+
CI tests use `curl` commands rather than database CLI tools.
|
|
249
|
+
|
|
250
|
+
### GitHub Actions Cache Step
|
|
251
|
+
|
|
252
|
+
```yaml
|
|
253
|
+
- name: Cache Weaviate binaries
|
|
254
|
+
uses: actions/cache@v4
|
|
255
|
+
with:
|
|
256
|
+
path: ~/.spindb/bin/weaviate-*
|
|
257
|
+
key: weaviate-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('engines/weaviate/version-maps.ts') }}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## REST API Quick Reference
|
|
261
|
+
|
|
262
|
+
### Schema (Classes)
|
|
263
|
+
```bash
|
|
264
|
+
# List all classes
|
|
265
|
+
GET /v1/schema
|
|
266
|
+
|
|
267
|
+
# Get class info
|
|
268
|
+
GET /v1/schema/{class}
|
|
269
|
+
|
|
270
|
+
# Create class
|
|
271
|
+
POST /v1/schema
|
|
272
|
+
|
|
273
|
+
# Delete class
|
|
274
|
+
DELETE /v1/schema/{class}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Objects
|
|
278
|
+
```bash
|
|
279
|
+
# Batch insert objects
|
|
280
|
+
POST /v1/batch/objects
|
|
281
|
+
|
|
282
|
+
# Get object
|
|
283
|
+
GET /v1/objects/{class}/{id}
|
|
284
|
+
|
|
285
|
+
# Delete object
|
|
286
|
+
DELETE /v1/objects/{class}/{id}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Search
|
|
290
|
+
```bash
|
|
291
|
+
# GraphQL query
|
|
292
|
+
POST /v1/graphql
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Meta
|
|
296
|
+
```bash
|
|
297
|
+
# Server meta info (includes version)
|
|
298
|
+
GET /v1/meta
|
|
299
|
+
|
|
300
|
+
# Health/ready check
|
|
301
|
+
GET /v1/.well-known/ready
|
|
302
|
+
```
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Weaviate REST API client utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Make an HTTP request to Weaviate REST API
|
|
7
|
+
*
|
|
8
|
+
* @param port - The HTTP port Weaviate is listening on
|
|
9
|
+
* @param method - HTTP method (GET, POST, PUT, DELETE)
|
|
10
|
+
* @param path - API path (e.g., '/v1/schema', '/v1/.well-known/ready')
|
|
11
|
+
* @param body - Optional JSON body for POST/PUT requests
|
|
12
|
+
* @param timeoutMs - Request timeout in milliseconds (default: 30s)
|
|
13
|
+
*/
|
|
14
|
+
export async function weaviateApiRequest(
|
|
15
|
+
port: number,
|
|
16
|
+
method: string,
|
|
17
|
+
path: string,
|
|
18
|
+
body?: Record<string, unknown>,
|
|
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
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
},
|
|
31
|
+
signal: controller.signal,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (body) {
|
|
35
|
+
options.body = JSON.stringify(body)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(url, options)
|
|
40
|
+
|
|
41
|
+
// Try to parse as JSON, fall back to text for endpoints like /v1/.well-known/ready
|
|
42
|
+
let data: unknown
|
|
43
|
+
const contentType = response.headers.get('content-type') || ''
|
|
44
|
+
if (contentType.includes('application/json')) {
|
|
45
|
+
data = await response.json()
|
|
46
|
+
} else {
|
|
47
|
+
data = await response.text()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { status: response.status, data }
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Weaviate API request timed out after ${timeoutMs / 1000}s: ${method} ${path}`,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
throw error
|
|
58
|
+
} finally {
|
|
59
|
+
clearTimeout(timeoutId)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weaviate backup module
|
|
3
|
+
* Supports snapshot-based backup using Weaviate's filesystem backup API.
|
|
4
|
+
*
|
|
5
|
+
* Weaviate's filesystem backup creates a directory at BACKUP_FILESYSTEM_PATH/<id>/
|
|
6
|
+
* containing backup metadata and class data. We copy this entire directory
|
|
7
|
+
* as the "snapshot" for backup/restore.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdir, stat, cp, readdir } from 'fs/promises'
|
|
11
|
+
import { existsSync } from 'fs'
|
|
12
|
+
import { join, dirname } from 'path'
|
|
13
|
+
import { logDebug } from '../../core/error-handler'
|
|
14
|
+
import { paths } from '../../config/paths'
|
|
15
|
+
import { weaviateApiRequest } from './api-client'
|
|
16
|
+
import type { ContainerConfig, BackupOptions, BackupResult } from '../../types'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a snapshot backup using Weaviate's REST API.
|
|
20
|
+
* Triggers a filesystem backup, polls for completion, then copies the
|
|
21
|
+
* backup directory to the output path.
|
|
22
|
+
*/
|
|
23
|
+
export async function createBackup(
|
|
24
|
+
container: ContainerConfig,
|
|
25
|
+
outputPath: string,
|
|
26
|
+
_options: BackupOptions,
|
|
27
|
+
): Promise<BackupResult> {
|
|
28
|
+
const { port, name } = container
|
|
29
|
+
|
|
30
|
+
// Ensure output parent directory exists
|
|
31
|
+
const outputDir = dirname(outputPath)
|
|
32
|
+
if (!existsSync(outputDir)) {
|
|
33
|
+
await mkdir(outputDir, { recursive: true })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Generate a unique backup ID
|
|
37
|
+
const backupId = `spindb-backup-${Date.now()}`
|
|
38
|
+
|
|
39
|
+
// Trigger backup creation via REST API
|
|
40
|
+
logDebug(
|
|
41
|
+
`Creating Weaviate backup '${backupId}' via REST API on port ${port}`,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const response = await weaviateApiRequest(
|
|
45
|
+
port,
|
|
46
|
+
'POST',
|
|
47
|
+
'/v1/backups/filesystem',
|
|
48
|
+
{ id: backupId },
|
|
49
|
+
600000, // 10 minute timeout
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if (response.status !== 200) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Failed to create Weaviate backup: ${JSON.stringify(response.data)}`,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logDebug(`Weaviate backup initiated: ${backupId}`)
|
|
59
|
+
|
|
60
|
+
// Poll for backup completion
|
|
61
|
+
const maxWait = 300000 // 5 minutes
|
|
62
|
+
const startTime = Date.now()
|
|
63
|
+
|
|
64
|
+
let backupCompleted = false
|
|
65
|
+
|
|
66
|
+
while (Date.now() - startTime < maxWait) {
|
|
67
|
+
const statusResponse = await weaviateApiRequest(
|
|
68
|
+
port,
|
|
69
|
+
'GET',
|
|
70
|
+
`/v1/backups/filesystem/${backupId}`,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if (statusResponse.status === 200) {
|
|
74
|
+
const statusData = statusResponse.data as {
|
|
75
|
+
status?: string
|
|
76
|
+
path?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (statusData.status === 'SUCCESS') {
|
|
80
|
+
logDebug(`Weaviate backup completed: ${backupId}`)
|
|
81
|
+
backupCompleted = true
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (statusData.status === 'FAILED') {
|
|
86
|
+
throw new Error(`Weaviate backup failed: ${JSON.stringify(statusData)}`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
logDebug(`Backup status: ${statusData.status}, waiting...`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!backupCompleted) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Weaviate backup '${backupId}' timed out after ${maxWait / 1000}s without completing`,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Weaviate stores backup at BACKUP_FILESYSTEM_PATH/<backupId>/
|
|
102
|
+
// BACKUP_FILESYSTEM_PATH is set to <dataDir>/backups in start()
|
|
103
|
+
const dataDir = paths.getContainerDataPath(name, { engine: 'weaviate' })
|
|
104
|
+
const backupDir = join(dataDir, 'backups', backupId)
|
|
105
|
+
|
|
106
|
+
if (!existsSync(backupDir)) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Weaviate backup directory not found at ${backupDir} after completion`,
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Copy entire backup directory to output path
|
|
113
|
+
await cp(backupDir, outputPath, { recursive: true })
|
|
114
|
+
|
|
115
|
+
// Get total size of backup directory
|
|
116
|
+
const files = await readdir(backupDir, { recursive: true })
|
|
117
|
+
let totalSize = 0
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
try {
|
|
120
|
+
const filePath = join(backupDir, String(file))
|
|
121
|
+
const stats = await stat(filePath)
|
|
122
|
+
if (stats.isFile()) {
|
|
123
|
+
totalSize += stats.size
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// Skip inaccessible files
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
path: outputPath,
|
|
132
|
+
format: 'snapshot',
|
|
133
|
+
size: totalSize,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create a backup for cloning purposes
|
|
139
|
+
*/
|
|
140
|
+
export async function createCloneBackup(
|
|
141
|
+
container: ContainerConfig,
|
|
142
|
+
outputPath: string,
|
|
143
|
+
): Promise<BackupResult> {
|
|
144
|
+
return createBackup(container, outputPath, { database: 'default' })
|
|
145
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weaviate Binary Manager
|
|
3
|
+
*
|
|
4
|
+
* Handles downloading, extracting, and managing Weaviate binaries from hostdb.
|
|
5
|
+
* Extends BaseBinaryManager for shared download/extraction logic.
|
|
6
|
+
*
|
|
7
|
+
* Note: Weaviate doesn't support --version flag (as of v1.35.x). We override
|
|
8
|
+
* verify() to just check binary existence instead of running it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync } from 'fs'
|
|
12
|
+
import { join } from 'path'
|
|
13
|
+
import {
|
|
14
|
+
BaseBinaryManager,
|
|
15
|
+
type BinaryManagerConfig,
|
|
16
|
+
} from '../../core/base-binary-manager'
|
|
17
|
+
import { getBinaryUrl } from './binary-urls'
|
|
18
|
+
import { normalizeVersion } from './version-maps'
|
|
19
|
+
import { Engine, Platform, type Arch } from '../../types'
|
|
20
|
+
import { paths } from '../../config/paths'
|
|
21
|
+
|
|
22
|
+
class WeaviateBinaryManager extends BaseBinaryManager {
|
|
23
|
+
protected readonly config: BinaryManagerConfig = {
|
|
24
|
+
engine: Engine.Weaviate,
|
|
25
|
+
engineName: 'weaviate',
|
|
26
|
+
displayName: 'Weaviate',
|
|
27
|
+
serverBinary: 'weaviate',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected getBinaryUrlFromModule(
|
|
31
|
+
version: string,
|
|
32
|
+
platform: Platform,
|
|
33
|
+
arch: Arch,
|
|
34
|
+
): string {
|
|
35
|
+
return getBinaryUrl(version, platform, arch)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected normalizeVersionFromModule(version: string): string {
|
|
39
|
+
return normalizeVersion(version)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected parseVersionFromOutput(stdout: string): string | null {
|
|
43
|
+
// Extract version from output like "weaviate v1.35.7" or "1.35.7"
|
|
44
|
+
const match = stdout.match(/(?:weaviate\s+)?v?(\d+\.\d+\.\d+)/)
|
|
45
|
+
return match?.[1] ?? null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Override verify to just check binary existence.
|
|
50
|
+
* Weaviate doesn't support --version flag (as of v1.35.x).
|
|
51
|
+
* See: https://github.com/weaviate/weaviate/issues/6571
|
|
52
|
+
*/
|
|
53
|
+
async verify(
|
|
54
|
+
version: string,
|
|
55
|
+
platform: Platform,
|
|
56
|
+
arch: Arch,
|
|
57
|
+
): Promise<boolean> {
|
|
58
|
+
const fullVersion = this.getFullVersion(version)
|
|
59
|
+
const binPath = paths.getBinaryPath({
|
|
60
|
+
engine: this.config.engineName,
|
|
61
|
+
version: fullVersion,
|
|
62
|
+
platform,
|
|
63
|
+
arch,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const ext = platform === Platform.Win32 ? '.exe' : ''
|
|
67
|
+
const serverPath = join(binPath, 'bin', `${this.config.serverBinary}${ext}`)
|
|
68
|
+
|
|
69
|
+
if (!existsSync(serverPath)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`${this.config.displayName} binary not found at ${binPath}/bin/`,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Just verify the binary exists - we can't run --version on Weaviate
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const weaviateBinaryManager = new WeaviateBinaryManager()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { WEAVIATE_VERSION_MAP } from './version-maps'
|
|
2
|
+
import { buildHostdbUrl } from '../../core/hostdb-client'
|
|
3
|
+
import { logDebug } from '../../core/error-handler'
|
|
4
|
+
import { Engine, Platform, type Arch } from '../../types'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Supported platform identifiers for hostdb downloads.
|
|
8
|
+
* hostdb uses standard Node.js platform naming - this set validates
|
|
9
|
+
* that a platform/arch combination is supported, not transforms it.
|
|
10
|
+
*/
|
|
11
|
+
const SUPPORTED_PLATFORMS = new Set([
|
|
12
|
+
'darwin-arm64',
|
|
13
|
+
'darwin-x64',
|
|
14
|
+
'linux-arm64',
|
|
15
|
+
'linux-x64',
|
|
16
|
+
'win32-x64',
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the hostdb platform identifier
|
|
21
|
+
*
|
|
22
|
+
* hostdb uses standard platform naming that matches Node.js identifiers directly.
|
|
23
|
+
* This function validates the platform/arch combination is supported.
|
|
24
|
+
*
|
|
25
|
+
* @param platform - Node.js platform (e.g., 'darwin', 'linux', 'win32')
|
|
26
|
+
* @param arch - Node.js architecture (e.g., 'arm64', 'x64')
|
|
27
|
+
* @returns hostdb platform identifier or null if unsupported
|
|
28
|
+
*/
|
|
29
|
+
export function getHostdbPlatform(
|
|
30
|
+
platform: Platform,
|
|
31
|
+
arch: Arch,
|
|
32
|
+
): string | null {
|
|
33
|
+
const key = `${platform}-${arch}`
|
|
34
|
+
return SUPPORTED_PLATFORMS.has(key) ? key : null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build the download URL for Weaviate binaries from hostdb
|
|
39
|
+
*
|
|
40
|
+
* Format: https://registry.layerbase.host/weaviate-{version}/weaviate-{version}-{platform}-{arch}.{ext}
|
|
41
|
+
*
|
|
42
|
+
* @param version - Weaviate version (e.g., '1', '1.35.7')
|
|
43
|
+
* @param platform - Platform identifier (e.g., 'darwin', 'linux', 'win32')
|
|
44
|
+
* @param arch - Architecture identifier (e.g., 'arm64', 'x64')
|
|
45
|
+
* @returns Download URL for the binary
|
|
46
|
+
*/
|
|
47
|
+
export function getBinaryUrl(
|
|
48
|
+
version: string,
|
|
49
|
+
platform: Platform,
|
|
50
|
+
arch: Arch,
|
|
51
|
+
): string {
|
|
52
|
+
const platformKey = `${platform}-${arch}`
|
|
53
|
+
const hostdbPlatform = getHostdbPlatform(platform, arch)
|
|
54
|
+
if (!hostdbPlatform) {
|
|
55
|
+
const supported = Array.from(SUPPORTED_PLATFORMS).join(', ')
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Unsupported platform: ${platformKey}. Supported platforms: ${supported}`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Normalize version (handles major version lookup and X.Y -> X.Y.Z conversion)
|
|
62
|
+
const fullVersion = normalizeVersion(version, WEAVIATE_VERSION_MAP)
|
|
63
|
+
const ext = platform === Platform.Win32 ? 'zip' : 'tar.gz'
|
|
64
|
+
|
|
65
|
+
return buildHostdbUrl(Engine.Weaviate, {
|
|
66
|
+
version: fullVersion,
|
|
67
|
+
hostdbPlatform,
|
|
68
|
+
extension: ext,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Normalize version string to X.Y.Z format
|
|
74
|
+
*
|
|
75
|
+
* @param version - Version string (e.g., '1', '1.35', '1.35.7')
|
|
76
|
+
* @param versionMap - Optional version map for major version lookup
|
|
77
|
+
* @returns Normalized version (e.g., '1.35.7')
|
|
78
|
+
*/
|
|
79
|
+
function normalizeVersion(
|
|
80
|
+
version: string,
|
|
81
|
+
versionMap: Record<string, string> = WEAVIATE_VERSION_MAP,
|
|
82
|
+
): string {
|
|
83
|
+
// Check if it's an exact key in the map (handles "1", "1.35", etc.)
|
|
84
|
+
if (versionMap[version]) {
|
|
85
|
+
return versionMap[version]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const parts = version.split('.')
|
|
89
|
+
|
|
90
|
+
// If it's already a full version (X.Y.Z), return as-is
|
|
91
|
+
if (parts.length === 3) {
|
|
92
|
+
return version
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// For two-part versions (e.g., "1.35"), first try exact two-part key, then fall back to major
|
|
96
|
+
if (parts.length === 2) {
|
|
97
|
+
const twoPart = `${parts[0]}.${parts[1]}`
|
|
98
|
+
if (versionMap[twoPart]) {
|
|
99
|
+
return versionMap[twoPart]
|
|
100
|
+
}
|
|
101
|
+
// Fall back to major version for latest patch
|
|
102
|
+
const major = parts[0]
|
|
103
|
+
const mapped = versionMap[major]
|
|
104
|
+
if (mapped) {
|
|
105
|
+
return mapped
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Unknown version format - log and return as-is
|
|
110
|
+
// This may cause download failures if the version doesn't exist in hostdb
|
|
111
|
+
logDebug(
|
|
112
|
+
`Weaviate version '${version}' not in version map, may not be available in hostdb`,
|
|
113
|
+
)
|
|
114
|
+
return version
|
|
115
|
+
}
|