lex-node 0.2.0 → 0.2.3
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +16 -0
- data/.rubocop.yml +40 -10
- data/CHANGELOG.md +19 -0
- data/CLAUDE.md +109 -0
- data/Dockerfile +1 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +87 -0
- data/README.md +65 -5
- data/docker_deploy.rb +1 -0
- data/lex-node.gemspec +4 -1
- data/lib/legion/extensions/node/actors/beat.rb +27 -19
- data/lib/legion/extensions/node/actors/crypt.rb +12 -4
- data/lib/legion/extensions/node/actors/push_key.rb +27 -19
- data/lib/legion/extensions/node/actors/vault.rb +27 -19
- data/lib/legion/extensions/node/actors/vault_token_request.rb +27 -19
- data/lib/legion/extensions/node/data_test/migrations/001_nodes_table.rb +2 -0
- data/lib/legion/extensions/node/data_test/migrations/002_node_history_table.rb +2 -1
- data/lib/legion/extensions/node/data_test/migrations/003_legion_version_colume.rb +2 -0
- data/lib/legion/extensions/node/data_test/migrations/004_node_extensions.rb +2 -1
- data/lib/legion/extensions/node/runners/beat.rb +17 -9
- data/lib/legion/extensions/node/runners/crypt.rb +60 -52
- data/lib/legion/extensions/node/runners/node.rb +109 -52
- data/lib/legion/extensions/node/runners/vault.rb +44 -36
- data/lib/legion/extensions/node/transport/exchanges/node.rb +12 -6
- data/lib/legion/extensions/node/transport/messages/beat.rb +85 -22
- data/lib/legion/extensions/node/transport/messages/public_key.rb +24 -14
- data/lib/legion/extensions/node/transport/messages/push_cluster_secret.rb +34 -24
- data/lib/legion/extensions/node/transport/messages/push_vault_token.rb +34 -24
- data/lib/legion/extensions/node/transport/messages/request_cluster_secret.rb +26 -16
- data/lib/legion/extensions/node/transport/messages/request_public_keys.rb +23 -13
- data/lib/legion/extensions/node/transport/messages/request_vault_token.rb +31 -21
- data/lib/legion/extensions/node/transport/messages/update_result.rb +36 -0
- data/lib/legion/extensions/node/transport/queues/crypt.rb +14 -4
- data/lib/legion/extensions/node/transport/queues/health.rb +14 -4
- data/lib/legion/extensions/node/transport/queues/node.rb +18 -7
- data/lib/legion/extensions/node/transport/queues/vault.rb +14 -4
- data/lib/legion/extensions/node/transport.rb +17 -8
- data/lib/legion/extensions/node/version.rb +3 -1
- data/lib/legion/extensions/node.rb +2 -0
- metadata +10 -9
- data/.github/workflows/rspec.yml +0 -69
- data/.github/workflows/rubocop.yml +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0260fcda3e0a5469d3bfc8d7065d4870addf415732a693da58d979746dcf97b
|
|
4
|
+
data.tar.gz: 67a9e3e080786935fea85006e9623cae1aff3a0995d9e507ba37eb723ffff48b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27d58631dc278eceb7988cc6125382e55babcac167e2df4a68b386d0b2ac47c8ece02b76f8333732e9d342659d2fc0cc159241fae86a7194a5490ec90be6d504
|
|
7
|
+
data.tar.gz: 886335964005f1f868d644c0b084a1a2e82991a9714e00a25154551864c6cf6b55d4d2aaf2d66edd4c9b9313cc1650b07589345c20dbdcb71e95ee815b247384
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
ci:
|
|
9
|
+
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
10
|
+
|
|
11
|
+
release:
|
|
12
|
+
needs: ci
|
|
13
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
15
|
+
secrets:
|
|
16
|
+
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.rubocop.yml
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.4
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
1
6
|
Layout/LineLength:
|
|
2
|
-
Max:
|
|
7
|
+
Max: 160
|
|
8
|
+
|
|
9
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
10
|
+
EnforcedStyle: space
|
|
11
|
+
|
|
12
|
+
Layout/HashAlignment:
|
|
13
|
+
EnforcedHashRocketStyle: table
|
|
14
|
+
EnforcedColonStyle: table
|
|
15
|
+
|
|
3
16
|
Metrics/MethodLength:
|
|
4
|
-
Max:
|
|
17
|
+
Max: 50
|
|
18
|
+
|
|
5
19
|
Metrics/ClassLength:
|
|
6
20
|
Max: 1500
|
|
21
|
+
|
|
22
|
+
Metrics/ModuleLength:
|
|
23
|
+
Max: 1500
|
|
24
|
+
|
|
7
25
|
Metrics/BlockLength:
|
|
8
|
-
Max:
|
|
26
|
+
Max: 40
|
|
27
|
+
Exclude:
|
|
28
|
+
- 'spec/**/*'
|
|
29
|
+
|
|
30
|
+
Metrics/AbcSize:
|
|
31
|
+
Max: 60
|
|
32
|
+
|
|
33
|
+
Metrics/CyclomaticComplexity:
|
|
34
|
+
Max: 15
|
|
35
|
+
|
|
36
|
+
Metrics/PerceivedComplexity:
|
|
37
|
+
Max: 17
|
|
38
|
+
|
|
9
39
|
Style/Documentation:
|
|
10
40
|
Enabled: false
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
41
|
+
|
|
42
|
+
Style/SymbolArray:
|
|
43
|
+
Enabled: true
|
|
44
|
+
|
|
15
45
|
Style/FrozenStringLiteralComment:
|
|
16
|
-
Enabled:
|
|
46
|
+
Enabled: true
|
|
47
|
+
EnforcedStyle: always
|
|
48
|
+
|
|
17
49
|
Naming/FileName:
|
|
18
50
|
Enabled: false
|
|
19
|
-
Style/ClassAndModuleChildren:
|
|
20
|
-
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.3] - 2026-03-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Beat message now includes `version` (Legion::VERSION), `metrics` (memory RSS, extension count, uptime), and `hosted_worker_ids` (active digital worker IDs on this node)
|
|
7
|
+
- Platform-aware resource metrics collection (macOS `ps` vs Linux `/proc`)
|
|
8
|
+
|
|
9
|
+
## [0.2.2] - 2026-03-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `update_gem` runner function for remote gem installation and reload
|
|
13
|
+
- `update_settings` runner function for remote settings propagation
|
|
14
|
+
- `UpdateResult` message class for publishing operation results
|
|
15
|
+
|
|
16
|
+
## [0.2.1] - 2026-03-13
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Initial release
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# lex-node: Node Identity and Cluster Management for LegionIO
|
|
2
|
+
|
|
3
|
+
**Repository Level 3 Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-core/CLAUDE.md`
|
|
5
|
+
- **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Core Legion Extension responsible for node identity within a LegionIO cluster. Handles heartbeat broadcasting, dynamic configuration distribution, cluster secret exchange, Vault token management, and RSA public key distribution between nodes.
|
|
10
|
+
|
|
11
|
+
**GitHub**: https://github.com/LegionIO/lex-node
|
|
12
|
+
**License**: MIT
|
|
13
|
+
**Version**: 0.2.3
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Legion::Extensions::Node
|
|
19
|
+
├── Actors/
|
|
20
|
+
│ ├── Beat # Periodic heartbeat broadcast
|
|
21
|
+
│ ├── Crypt # Cryptographic key exchange actor
|
|
22
|
+
│ ├── PushKey # Once actor: calls request_public_keys on startup
|
|
23
|
+
│ ├── Vault # Vault token lifecycle management
|
|
24
|
+
│ └── VaultTokenRequest # Once actor: calls 'request_token' on Vault runner (request_token delegates to request_vault_token)
|
|
25
|
+
├── Runners/
|
|
26
|
+
│ ├── Beat # beat: publishes heartbeat message with node status
|
|
27
|
+
│ ├── Crypt # RSA key exchange: push_public_key, update_public_key,
|
|
28
|
+
│ │ # delete_public_key, request_public_keys,
|
|
29
|
+
│ │ # push_cluster_secret, request_cluster_secret,
|
|
30
|
+
│ │ # receive_cluster_secret
|
|
31
|
+
│ ├── Node # Dynamic config and remote ops: message, update_settings,
|
|
32
|
+
│ │ # update_gem, push_public_key, update_public_key,
|
|
33
|
+
│ │ # push_cluster_secret, receive_cluster_secret,
|
|
34
|
+
│ │ # receive_vault_token
|
|
35
|
+
│ └── Vault # Vault: request_token, request_vault_token,
|
|
36
|
+
│ # receive_vault_token, push_vault_token
|
|
37
|
+
├── Transport/
|
|
38
|
+
│ ├── Exchanges/Node # Node communication exchange
|
|
39
|
+
│ ├── Queues/
|
|
40
|
+
│ │ ├── Node # node.<name> (exclusive, auto-delete, per-node)
|
|
41
|
+
│ │ ├── Vault # Vault token queue
|
|
42
|
+
│ │ ├── Health # Health check queue
|
|
43
|
+
│ │ └── Crypt # Encryption key exchange queue
|
|
44
|
+
│ └── Messages/
|
|
45
|
+
│ ├── Beat # Heartbeat message (routing_key: 'status', TTL 5s, includes metrics/worker_ids/version)
|
|
46
|
+
│ ├── PublicKey # Public key distribution
|
|
47
|
+
│ ├── RequestPublicKeys # Broadcast request for cluster public keys
|
|
48
|
+
│ ├── PushClusterSecret # Distribute encrypted cluster secret
|
|
49
|
+
│ ├── RequestClusterSecret # Request cluster secret from peer
|
|
50
|
+
│ ├── RequestVaultToken # Request Vault token from peer
|
|
51
|
+
│ ├── PushVaultToken # Distribute encrypted Vault token
|
|
52
|
+
│ └── UpdateResult # Operation result (update_gem / update_settings outcomes)
|
|
53
|
+
└── DataTest/
|
|
54
|
+
└── Migrations/
|
|
55
|
+
├── 001_nodes_table # Core nodes table
|
|
56
|
+
├── 002_node_history_table # Node activity history
|
|
57
|
+
├── 003_legion_version_colume # Legion version column (note: typo in filename)
|
|
58
|
+
└── 004_node_extensions # Installed extensions per node
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Key Files
|
|
62
|
+
|
|
63
|
+
| Path | Purpose |
|
|
64
|
+
|------|---------|
|
|
65
|
+
| `lib/legion/extensions/node.rb` | Entry point, extension registration |
|
|
66
|
+
| `lib/legion/extensions/node/runners/beat.rb` | Heartbeat broadcasting |
|
|
67
|
+
| `lib/legion/extensions/node/runners/crypt.rb` | RSA keypair and cluster secret exchange |
|
|
68
|
+
| `lib/legion/extensions/node/runners/node.rb` | Dynamic config distribution, update_settings, update_gem, public key relay |
|
|
69
|
+
| `lib/legion/extensions/node/runners/vault.rb` | Vault token request/receive/push lifecycle |
|
|
70
|
+
| `lib/legion/extensions/node/transport/messages/beat.rb` | Heartbeat message (routing_key: 'status', TTL 5s) with resource metrics, hosted worker IDs, and Legion version |
|
|
71
|
+
| `lib/legion/extensions/node/transport/messages/update_result.rb` | Operation result message for update_gem/update_settings |
|
|
72
|
+
| `lib/legion/extensions/node/transport/messages/` | All node-to-node message types |
|
|
73
|
+
| `lib/legion/extensions/node/transport/queues/node.rb` | Per-node queue (exclusive, auto-delete, classic type) |
|
|
74
|
+
| `lib/legion/extensions/node/data_test/migrations/` | DB migrations |
|
|
75
|
+
|
|
76
|
+
## Cluster Secret Exchange Flow
|
|
77
|
+
|
|
78
|
+
1. Node starts, calls `request_public_keys` (broadcasts to all nodes)
|
|
79
|
+
2. Nodes respond with `push_public_key` (RSA public key, base64-encoded)
|
|
80
|
+
3. Node with cluster secret calls `push_cluster_secret`: encrypts secret with recipient's RSA public key, publishes to their queue
|
|
81
|
+
4. Recipient calls `receive_cluster_secret`: decrypts with own private key, stores in settings
|
|
82
|
+
|
|
83
|
+
## Vault Token Flow
|
|
84
|
+
|
|
85
|
+
1. Node starts without Vault token; `VaultTokenRequest` (Once actor) calls `request_token`
|
|
86
|
+
2. `request_token` checks `vault.enabled` and `vault.connected` before broadcasting `request_vault_token`
|
|
87
|
+
3. Node with Vault token calls `push_vault_token`: encrypts token with recipient's RSA public key
|
|
88
|
+
4. Recipient calls `receive_vault_token`: decrypts, stores token, calls `Legion::Crypt.connect_vault`
|
|
89
|
+
|
|
90
|
+
## Remote Settings and Gem Updates
|
|
91
|
+
|
|
92
|
+
`update_settings` and `update_gem` enable runtime cluster management without redeployment:
|
|
93
|
+
|
|
94
|
+
- `update_settings(settings:, restart: false)` - Merges the provided hash into `Legion::Settings`. Nested hashes are merged key-by-key. Set `restart: true` to trigger `Legion.reload` after applying. Publishes `UpdateResult` with `action: 'update_settings'` and the merged key names.
|
|
95
|
+
- `update_gem(extension:, version: nil, reload: true)` - Installs or upgrades a LEX gem (`lex-<name>`) via `Gem.install`. Set `reload: false` to skip `Legion.reload`. Publishes `UpdateResult` with `action: 'update_gem'` and the gem name/version.
|
|
96
|
+
|
|
97
|
+
Both functions rescue `StandardError` and publish a failed `UpdateResult` on error. The `UpdateResult` message routes to `node.<name>.update_result` so callers can observe outcomes.
|
|
98
|
+
|
|
99
|
+
## Testing
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
bundle install
|
|
103
|
+
bundle exec rspec
|
|
104
|
+
bundle exec rubocop
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
**Maintained By**: Matthew Iverson (@Esity)
|
data/Dockerfile
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
lex-node (0.2.3)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
addressable (2.8.9)
|
|
10
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
11
|
+
ast (2.4.3)
|
|
12
|
+
bigdecimal (4.0.1)
|
|
13
|
+
diff-lcs (1.6.2)
|
|
14
|
+
docile (1.4.1)
|
|
15
|
+
json (2.19.1)
|
|
16
|
+
json-schema (6.2.0)
|
|
17
|
+
addressable (~> 2.8)
|
|
18
|
+
bigdecimal (>= 3.1, < 5)
|
|
19
|
+
language_server-protocol (3.17.0.5)
|
|
20
|
+
lint_roller (1.1.0)
|
|
21
|
+
mcp (0.8.0)
|
|
22
|
+
json-schema (>= 4.1)
|
|
23
|
+
parallel (1.27.0)
|
|
24
|
+
parser (3.3.10.2)
|
|
25
|
+
ast (~> 2.4.1)
|
|
26
|
+
racc
|
|
27
|
+
prism (1.9.0)
|
|
28
|
+
public_suffix (7.0.5)
|
|
29
|
+
racc (1.8.1)
|
|
30
|
+
rainbow (3.1.1)
|
|
31
|
+
rake (13.3.1)
|
|
32
|
+
regexp_parser (2.11.3)
|
|
33
|
+
rspec (3.13.2)
|
|
34
|
+
rspec-core (~> 3.13.0)
|
|
35
|
+
rspec-expectations (~> 3.13.0)
|
|
36
|
+
rspec-mocks (~> 3.13.0)
|
|
37
|
+
rspec-core (3.13.6)
|
|
38
|
+
rspec-support (~> 3.13.0)
|
|
39
|
+
rspec-expectations (3.13.5)
|
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
41
|
+
rspec-support (~> 3.13.0)
|
|
42
|
+
rspec-mocks (3.13.8)
|
|
43
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
44
|
+
rspec-support (~> 3.13.0)
|
|
45
|
+
rspec-support (3.13.7)
|
|
46
|
+
rspec_junit_formatter (0.6.0)
|
|
47
|
+
rspec-core (>= 2, < 4, != 2.12.0)
|
|
48
|
+
rubocop (1.85.1)
|
|
49
|
+
json (~> 2.3)
|
|
50
|
+
language_server-protocol (~> 3.17.0.2)
|
|
51
|
+
lint_roller (~> 1.1.0)
|
|
52
|
+
mcp (~> 0.6)
|
|
53
|
+
parallel (~> 1.10)
|
|
54
|
+
parser (>= 3.3.0.2)
|
|
55
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
56
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
57
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
58
|
+
ruby-progressbar (~> 1.7)
|
|
59
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
60
|
+
rubocop-ast (1.49.1)
|
|
61
|
+
parser (>= 3.3.7.2)
|
|
62
|
+
prism (~> 1.7)
|
|
63
|
+
ruby-progressbar (1.13.0)
|
|
64
|
+
simplecov (0.22.0)
|
|
65
|
+
docile (~> 1.1)
|
|
66
|
+
simplecov-html (~> 0.11)
|
|
67
|
+
simplecov_json_formatter (~> 0.1)
|
|
68
|
+
simplecov-html (0.13.2)
|
|
69
|
+
simplecov_json_formatter (0.1.4)
|
|
70
|
+
unicode-display_width (3.2.0)
|
|
71
|
+
unicode-emoji (~> 4.1)
|
|
72
|
+
unicode-emoji (4.2.0)
|
|
73
|
+
|
|
74
|
+
PLATFORMS
|
|
75
|
+
arm64-darwin-25
|
|
76
|
+
ruby
|
|
77
|
+
|
|
78
|
+
DEPENDENCIES
|
|
79
|
+
lex-node!
|
|
80
|
+
rake
|
|
81
|
+
rspec
|
|
82
|
+
rspec_junit_formatter
|
|
83
|
+
rubocop
|
|
84
|
+
simplecov
|
|
85
|
+
|
|
86
|
+
BUNDLED WITH
|
|
87
|
+
2.6.9
|
data/README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# lex-node
|
|
2
2
|
|
|
3
|
+
Node identity and cluster management for [LegionIO](https://github.com/LegionIO/LegionIO). Handles heartbeat broadcasting, dynamic configuration distribution, cluster secret exchange, Vault token management, and RSA public key distribution between nodes.
|
|
4
|
+
|
|
5
|
+
**Version**: 0.2.2
|
|
6
|
+
|
|
7
|
+
This is a core LEX installed by default with LegionIO.
|
|
3
8
|
|
|
4
9
|
## Installation
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
Installed automatically as a dependency of `legionio`. To disable:
|
|
7
12
|
|
|
8
13
|
```json
|
|
9
14
|
{
|
|
@@ -15,10 +20,65 @@ This LEX is installed into LegionIO by default. You can disable it with the foll
|
|
|
15
20
|
}
|
|
16
21
|
```
|
|
17
22
|
|
|
18
|
-
##
|
|
23
|
+
## Runners
|
|
24
|
+
|
|
25
|
+
### Beat
|
|
26
|
+
|
|
27
|
+
Periodic heartbeat broadcast to the cluster.
|
|
28
|
+
|
|
29
|
+
- `beat(status:)` - Publishes a heartbeat message with the node hostname, PID, timestamp, and status. Interval is controlled by the `beat_interval` setting.
|
|
30
|
+
|
|
31
|
+
### Crypt
|
|
32
|
+
|
|
33
|
+
RSA keypair and cluster secret exchange.
|
|
34
|
+
|
|
35
|
+
- `push_public_key` - Broadcasts this node's RSA public key to all peers
|
|
36
|
+
- `update_public_key(name:, public_key:)` - Stores a received public key in cluster settings
|
|
37
|
+
- `delete_public_key(name:)` - Removes a stored public key from cluster settings
|
|
38
|
+
- `request_public_keys` - Broadcasts a request for all nodes to send their public keys
|
|
39
|
+
- `push_cluster_secret(public_key:, queue_name:)` - Encrypts the cluster secret with a peer's RSA public key and delivers it to their queue
|
|
40
|
+
- `request_cluster_secret` - Requests the cluster secret from peers
|
|
41
|
+
- `receive_cluster_secret(message:)` - Decrypts and stores the cluster secret received from a peer
|
|
42
|
+
|
|
43
|
+
### Node
|
|
44
|
+
|
|
45
|
+
Dynamic configuration distribution and node management.
|
|
46
|
+
|
|
47
|
+
- `message(**hash)` - Merges arbitrary key/value pairs into `Legion::Settings` at runtime
|
|
48
|
+
- `update_settings(settings:, restart: false)` - Merges a settings hash into `Legion::Settings`; optionally restarts Legion. Publishes an `UpdateResult` message on completion.
|
|
49
|
+
- `update_gem(extension:, version: nil, reload: true)` - Installs or upgrades a LEX gem via `Gem.install`, then reloads Legion. Publishes an `UpdateResult` message on completion.
|
|
50
|
+
- `push_public_key` - Broadcasts this node's RSA public key (raw string form)
|
|
51
|
+
- `update_public_key(name:, public_key:)` - Stores a received public key in cluster settings
|
|
52
|
+
- `push_cluster_secret(public_key:, queue_name:)` - Encrypts and delivers the cluster secret to a peer
|
|
53
|
+
- `receive_cluster_secret(message:)` - Decrypts and stores the received cluster secret
|
|
54
|
+
- `receive_vault_token(message:, routing_key:, public_key:)` - Delegates to the Vault runner
|
|
55
|
+
|
|
56
|
+
### Vault
|
|
57
|
+
|
|
58
|
+
Vault token lifecycle management.
|
|
59
|
+
|
|
60
|
+
- `request_token` - Checks if Vault is enabled but not connected, then calls `request_vault_token`
|
|
61
|
+
- `request_vault_token` - Broadcasts a request for a Vault token from peers
|
|
62
|
+
- `receive_vault_token(message:)` - Decrypts the received Vault token, stores it, and calls `Legion::Crypt.connect_vault`
|
|
63
|
+
- `push_vault_token(public_key:, node_name:)` - Encrypts the local Vault token with a peer's RSA public key and delivers it to their queue
|
|
64
|
+
|
|
65
|
+
## How It Works
|
|
66
|
+
|
|
67
|
+
Each node periodically calls `beat` to broadcast its presence. On startup, nodes exchange RSA public keys, then use them to securely distribute the cluster shared secret (AES encryption key) and Vault tokens - all encrypted peer-to-peer so no secrets traverse the bus in plaintext.
|
|
68
|
+
|
|
69
|
+
Dynamic config changes (`update_settings`) and gem upgrades (`update_gem`) can be pushed to individual nodes or broadcast to all nodes at runtime. Both operations publish an `UpdateResult` message to `node.<name>.update_result` so the outcome can be observed cluster-wide.
|
|
70
|
+
|
|
71
|
+
## Transport
|
|
72
|
+
|
|
73
|
+
- **Exchange**: `node` (topic exchange)
|
|
74
|
+
- **Queues**: `node.<name>` (node-specific, exclusive, auto-delete), `crypt`, `vault`, `health`
|
|
75
|
+
- **Messages**: Beat, PublicKey, RequestPublicKeys, PushClusterSecret, RequestClusterSecret, RequestVaultToken, PushVaultToken, UpdateResult
|
|
76
|
+
|
|
77
|
+
## Requirements
|
|
19
78
|
|
|
20
|
-
|
|
79
|
+
- Ruby >= 3.4
|
|
80
|
+
- [LegionIO](https://github.com/LegionIO/LegionIO) framework
|
|
21
81
|
|
|
22
82
|
## License
|
|
23
83
|
|
|
24
|
-
|
|
84
|
+
MIT
|
data/docker_deploy.rb
CHANGED
data/lex-node.gemspec
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'lib/legion/extensions/node/version'
|
|
2
4
|
|
|
3
5
|
Gem::Specification.new do |spec|
|
|
@@ -10,13 +12,14 @@ Gem::Specification.new do |spec|
|
|
|
10
12
|
spec.description = 'This lex is responsible for sending heartbeats, allowing for dynamic config, cluster secret, etc'
|
|
11
13
|
spec.homepage = 'https://github.com/LegionIO/lex-node'
|
|
12
14
|
spec.license = 'MIT'
|
|
13
|
-
spec.required_ruby_version =
|
|
15
|
+
spec.required_ruby_version = '>= 3.4'
|
|
14
16
|
|
|
15
17
|
spec.metadata['homepage_uri'] = spec.homepage
|
|
16
18
|
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-node'
|
|
17
19
|
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-node'
|
|
18
20
|
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-node'
|
|
19
21
|
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-node/issues'
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
20
23
|
|
|
21
24
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
22
25
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
class Beat < Legion::Extensions::Actors::Every
|
|
3
|
-
def runner_function
|
|
4
|
-
'beat'
|
|
5
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Node
|
|
6
|
+
module Actor
|
|
7
|
+
class Beat < Legion::Extensions::Actors::Every
|
|
8
|
+
def runner_function
|
|
9
|
+
'beat'
|
|
10
|
+
end
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
def use_runner?
|
|
13
|
+
false
|
|
14
|
+
end
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
def check_subtask?
|
|
17
|
+
false
|
|
18
|
+
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
def generate_task?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run_now?
|
|
25
|
+
true
|
|
26
|
+
end
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
def time
|
|
29
|
+
settings['beat_interval']
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
end
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Node
|
|
6
|
+
module Actor
|
|
7
|
+
class Crypt < Legion::Extensions::Actors::Subscription
|
|
8
|
+
def delay_start
|
|
9
|
+
2
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
5
13
|
end
|
|
6
14
|
end
|
|
7
15
|
end
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
class PushKey < Legion::Extensions::Actors::Once
|
|
3
|
-
def function
|
|
4
|
-
'request_public_keys'
|
|
5
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Node
|
|
6
|
+
module Actor
|
|
7
|
+
class PushKey < Legion::Extensions::Actors::Once
|
|
8
|
+
def function
|
|
9
|
+
'request_public_keys'
|
|
10
|
+
end
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
def runner_class
|
|
13
|
+
Legion::Extensions::Node::Runners::Crypt
|
|
14
|
+
end
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
def disabled?
|
|
17
|
+
false
|
|
18
|
+
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
def use_runner?
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_subtask?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
def generate_task?
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
end
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
class Vault < Legion::Extensions::Actors::Subscription
|
|
3
|
-
# def delay_start
|
|
4
|
-
# 2
|
|
5
|
-
# end
|
|
1
|
+
# frozen_string_literal: true
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Node
|
|
6
|
+
module Actor
|
|
7
|
+
class Vault < Legion::Extensions::Actors::Subscription
|
|
8
|
+
# def delay_start
|
|
9
|
+
# 2
|
|
10
|
+
# end
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
def runner_class
|
|
13
|
+
Legion::Extensions::Node::Runners::Vault
|
|
14
|
+
end
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
def disabled?
|
|
17
|
+
false
|
|
18
|
+
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
def use_runner?
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_subtask?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
def generate_task?
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
end
|