Skip to content

Commit 528e7a3

Browse files
committed
Add Docker build configuration for server
The final image is around 150M in size, which is totally reasonable. Most of the size is added by the maps.
1 parent 51edc9d commit 528e7a3

File tree

6 files changed

+295
-0
lines changed

6 files changed

+295
-0
lines changed

.dockerignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# remember: the last(!) line that matches a specific file defines the behavior
2+
3+
# ignore everything, then unblock one by one (note: . does not work "for historical reasons")
4+
*
5+
6+
#!.git/
7+
!README.md
8+
!CMakeLists.txt
9+
!config/
10+
!data/
11+
!doc/
12+
!docker/
13+
!src/
14+
15+
# ignore the Dockerfile so changes in it don't retrigger an entire build
16+
!docker/Dockerfile

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ out
3232
*.core
3333
*.diff
3434
*.patch
35+
!docker/serverip.patch
3536
*.out
3637
home/
3738
cache/
@@ -54,3 +55,4 @@ GTAGS
5455
GRTAGS
5556
.idea/
5657
*build*/
58+
*.*swp*

docker/Dockerfile

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# first, let's build the software
2+
# this is going to take the most time
3+
FROM alpine:3.17 AS builder
4+
5+
RUN apk add gcc g++ binutils sdl2-dev zlib-dev perl git wget ca-certificates coreutils \
6+
sdl2_image-dev sdl2_mixer-dev psmisc cmake ninja
7+
8+
RUN mkdir /blue-nebula
9+
WORKDIR /blue-nebula
10+
11+
COPY src/ /blue-nebula/src
12+
COPY config/ /blue-nebula/config
13+
COPY data/ /blue-nebula/data
14+
15+
COPY CMakeLists.txt /blue-nebula/
16+
COPY README.md /blue-nebula/
17+
COPY docker/serverip.patch /serverip.patch
18+
19+
RUN git apply < /serverip.patch && \
20+
ls -al && \
21+
mkdir build && \
22+
cd build && \
23+
# TODO: server-only build configuration
24+
cmake .. -DCMAKE_BUILD_TYPE=Release -G Ninja -DCMAKE_INSTALL_PREFIX=/blue-nebula/install -DBUILD_CLIENT=OFF && \
25+
ninja -v && \
26+
ninja install
27+
28+
29+
# next, we convert the example server config to a template
30+
# this is done in no time, so we do not have to benefit from Docker's caching a lot there
31+
FROM python:3.10-alpine AS template
32+
33+
COPY docker/generate-servinit-template.py doc/examples/servinit.cfg /
34+
35+
RUN python3 /generate-servinit-template.py /servinit.cfg > /servinit.tmpl
36+
37+
38+
# finally, let's build the final runtime image (doesn't need all of the build dependencies)
39+
FROM alpine:3.17
40+
41+
MAINTAINER "TheAssassin <theassassin@assassinate-you.net>"
42+
43+
RUN apk add tini && \
44+
apk add --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing dockerize
45+
46+
RUN adduser -S -D -h /blue-nebula blue-nebula
47+
WORKDIR /blue-nebula
48+
49+
COPY --from=template /servinit.tmpl /
50+
COPY --from=builder /blue-nebula/install /blue-nebula/install
51+
52+
# keep the directory read-only but allow writing to the file
53+
# this is needed since dockerize runs as blue-nebula and needs to modify the file
54+
# we might want to run dockerize separately and then run the process as blue-nebula
55+
RUN mkdir /blue-nebula/.blue-nebula && \
56+
install -o blue-nebula -m 0644 /dev/null /blue-nebula/.blue-nebula/servinit.cfg
57+
58+
# install runtime dependencies
59+
RUN apk add --no-cache libstdc++ libgcc
60+
61+
# keep files copied from elsewhere read-only to prevent the process from writing files
62+
USER blue-nebula
63+
64+
EXPOSE 28799/udp 28800/udp 28801/udp 28802/udp
65+
66+
ENTRYPOINT ["/sbin/tini", "--"]
67+
68+
CMD dockerize -template /servinit.tmpl:/blue-nebula/.blue-nebula/servinit.cfg install/bin/blue-nebula_server_linux

docker/docker-compose.yml.example

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
version: "3"
2+
3+
services:
4+
bn-server:
5+
image: ghcr.io/blue-nebula/server:latest
6+
restart: unless-stopped
7+
8+
# adjust accordingly if you change the ports in the config
9+
# note: the ports must be in sync! otherwise, the server will announce the wrong ports to the master
10+
ports:
11+
- "0.0.0.0:28801:28801/udp"
12+
- "0.0.0.0:28802:28802/udp"
13+
14+
# most-if-not-all environment variables are exposed as upper-case environment variables as shown below
15+
environment:
16+
ADMIN_PASS: "so-secret"
17+
SERVER_TYPE: 1
18+
SERVER_PORT: 28801
19+
SV_SERVERCLIENTS: 16
20+
SV_SERVERDESC: "Example BN server running in Docker"
21+
SV_SERVERMOTD: "Welcome to this example Blue Nebula server hosted in a Docker container!"
22+
23+
# to add additional maps, you can mount a directory as shown below
24+
volumes:
25+
- ./custom-maps:/blue-nebula/.blue-nebula/maps/
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#! /usr/bin/env python3
2+
3+
import re
4+
import sys
5+
import textwrap
6+
7+
8+
class ParsingException(Exception):
9+
pass
10+
11+
12+
def convert_variable(variable_name: str, default_value: str, comment: str, indentation: int = None):
13+
if indentation is None:
14+
indentation = 0
15+
16+
variable_name_upper = variable_name.upper()
17+
env_var_name = ".Env.%s" % variable_name_upper
18+
19+
lines = []
20+
lines.append("{{ if (contains .Env \"%s\") -}}" % variable_name_upper)
21+
#lines.append(comment)
22+
#lines.append("// default/example value: %s" % default_value)
23+
lines.append("%s \"{{ %s }}\"" % (variable_name, env_var_name))
24+
lines.append("{{ else -}}")
25+
lines.append("// %s %s %s" % (variable_name, default_value, comment))
26+
lines.append("{{ end -}}")
27+
lines.append("")
28+
29+
return textwrap.indent("\n".join(lines), " "*indentation)
30+
31+
32+
def parse_and_convert_variable(pattern: str, line: str, indentation: int = None):
33+
match = re.match(pattern, line)
34+
35+
if not match:
36+
return
37+
38+
return convert_variable(*match.groups(), indentation)
39+
40+
41+
def parse_regular_variable(line: str):
42+
return parse_and_convert_variable(r"^//\s?((?:sv_|admin|server)\w+)\s+(.+)\s+(\/\/.*)$", line)
43+
44+
45+
def parse_rehashing_variable(line: str):
46+
return parse_and_convert_variable(r"^ //\s*((?!irc)\w+)\s+(.+)\s+(\/\/.*)$", line, 4)
47+
48+
49+
def parse_add_variable(line: str):
50+
match = re.match(r"^//\s? (add\w+)\s+(.+)\s*(|\/\/.*)$", line)
51+
52+
if not match:
53+
return
54+
55+
variable_name, default_value, comment = match.groups()
56+
57+
variable_name_upper = variable_name.upper()
58+
env_var_name = ".Env.%s" % variable_name_upper
59+
60+
lines = []
61+
lines.append("{{ if (contains .Env \"%s\") -}}" % variable_name_upper)
62+
#lines.append(comment)
63+
#lines.append("// default/example value: %s" % default_value)
64+
lines.append("{{ range $e := ( split %s \";\" ) -}}" % env_var_name)
65+
lines.append("%s {{ $e }}" % variable_name)
66+
lines.append("{{ end -}}")
67+
lines.append("{{ else -}}")
68+
lines.append(line)
69+
lines.append("{{ end -}}")
70+
lines.append("")
71+
72+
return "\n".join(lines)
73+
74+
75+
def parse_and_convert_line(line: str) -> str:
76+
for f in [parse_regular_variable, parse_rehashing_variable, parse_add_variable]:
77+
parsed = f(line)
78+
79+
if parsed is not None:
80+
return parsed
81+
82+
else:
83+
return line
84+
85+
86+
def make_irc_section():
87+
return textwrap.dedent(
88+
"""
89+
{{ if (contains .Env "ENABLE_IRC") -}}
90+
// special single-server IRC configuration, suitable for our Docker deployment
91+
// setting ENABLE_IRC to some value will be sufficient in most cases
92+
if (= $rehashing 0) [
93+
ircfilter {{ default .Env.IRC_FILTER "1" }} // defines the way the colour-to-irc filter works; 0 = off, "1" = convert, 2 = strip
94+
95+
ircaddrelay ircrelay {{ default .Env.IRC_RELAY_HOSTNAME "localhost" }} {{ default .Env.IRC_RELAY_PORT "6667" }} {{ default .Env.IRC_RELAY_NICK "re-server" }}
96+
97+
{{ if (contains .Env "IRC_BIND_ADDRESS") -}}
98+
ircbind ircrelay {{ .Env.IRC_BIND_ADDRESS }} // use this only if you need to bind to a specific address, eg. multihomed machines
99+
{{ end -}}
100+
101+
{{ if (contains .Env "IRC_SERVER_PASS" ) -}}
102+
ircpass ircrelay {{ .Env.IRC_SERVER_PASS }} // some networks can use the PASS field to identify to nickserv
103+
{{ end -}}
104+
105+
{{ if (contains .Env "IRC_CHANNELS") -}}
106+
{{ range $e := ( split .Env.IRC_CHANNELS "," ) -}}
107+
ircaddchan ircrelay "{{ $e }}"
108+
ircrelaychan ircrelay "{{ $e }}" 3
109+
{{ end -}}
110+
{{ end -}}
111+
112+
ircconnect ircrelay // and tell it to connect!
113+
]
114+
{{ end -}}
115+
"""
116+
)
117+
118+
119+
def make_additional_vars_section():
120+
text = textwrap.dedent(
121+
"""
122+
{{ if (contains .Env "ADDITIONAL_VARS") -}}
123+
// additional variables
124+
{{ range $e := (split .Env.ADDITIONAL_VARS ";") -}}
125+
{{ $a := (split $e "=") -}}
126+
{{ index $a 0 }} {{ index $a 1 }}
127+
{{ end -}}
128+
{{ end -}}
129+
130+
{{ if (contains .Env "SV_DUELMAXQUEUED") -}}
131+
sv_duelmaxqueued "{{ .Env.SV_DUELMAXQUEUED }}"
132+
{{ end -}}
133+
"""
134+
)
135+
136+
for i in ["duelmaxqueued", "teamneutralcolour"]:
137+
pass
138+
text += textwrap.dedent(
139+
"""
140+
{{{{ if (contains .Env "SV_{upper}") -}}}}
141+
sv_{lower} "{{{{ .Env.SV_{upper} }}}}"
142+
{{{{ end -}}}}
143+
""".format(lower=i, upper=i.upper())
144+
)
145+
146+
return text
147+
148+
149+
def main():
150+
with open(sys.argv[1]) as f:
151+
lines = f.read().splitlines()
152+
153+
for line in lines:
154+
print(parse_and_convert_line(line))
155+
156+
print(make_irc_section())
157+
print(make_additional_vars_section())
158+
159+
160+
if __name__ == "__main__":
161+
try:
162+
sys.exit(main())
163+
except BrokenPipeError:
164+
pass

docker/serverip.patch

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
diff --git a/src/engine/server.cpp b/src/engine/server.cpp
2+
index a8530e43..89e63f4f 100644
3+
--- a/src/engine/server.cpp
4+
+++ b/src/engine/server.cpp
5+
@@ -1329,6 +1329,7 @@ int setupserversockets()
6+
if(!servertype || (serverhost && pongsock != ENET_SOCKET_NULL)) return servertype;
7+
8+
ENetAddress address = { ENET_HOST_ANY, enet_uint16(serverport) };
9+
+#if 0
10+
if(*serverip)
11+
{
12+
if(enet_address_set_host(&address, serverip) < 0)
13+
@@ -1338,6 +1339,7 @@ int setupserversockets()
14+
}
15+
else serveraddress.host = address.host;
16+
}
17+
+#endif
18+
19+
if(!serverhost)
20+
{

0 commit comments

Comments
 (0)