mirror of
https://github.com/will-moss/isaiah.git
synced 2024-08-22 23:27:15 +03:00
feat(project): added support for multi-host deployment
This commit is contained in:
50
README.md
50
README.md
@@ -31,6 +31,9 @@
|
||||
- [Multi-node deployment](#multi-node-deployment)
|
||||
* [General information](#general-information)
|
||||
* [Setup](#setup)
|
||||
- [Multi-host deployment](#multi-host-deployment)
|
||||
* [General information](#general-information-1)
|
||||
* [Setup](#setup-1)
|
||||
- [Configuration](#configuration)
|
||||
- [Theming](#theming)
|
||||
- [Troubleshoot](#troubleshoot)
|
||||
@@ -80,7 +83,7 @@ Isaiah has all these features implemented :
|
||||
- Support for custom Docker Host / Context.
|
||||
- Support for extensive configuration with `.env`
|
||||
- Support for HTTP and HTTPS
|
||||
- Support for standalone / proxy / multi-node deployment
|
||||
- Support for standalone / proxy / multi-node / multi-host deployment
|
||||
|
||||
On top of these, one may appreciate the following characteristics :
|
||||
- Written in Go (for the server) and Vanilla JS (for the client)
|
||||
@@ -144,6 +147,8 @@ Here's a description of every example :
|
||||
|
||||
- `docker-compose.agent.yml`: A sample setup with Isaiah operating as an Agent in a multi-node deployment.
|
||||
|
||||
- `docker-compose.host.yml`: A sample setup with Isaiah expecting to communicate with other hosts in a multi-host deployment.
|
||||
|
||||
When your `docker-compose` file is on point, you can use the following commands :
|
||||
```sh
|
||||
# Option 1 : Run Isaiah in the current terminal (useful for debugging)
|
||||
@@ -299,6 +304,46 @@ If encounter any issue, please read the [Troubleshoot](#troubleshoot) section.
|
||||
|
||||
> You may want to note that you don't need to expose ports on the machine / Docker container running Isaiah when it is configured as an Agent.
|
||||
|
||||
## Multi-host deployment
|
||||
|
||||
Using Isaiah, you can manage multiple hosts with their own distinct Docker resources from a single dashboard.
|
||||
|
||||
Before delving into that part, please get familiar with the general information below.
|
||||
|
||||
### General information
|
||||
|
||||
The big difference between multi-node and multi-host deployments is that you won't need to install Isaiah on every single node
|
||||
if you are using multi-host. In this setup, Isaiah is installed only on one server, and communicates with other Docker hosts
|
||||
directly over TCP / Unix sockets. It makes it easier to manage multiple remote Docker environments without having to setup Isaiah
|
||||
on all of them.
|
||||
|
||||
Please note that, in a multi-host setup, there must be a direct access between the main host (where Isaiah is running)
|
||||
and the other ones. Usually, they should be on the same network, or visible through a secured gateway / VPN / filesystem mount.
|
||||
|
||||
Let's see how to set up a multi-host deployment.
|
||||
|
||||
### Setup
|
||||
|
||||
In order to help you get started, a [sample file](/app/sample.docker_hosts) was created.
|
||||
|
||||
First, please ensure the following :
|
||||
- Your `Master` host is running, exposed on the network, and available in your web browser
|
||||
- Your `Master` host has the setting `MULTI_HOST_ENABLED` set to `true`.
|
||||
- Your `Master` host has access to the other Docker hosts over TCP / Unix socket.
|
||||
|
||||
Second, please create a `docker_hosts` file next to Isaiah's executable, using the sample file cited above:
|
||||
- Every line should contain two strings separated by a single space.
|
||||
- The first string is the name of your host, and the second string is the path to reach it.
|
||||
- The path to your host should look like this : [PROTOCOL]://[URI]
|
||||
- Example 1 : Local unix:///var/run/docker.sock
|
||||
- Example 2 : Remote tcp://my-domain.tld:4382
|
||||
|
||||
> If you're using Docker, you can mount the file at the root of the filesystem, as in :<br />
|
||||
`docker ... -v my_docker_hosts:/docker_hosts ...`
|
||||
|
||||
Finally, launch Isaiah on the Master host, and you should see logs indicating whether connection with remote hosts was established.
|
||||
Eventually, you will see `Master` with `The name of your host` in the lower right corner of your screen.
|
||||
|
||||
## Configuration
|
||||
|
||||
To run Isaiah, you will need to set the following environment variables in a `.env` file located next to your executable :
|
||||
@@ -329,6 +374,7 @@ To run Isaiah, you will need to set the following environment variables in a `.e
|
||||
| `MASTER_HOST` | `string` | For multi-node deployments only. The host used to reach the Master node, specifying the IP address or the hostname, and the port if applicable (e.g. my-server.tld:3000). | Empty |
|
||||
| `MASTER_SECRET` | `string` | For multi-node deployments only. The secret password used to authenticate on the Master node. Note that it should equal the `AUTHENTICATION_SECRET` setting on the Master node. | Empty |
|
||||
| `AGENT_NAME` | `string` | For multi-node deployments only. The name associated with the Agent node as it is displayed on the web interface. It should be unique for each Agent. | Empty |
|
||||
| `MULTI_HOST_ENABLED` | `boolean` | Whether Isaiah should be run in multi-host mode. When enabled, make sure to have your `docker_hosts` file next to the executable. | False |
|
||||
|
||||
> **Note :** Boolean values are case-insensitive, and can be represented via "ON" / "OFF" / "TRUE" / "FALSE" / 0 / 1.
|
||||
|
||||
@@ -421,7 +467,7 @@ I leave here a few ideas that I believe could be implemented, but may require mo
|
||||
- Wrap every command in a "block" (begin - command - end) to easily distinguish user-sent commands from output
|
||||
- Sending to the real terminal the key presses captured from the web (a.k.a sending key presses to a running process)
|
||||
|
||||
Ultimately, please also note that in a multi-node setup, the extra network latency and unexpected buffering from remote terminals may cause additional display artifacts.
|
||||
Ultimately, please also note that in a multi-node / multi-host setup, the extra network latency and unexpected buffering from remote terminals may cause additional display artifacts.
|
||||
|
||||
#### An error happens when spawning a new shell on the server / inside a Docker container
|
||||
|
||||
|
||||
@@ -537,6 +537,7 @@
|
||||
bulk: 'Bulk actions',
|
||||
theme: 'Theme',
|
||||
agent: 'Agent',
|
||||
host: 'Host',
|
||||
}[menu.key]
|
||||
}
|
||||
</span>
|
||||
@@ -634,6 +635,10 @@
|
||||
<span class="cell">< > </span>
|
||||
<span class="cell">switch node</span>
|
||||
</div>
|
||||
<div class="row is-not-interactive">
|
||||
<span class="cell">l k </span>
|
||||
<span class="cell">switch host</span>
|
||||
</div>
|
||||
<div class="row is-not-interactive"></div>
|
||||
<div class="row is-not-interactive">
|
||||
<span class="cell">y n </span>
|
||||
@@ -677,6 +682,10 @@
|
||||
<span class="cell">A </span>
|
||||
<span class="cell">open agent picker</span>
|
||||
</div>
|
||||
<div class="row is-not-interactive">
|
||||
<span class="cell">H </span>
|
||||
<span class="cell">open host picker</span>
|
||||
</div>
|
||||
<div class="row is-not-interactive"></div>
|
||||
<div class="row is-not-interactive">
|
||||
<span class="cell">Ctrl+C </span>
|
||||
@@ -914,13 +923,22 @@
|
||||
if (_state.isLoading)
|
||||
hgetConnectionIndicator('loading').classList.add('is-active');
|
||||
|
||||
// 8. Set communication (master / agent) indicator
|
||||
if (_state.communication.availableAgents.length > 0) {
|
||||
// 8. Set communication (master / agent / host) indicator
|
||||
if (
|
||||
_state.communication.availableAgents.length > 0 ||
|
||||
_state.communication.availableHosts
|
||||
) {
|
||||
hgetConnectionIndicator('communication-target').classList.add(
|
||||
'is-active'
|
||||
);
|
||||
hgetConnectionIndicator('communication-target').textContent =
|
||||
_state.communication.currentAgent || 'Master';
|
||||
|
||||
let fullIndicator = _state.communication.currentAgent || 'Master';
|
||||
if (_state.communication.currentHost)
|
||||
fullIndicator = `${fullIndicator} (${_state.communication.currentHost})`;
|
||||
|
||||
hgetConnectionIndicator(
|
||||
'communication-target'
|
||||
).textContent = fullIndicator;
|
||||
}
|
||||
|
||||
// 9. Reset mobile controls' visibility
|
||||
@@ -982,6 +1000,9 @@
|
||||
if (state.communication.currentAgent)
|
||||
copy.Agent = state.communication.currentAgent;
|
||||
|
||||
if (state.communication.currentHost)
|
||||
copy.Host = state.communication.currentHost;
|
||||
|
||||
wsSocket.send(JSON.stringify(copy));
|
||||
};
|
||||
|
||||
@@ -1046,7 +1067,7 @@
|
||||
/**
|
||||
* @typedef {object} Menu
|
||||
* @property {Array<MenuAction>} actions
|
||||
* @property {'menu'|'bulk'|'theme'|'agent'} key
|
||||
* @property {'menu'|'bulk'|'theme'|'agent'|'host'} key
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -1069,7 +1090,7 @@
|
||||
actions: [],
|
||||
|
||||
/**
|
||||
* @type {'menu'|'bulk'|'theme'|'agent'}
|
||||
* @type {'menu'|'bulk'|'theme'|'agent'|'host'}
|
||||
*/
|
||||
key: null,
|
||||
},
|
||||
@@ -1080,7 +1101,7 @@
|
||||
helper: 'default',
|
||||
|
||||
/**
|
||||
* @type {"menu"|"bulk"|"prompt"|"message"|"tty"|"help"|"theme"|"agent"}
|
||||
* @type {"menu"|"bulk"|"prompt"|"message"|"tty"|"help"|"theme"|"agent"|'host'}
|
||||
*/
|
||||
popup: null,
|
||||
|
||||
@@ -1215,14 +1236,24 @@
|
||||
|
||||
communication: {
|
||||
/**
|
||||
* @type {String}
|
||||
* @type {string}
|
||||
*/
|
||||
currentAgent: null,
|
||||
|
||||
/**
|
||||
* @type {Array<String>}
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
availableAgents: [],
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
currentHost: null,
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
availableHosts: [],
|
||||
},
|
||||
|
||||
_delays: {
|
||||
@@ -1747,6 +1778,15 @@
|
||||
websocketSend({ action: 'clear' });
|
||||
},
|
||||
|
||||
/**
|
||||
* Private - Pick a new host for further communication
|
||||
* @param {MenuAction} action
|
||||
*/
|
||||
_pickHost: function (action) {
|
||||
state.communication.currentHost = action.Label;
|
||||
cmdRun(cmds._init);
|
||||
},
|
||||
|
||||
/**
|
||||
* Public - Quit the app / Quit the current popup
|
||||
* Requires prompt
|
||||
@@ -2382,6 +2422,26 @@
|
||||
cmdRun(cmds._showPopup, 'menu');
|
||||
},
|
||||
|
||||
/**
|
||||
* Public - Show host picker
|
||||
*/
|
||||
host: function () {
|
||||
if (state.communication.availableHosts.length === 0) return;
|
||||
|
||||
state.menu.key = 'host';
|
||||
|
||||
state.menu.actions = state.communication.availableHosts.map((t) => ({
|
||||
RunLocally: true,
|
||||
RequiresResource: false,
|
||||
RequiresMenuAction: true,
|
||||
Label: t,
|
||||
Command: '_pickHost',
|
||||
}));
|
||||
|
||||
state.navigation.currentMenuRow = 1;
|
||||
cmdRun(cmds._showPopup, 'menu');
|
||||
},
|
||||
|
||||
/**
|
||||
* Public - Switch to the previous agent for further communication
|
||||
*/
|
||||
@@ -2448,6 +2508,49 @@
|
||||
|
||||
cmdRun(cmds._init);
|
||||
},
|
||||
|
||||
/**
|
||||
* Public - Switch to the previous host for further communication
|
||||
*/
|
||||
previousHost: function () {
|
||||
if (state.communication.availableHosts.length === 0) return;
|
||||
|
||||
const currentIndex = state.communication.availableHosts.indexOf(
|
||||
state.communication.currentHost
|
||||
);
|
||||
|
||||
if (currentIndex === 0) {
|
||||
state.communication.currentHost =
|
||||
state.communication.availableHosts[
|
||||
state.communication.availableHosts.length - 1
|
||||
];
|
||||
}
|
||||
// Regular case, switching to the previous agent
|
||||
else
|
||||
state.communication.currentHost =
|
||||
state.communication.availableHosts[currentIndex - 1];
|
||||
|
||||
cmdRun(cmds._init);
|
||||
},
|
||||
|
||||
/**
|
||||
* Public - Switch to the next host for further communication
|
||||
*/
|
||||
nextHost: function () {
|
||||
if (state.communication.availableHosts.length === 0) return;
|
||||
|
||||
const currentIndex = state.communication.availableHosts.indexOf(
|
||||
state.communication.currentHost
|
||||
);
|
||||
|
||||
if (currentIndex === state.communication.availableHosts.length - 1) {
|
||||
state.communication.currentHost = state.communication.availableHosts[0];
|
||||
} else
|
||||
state.communication.currentHost =
|
||||
state.communication.availableHosts[currentIndex + 1];
|
||||
|
||||
cmdRun(cmds._init);
|
||||
},
|
||||
};
|
||||
|
||||
// === Variables
|
||||
@@ -2513,6 +2616,10 @@
|
||||
'<': 'previousAgent',
|
||||
'>': 'nextAgent',
|
||||
|
||||
// Hosts
|
||||
k: 'previousHost',
|
||||
l: 'nextHost',
|
||||
|
||||
// Sub commands
|
||||
q: 'quit',
|
||||
d: 'remove',
|
||||
@@ -2528,6 +2635,7 @@
|
||||
w: 'browser',
|
||||
h: 'hub',
|
||||
A: 'agent',
|
||||
H: 'host',
|
||||
G: 'github',
|
||||
T: 'theme',
|
||||
'?': 'help',
|
||||
@@ -2826,11 +2934,22 @@
|
||||
{}
|
||||
);
|
||||
|
||||
// Update agents' list only on the very first init
|
||||
// Update agents list only on the very first init
|
||||
if (state.communication.availableAgents.length === 0)
|
||||
state.communication.availableAgents =
|
||||
notification.Content.Agents || [];
|
||||
|
||||
// Update hosts list only on the very first init
|
||||
if (state.communication.availableHosts.length === 0) {
|
||||
state.communication.availableHosts = notification.Content.Hosts || [];
|
||||
if (state.communication.availableHosts.length > 0) {
|
||||
state.communication.currentHost =
|
||||
state.communication.availableHosts[0];
|
||||
|
||||
cmdRun(cmds._init);
|
||||
}
|
||||
}
|
||||
|
||||
state.isLoading = false;
|
||||
cmdRun(cmds._inspectorTabs);
|
||||
break;
|
||||
|
||||
108
app/main.go
108
app/main.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -51,11 +52,13 @@ func performVerifications() error {
|
||||
}
|
||||
|
||||
// 2. Ensure Docker socket is reachable
|
||||
c, err := client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed Verification : Access to Docker socket -> %s", err)
|
||||
if _os.GetEnv("MULTI_HOST_ENABLED") != "TRUE" {
|
||||
c, err := client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed Verification : Access to Docker socket -> %s", err)
|
||||
}
|
||||
defer c.Close()
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// 3. Ensure server port is available
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%s", _os.GetEnv("SERVER_PORT")))
|
||||
@@ -90,19 +93,53 @@ func performVerifications() error {
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Ensure docker_hosts file is available when multi-host is enabled
|
||||
if _os.GetEnv("MULTI_HOST_ENABLED") == "TRUE" {
|
||||
if _, err := os.Stat("docker_hosts"); errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("Failed Verification : docker_hosts file is missing. Please put it next to the executable")
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Ensure every host is reachable if multi-host is enabled, and docker_hosts is well-formatted
|
||||
if _os.GetEnv("MULTI_HOST_ENABLED") == "TRUE" {
|
||||
raw, err := os.ReadFile("docker_hosts")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed Verification : docker_hosts file can't be read -> %s", err)
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
return fmt.Errorf("Failed Verification : docker_hosts file is empty.")
|
||||
}
|
||||
|
||||
lines := strings.Split(string(raw), "\n")
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("Failed Verification : docker_hosts file isn't properly formatted. Line : -> %s", line)
|
||||
}
|
||||
|
||||
c, err := client.NewClientWithOpts(client.WithHost(parts[1]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed Verification : Access to Docker host -> %s", err)
|
||||
}
|
||||
|
||||
_, err = c.Ping(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed Verification : Access to Docker host -> %s", err)
|
||||
}
|
||||
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Entrypoint
|
||||
func main() {
|
||||
// Automatically discover the Docker host on the machine
|
||||
discoveredHost, err := _client.DiscoverDockerHost()
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
return
|
||||
}
|
||||
os.Setenv("DOCKER_HOST", discoveredHost)
|
||||
|
||||
// Load default settings via default.env file (workaround since the file is embed)
|
||||
defaultSettings, _ := godotenv.Unmarshal(defaultEnv)
|
||||
for k, v := range defaultSettings {
|
||||
@@ -112,11 +149,21 @@ func main() {
|
||||
}
|
||||
|
||||
// Load custom settings via .env file
|
||||
err = godotenv.Overload(".env")
|
||||
err := godotenv.Overload(".env")
|
||||
if err != nil {
|
||||
log.Print("No .env file provided, will continue with system env")
|
||||
}
|
||||
|
||||
if _os.GetEnv("MULTI_HOST_ENABLED") != "TRUE" {
|
||||
// Automatically discover the Docker host on the machine
|
||||
discoveredHost, err := _client.DiscoverDockerHost()
|
||||
if err != nil {
|
||||
log.Print(err.Error())
|
||||
return
|
||||
}
|
||||
os.Setenv("DOCKER_HOST", discoveredHost)
|
||||
}
|
||||
|
||||
// Perform initial verifications
|
||||
if _os.GetEnv("SKIP_VERIFICATIONS") != "TRUE" {
|
||||
// Ensure everything is ready for our app
|
||||
@@ -130,9 +177,38 @@ func main() {
|
||||
}
|
||||
|
||||
// Set up everything (Melody instance, Docker client, Server settings)
|
||||
_server := server.Server{
|
||||
Melody: melody.New(),
|
||||
Docker: _client.NewClientWithOpts(client.FromEnv),
|
||||
var _server server.Server
|
||||
if _os.GetEnv("MULTI_HOST_ENABLED") != "TRUE" {
|
||||
_server = server.Server{
|
||||
Melody: melody.New(),
|
||||
Docker: _client.NewClientWithOpts(client.FromEnv),
|
||||
}
|
||||
} else {
|
||||
_server = server.Server{
|
||||
Melody: melody.New(),
|
||||
}
|
||||
|
||||
// Populate server's known hosts when multi-host is enabled
|
||||
_server.Hosts = make(server.HostsArray, 0)
|
||||
var firstHost string
|
||||
|
||||
raw, _ := os.ReadFile("docker_hosts")
|
||||
lines := strings.Split(string(raw), "\n")
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(line, " ")
|
||||
|
||||
_server.Hosts = append(_server.Hosts, []string{parts[0], parts[1]})
|
||||
|
||||
if len(firstHost) == 0 {
|
||||
firstHost = parts[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Set default Docker client on the first known host
|
||||
_server.SetHost(firstHost)
|
||||
}
|
||||
_server.Melody.Config.MaxMessageSize = _strconv.ParseInt(_os.GetEnv("SERVER_MAX_READ_SIZE"), 10, 64)
|
||||
|
||||
|
||||
3
app/sample.docker_hosts
Normal file
3
app/sample.docker_hosts
Normal file
@@ -0,0 +1,3 @@
|
||||
Local unix:///var/run/docker.sock
|
||||
Host-1 tcp://your-domain.tld:your-port
|
||||
Host-2 tcp://your-ip:your-port
|
||||
14
app/server/server/hosts.go
Normal file
14
app/server/server/hosts.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package server
|
||||
|
||||
// Represent an array of Isaiah hosts ([name, hostname])
|
||||
type HostsArray [][]string
|
||||
|
||||
func (hosts HostsArray) ToStrings() []string {
|
||||
arr := make([]string, 0)
|
||||
|
||||
for _, v := range hosts {
|
||||
arr = append(arr, v[0])
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
_client "will-moss/isaiah/server/_internal/client"
|
||||
_io "will-moss/isaiah/server/_internal/io"
|
||||
_os "will-moss/isaiah/server/_internal/os"
|
||||
_session "will-moss/isaiah/server/_internal/session"
|
||||
@@ -22,6 +23,7 @@ type Server struct {
|
||||
Melody *melody.Melody
|
||||
Docker *client.Client
|
||||
Agents AgentsArray
|
||||
Hosts HostsArray
|
||||
}
|
||||
|
||||
// Represent a command handler, used only _internally
|
||||
@@ -80,6 +82,7 @@ func (server *Server) runCommand(session _session.GenericSession, command ui.Com
|
||||
volumes := resources.VolumesList(server.Docker)
|
||||
networks := resources.NetworksList(server.Docker)
|
||||
agents := server.Agents.ToStrings()
|
||||
hosts := server.Hosts.ToStrings()
|
||||
|
||||
if len(containers) > 0 {
|
||||
columns := strings.Split(_os.GetEnv("COLUMNS_CONTAINERS"), ",")
|
||||
@@ -108,7 +111,7 @@ func (server *Server) runCommand(session _session.GenericSession, command ui.Com
|
||||
server.SendNotification(
|
||||
session,
|
||||
ui.NotificationInit(ui.NotificationParams{
|
||||
Content: ui.JSON{"Tabs": tabs, "Agents": agents},
|
||||
Content: ui.JSON{"Tabs": tabs, "Agents": agents, "Hosts": hosts},
|
||||
}))
|
||||
|
||||
// Command : Agent-only - Clear TTY / Stream
|
||||
@@ -291,6 +294,13 @@ func (server *Server) Handle(session _session.GenericSession, message ...[]byte)
|
||||
return
|
||||
}
|
||||
|
||||
// When multi-host is enabled, set the appropriate host before interacting with Docker
|
||||
if _os.GetEnv("MULTI_HOST_ENABLED") == "TRUE" {
|
||||
if command.Host != "" {
|
||||
server.SetHost(command.Host)
|
||||
}
|
||||
}
|
||||
|
||||
// # - Dispatch the command to the appropriate handler
|
||||
var h handler
|
||||
|
||||
@@ -327,3 +337,15 @@ func (server *Server) Handle(session _session.GenericSession, message ...[]byte)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) SetHost(name string) {
|
||||
var correspondingHost []string
|
||||
for _, v := range s.Hosts {
|
||||
if v[0] == name {
|
||||
correspondingHost = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s.Docker = _client.NewClientWithOpts(client.WithHost(correspondingHost[1]))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ type Command struct {
|
||||
Action string
|
||||
Args map[string]interface{}
|
||||
Agent string
|
||||
Host string
|
||||
Initiator string
|
||||
Sequence int32
|
||||
}
|
||||
|
||||
11
examples/docker-compose.host.yml
Normal file
11
examples/docker-compose.host.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
services:
|
||||
isaiah:
|
||||
image: mosswill/isaiah:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- my_docker_hosts:/docker_hosts
|
||||
environment:
|
||||
AUTHENTICATION_SECRET: "your-very-long-and-mysterious-secret"
|
||||
MULTI_HOST_ENABLED: "TRUE"
|
||||
Reference in New Issue
Block a user