Skip to content

Commit 7c0ba55

Browse files
committed
Support multi-host operation
1 parent 4697a93 commit 7c0ba55

3 files changed

Lines changed: 111 additions & 7 deletions

File tree

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,43 @@ If a mod requires additional packages to be installed, each container will still
6262

6363
Note that the Modmanager container itself does not support applying mods *or* custom files/services.
6464

65+
### Multiple Hosts
66+
67+
>[!WARNING]
68+
>Make sure you fully understand what you're doing before you try and set this up as there are lots of ways it can go wrong.
69+
70+
Modmanager can query & download mods for remote hosts, as well as the one on which it is installed. At a very basic level if you're just using the DOCKER_MODS env and not the docker integration, simply mount the `/modcache` folder on your remote host(s), ensuring it is writeable by all participating containers.
71+
72+
If you are using the docker integration, our only supported means for connecting to remote hosts is [our socket proxy container](). Run an instance on each remote host:
73+
74+
>[!WARNING]
75+
>DO NOT expose a socket proxy to your LAN that allows any write operations (`POST=1`, `ALLOW_RESTART=1`, etc) or that exposes any more information than is absolutely necessary. NEVER expose a socket proxy to your WAN.
76+
77+
```yml
78+
modmanager-dockerproxy:
79+
image: lscr.io/linuxserver/socket-proxy:latest
80+
container_name: modmanager-dockerproxy
81+
environment:
82+
- CONTAINERS=1
83+
- POST=0
84+
volumes:
85+
- /var/run/docker.sock:/var/run/docker.sock:ro
86+
tmpfs:
87+
- /run:exec
88+
ports:
89+
- 2375:2375
90+
restart: unless-stopped
91+
read_only: true
92+
```
93+
94+
And then add it to the `DOCKER_MODS_EXTRA_HOSTS` env using the full protocol and port, e.g.
95+
96+
```yaml
97+
- DOCKER_MODS_EXTRA_HOSTS=tcp://host1.example.com:2375|tcp://host2.example.com:2375|tcp://192.168.0.5:2375
98+
```
99+
100+
As above you will need to mount the `/modcache` folder on your remote host(s), ensuring it is writeable by all participating containers.
101+
65102
### Security considerations
66103

67104
Mapping `docker.sock` is a potential security liability because docker has root access on the host and any process that has full access to `docker.sock` would also have root access on the host. Docker api has no built-in way to set limitations on access, however, you can use a proxy for the `docker.sock` via a solution like [our docker socket proxy](https://github.com/linuxserver/docker-socket-proxy), which adds the ability to limit access. Then you would just set `DOCKER_HOST=` environment variable to point to the proxy address.
@@ -84,6 +121,7 @@ services:
84121
environment:
85122
- DOCKER_MODS= `#optional`
86123
- DOCKER_HOST= `#optional`
124+
- DOCKER_MODS_EXTRA_HOSTS= `#optional`
87125
volumes:
88126
- /path/to/modcache:/modcache
89127
- /var/run/docker.sock:/var/run/docker.sock:ro `#optional`
@@ -97,6 +135,7 @@ docker run -d \
97135
--name=modmanager \
98136
-e DOCKER_MODS= `#optional` \
99137
-e DOCKER_HOST= `#optional` \
138+
-e DOCKER_MODS_EXTRA_HOSTS= `#optional` \
100139
-v /path/to/modcache:/modcache \
101140
-v /var/run/docker.sock:/var/run/docker.sock:ro `#optional` \
102141
--restart unless-stopped \
@@ -111,6 +150,7 @@ Containers are configured using parameters passed at runtime (such as those abov
111150
| :----: | --- |
112151
| `-e DOCKER_MODS=` | Pipe-delimited (`\|`) list of mods to download |
113152
| `-e DOCKER_HOST=` | Specify the docker endpoint to use if not using the docker.sock |
153+
| `-e DOCKER_MODS_EXTRA_HOSTS=` | Pipe-delimited (`\|`) list of additional hosts to query & download mods for. See app setup section for details. |
114154
| `-v /modcache` | Modmanager mod storage. |
115155
| `-v /var/run/docker.sock:ro` | Mount the host docker socket into the container. |
116156

readme-vars.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,43 @@ full_custom_readme: |
6666
6767
Note that the Modmanager container itself does not support applying mods *or* custom files/services.
6868
69+
### Multiple Hosts
70+
71+
>[!WARNING]
72+
>Make sure you fully understand what you're doing before you try and set this up as there are lots of ways it can go wrong.
73+
74+
Modmanager can query & download mods for remote hosts, as well as the one on which it is installed. At a very basic level if you're just using the DOCKER_MODS env and not the docker integration, simply mount the `/modcache` folder on your remote host(s), ensuring it is writeable by all participating containers.
75+
76+
If you are using the docker integration, our only supported means for connecting to remote hosts is [our socket proxy container](). Run an instance on each remote host:
77+
78+
>[!WARNING]
79+
>DO NOT expose a socket proxy to your LAN that allows any write operations (`POST=1`, `ALLOW_RESTART=1`, etc) or that exposes any more information than is absolutely necessary. NEVER expose a socket proxy to your WAN.
80+
81+
```yml
82+
modmanager-dockerproxy:
83+
image: lscr.io/linuxserver/socket-proxy:latest
84+
container_name: modmanager-dockerproxy
85+
environment:
86+
- CONTAINERS=1
87+
- POST=0
88+
volumes:
89+
- /var/run/docker.sock:/var/run/docker.sock:ro
90+
tmpfs:
91+
- /run:exec
92+
ports:
93+
- 2375:2375
94+
restart: unless-stopped
95+
read_only: true
96+
```
97+
98+
And then add it to the `DOCKER_MODS_EXTRA_HOSTS` env using the full protocol and port, e.g.
99+
100+
```yaml
101+
- DOCKER_MODS_EXTRA_HOSTS=tcp://host1.example.com:2375|tcp://host2.example.com:2375|tcp://192.168.0.5:2375
102+
```
103+
104+
As above you will need to mount the `/modcache` folder on your remote host(s), ensuring it is writeable by all participating containers.
105+
69106
### Security considerations
70107
71108
Mapping `docker.sock` is a potential security liability because docker has root access on the host and any process that has full access to `docker.sock` would also have root access on the host. Docker api has no built-in way to set limitations on access, however, you can use a proxy for the `docker.sock` via a solution like [our docker socket proxy](https://github.com/linuxserver/docker-socket-proxy), which adds the ability to limit access. Then you would just set `DOCKER_HOST=` environment variable to point to the proxy address.
@@ -88,6 +125,7 @@ full_custom_readme: |
88125
environment:
89126
- DOCKER_MODS= `#optional`
90127
- DOCKER_HOST= `#optional`
128+
- DOCKER_MODS_EXTRA_HOSTS= `#optional`
91129
volumes:
92130
- /path/to/modcache:/modcache
93131
- /var/run/docker.sock:/var/run/docker.sock:ro `#optional`
@@ -101,6 +139,7 @@ full_custom_readme: |
101139
--name=modmanager \
102140
-e DOCKER_MODS= `#optional` \
103141
-e DOCKER_HOST= `#optional` \
142+
-e DOCKER_MODS_EXTRA_HOSTS= `#optional` \
104143
-v /path/to/modcache:/modcache \
105144
-v /var/run/docker.sock:/var/run/docker.sock:ro `#optional` \
106145
--restart unless-stopped \
@@ -115,6 +154,7 @@ full_custom_readme: |
115154
| :----: | --- |
116155
| `-e DOCKER_MODS=` | Pipe-delimited (`\|`) list of mods to download |
117156
| `-e DOCKER_HOST=` | Specify the docker endpoint to use if not using the docker.sock |
157+
| `-e DOCKER_MODS_EXTRA_HOSTS=` | Pipe-delimited (`\|`) list of additional hosts to query & download mods for. See app setup section for details. |
118158
| `-v /modcache` | Modmanager mod storage. |
119159
| `-v /var/run/docker.sock:ro` | Mount the host docker socket into the container. |
120160

root/app/update-mods.sh

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,50 @@
11
#!/usr/bin/with-contenv bash
22
# shellcheck shell=bash
33

4-
# Main script loop
5-
if [[ -e "/var/run/docker.sock" ]] || [[ -n "${DOCKER_HOST}" ]]; then
4+
find_docker_mods() {
65
# Mods provided via Docker
7-
echo -e "[mod-init] Searching all containers for DOCKER_MODS..."
8-
for CONTAINER in $(docker ps -q); do
9-
CONTAINER_MODS=$(docker inspect "${CONTAINER}" | jq -r '.[].Config.Env | to_entries | map(select(.value | match("DOCKER_MODS="))) | .[].value')
10-
CONTAINER_NAME=$(docker inspect "${CONTAINER}" | jq -r .[].Name | cut -d '/' -f2)
6+
if [[ "${2}" != "default" ]]; then
7+
docker context create "${2}" --docker "host=${1}" >/dev/null 2>&1
8+
fi
9+
docker --context "${2}" ps -q >/dev/null 2>&1 || DOCKER_MOD_CONTEXT_FAIL=true
10+
if [[ "${DOCKER_MOD_CONTEXT_FAIL}" == "true" ]]; then
11+
unset DOCKER_MOD_CONTEXT_FAIL
12+
echo "[mod-init] (ERROR) Cannot connect to the Docker daemon at ${2}, skipping host"
13+
return
14+
fi
15+
echo -e "[mod-init] Searching all containers in the ${2} context for DOCKER_MODS..."
16+
for CONTAINER in $(docker --context "${2}" ps -q); do
17+
CONTAINER_MODS=$(docker --context "${2}" inspect "${CONTAINER}" | jq -r '.[].Config.Env | to_entries | map(select(.value | match("DOCKER_MODS="))) | .[].value')
18+
CONTAINER_NAME=$(docker --context "${2}" inspect "${CONTAINER}" | jq -r .[].Name | cut -d '/' -f2)
1119
if [[ -n ${CONTAINER_MODS} ]]; then
1220
CONTAINER_MODS=$(awk -F '=' '{print $2}' <<< "${CONTAINER_MODS}")
1321
for CONTAINER_MOD in $(tr '|' '\n' <<< "${CONTAINER_MODS}"); do
1422
if [[ "${DOCKER_MODS}" =~ ${CONTAINER_MOD} ]]; then
1523
echo -e "[mod-init] ${CONTAINER_MOD} already in mod list, skipping"
1624
else
17-
echo -e "[mod-init] Found new mod ${CONTAINER_MODS} for container ${CONTAINER_NAME}"
25+
echo -e "[mod-init] Found new mod ${CONTAINER_MOD} for container ${CONTAINER_NAME}"
1826
DOCKER_MODS="${DOCKER_MODS}|${CONTAINER_MOD}"
1927
DOCKER_MODS="${DOCKER_MODS#|}"
2028
fi
2129
done
2230
fi
2331
done
32+
if [[ "${2}" != "default" ]]; then
33+
docker context rm "${2}" >/dev/null
34+
fi
35+
}
36+
37+
# Main script loop
38+
if [[ -e "/var/run/docker.sock" ]] || [[ -n "${DOCKER_HOST}" ]]; then
39+
find_docker_mods "${DOCKER_HOST:-docker.sock}" "default"
40+
fi
41+
42+
if [[ -n "${DOCKER_MODS_EXTRA_HOSTS}" ]]; then
43+
for DOCKER_MOD_CONTEXT in $(echo "${DOCKER_MODS_EXTRA_HOSTS}" | tr '|' '\n'); do
44+
DOCKER_MOD_CONTEXT_NAME="${DOCKER_MOD_CONTEXT##*//}"
45+
DOCKER_MOD_CONTEXT_NAME="${DOCKER_MOD_CONTEXT_NAME%%:*}"
46+
find_docker_mods "${DOCKER_MOD_CONTEXT}" "${DOCKER_MOD_CONTEXT_NAME}"
47+
done
2448
fi
2549

2650
if [[ -n "${DOCKER_MODS}" ]]; then

0 commit comments

Comments
 (0)