mirror of
https://github.com/factoriotools/factorio-docker.git
synced 2025-03-13 11:40:31 +01:00
Merge 54a03517ba33db85c5f85fb63d578ba2874463c1 into d91c45fa62a7385b5c4cf76db5f22fde69ea531e
This commit is contained in:
commit
b10db90acf
@ -97,6 +97,15 @@ docker run -d -it \
|
||||
docker attach factorio
|
||||
```
|
||||
|
||||
### RCON
|
||||
|
||||
Alternativly (e.g. for scripting) the RCON connection can be used to send commands to the running factorio server.
|
||||
This does not require the RCON connection to be exposed.
|
||||
|
||||
```shell
|
||||
docker exec factorio rcon /h
|
||||
```
|
||||
|
||||
### Upgrading
|
||||
|
||||
Before upgrading backup the save. It's easy to make a save in the client.
|
||||
|
35
docker-compose.yml
Normal file
35
docker-compose.yml
Normal file
@ -0,0 +1,35 @@
|
||||
version: "2"
|
||||
services:
|
||||
factorio:
|
||||
container_name: factorio
|
||||
image: factoriotools/factorio:stable
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "34197:34197/udp"
|
||||
- "27015:27015/tcp"
|
||||
volumes:
|
||||
- ./data:/factorio
|
||||
environment:
|
||||
- UPDATE_MODS_ON_START=true
|
||||
#labels:
|
||||
# # Labels to allow autoupdate only if no players are online
|
||||
# - com.centurylinklabs.watchtower.enable=true
|
||||
# - com.centurylinklabs.watchtower.scope=factorio
|
||||
# - com.centurylinklabs.watchtower.lifecycle.pre-update="/players-online.sh"
|
||||
|
||||
# Uncomment the following files to use watchtower for updating the factorio container
|
||||
# Full documentation of watchtower: https://github.com/containrrr/watchtower
|
||||
#watchtower:
|
||||
# container_name: watchtower_factorio
|
||||
# image: containrrr/watchtower
|
||||
# restart: unless-stopped
|
||||
# volumes:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock
|
||||
# environment:
|
||||
# # Only update containers which have the option 'watchtower.enable=true' set
|
||||
# - WATCHTOWER_TIMEOUT=30s
|
||||
# - WATCHTOWER_LABEL_ENABLE=true
|
||||
# - WATCHTOWER_POLL_INTERVAL=3600
|
||||
# - WATCHTOWER_LIFECYCLE_HOOKS=true
|
||||
# labels:
|
||||
# - com.centurylinklabs.watchtower.scope=factorio
|
@ -1,5 +1,14 @@
|
||||
FROM debian:stable-slim
|
||||
# build rcon client
|
||||
FROM debian:stable-slim AS rcon-builder
|
||||
RUN apt-get -q update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get -qy install build-essential
|
||||
|
||||
WORKDIR /src
|
||||
COPY rcon/* /src
|
||||
RUN make
|
||||
|
||||
# build factorio image
|
||||
FROM debian:stable-slim
|
||||
LABEL maintainer="https://github.com/factoriotools/factorio-docker"
|
||||
|
||||
ARG USER=factorio
|
||||
@ -81,6 +90,7 @@ RUN set -ox pipefail \
|
||||
|
||||
COPY files/*.sh /
|
||||
COPY files/config.ini /opt/factorio/config/config.ini
|
||||
COPY --from=rcon-builder /src/rcon /bin/rcon
|
||||
|
||||
VOLUME /factorio
|
||||
EXPOSE $PORT/udp $RCON_PORT/tcp
|
||||
|
11
docker/files/players-online.sh
Executable file
11
docker/files/players-online.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
PLAYERS=$(rcon /players)
|
||||
ONLINE_COUNT=$(echo "$PLAYERS" | grep -c " (online)$")
|
||||
|
||||
if [[ "$ONLINE_COUNT" -gt "0" ]]; then
|
||||
echo "$PLAYERS"
|
||||
# exit with 75 (EX_TEMPFAIL) so watchtower skips the update
|
||||
# https://containrrr.dev/watchtower/lifecycle-hooks/
|
||||
exit 75
|
||||
fi
|
13
docker/rcon/Makefile
Normal file
13
docker/rcon/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
# Optimization
|
||||
OPT = -O3 -flto
|
||||
TARGET = rcon
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -std=c17 -Wall -Wextra -pedantic $(OPT)
|
||||
REMOVE = rm -f
|
||||
|
||||
all:
|
||||
$(CC) $(CFLAGS) main.c -o $(TARGET)
|
||||
|
||||
clean:
|
||||
$(REMOVE) $(TARGET)
|
217
docker/rcon/main.c
Normal file
217
docker/rcon/main.c
Normal file
@ -0,0 +1,217 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define MIN_PACKET 10
|
||||
#define MAX_PACKET 4096
|
||||
#define MAX_BODY (MAX_PACKET - (3 * sizeof(uint32_t)) - 2)
|
||||
|
||||
#define RCON_HOST "127.0.0.1"
|
||||
|
||||
typedef enum {
|
||||
RCON_TYPE_RESPONSE = 0,
|
||||
RCON_TYPE_EXECCOMMAND = 2,
|
||||
RCON_TYPE_AUTH_RESPONSE = 2,
|
||||
RCON_TYPE_AUTH = 3,
|
||||
} packet_type;
|
||||
|
||||
typedef struct {
|
||||
uint32_t length;
|
||||
uint32_t id;
|
||||
packet_type type;
|
||||
char body[MAX_BODY];
|
||||
} packet;
|
||||
|
||||
int rcon_open(const char *port);
|
||||
void rcon_create(packet* pkt, packet_type type, const char* body);
|
||||
bool rcon_send(int rcon_socket, const packet* pkt);
|
||||
bool rcon_auth(int rcon_socket, const char* password);
|
||||
bool rcon_recv(int rcon_socket, packet* pkt, packet_type expected_type);
|
||||
char* combine_args(int argc, char* argv[]);
|
||||
char* read_password(const char* conf_dir);
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "error: missing command argument\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
srand((unsigned int)time(NULL));
|
||||
|
||||
const char* port = getenv("RCON_PORT");
|
||||
if (port == NULL) {
|
||||
fprintf(stderr, "error: missing $RCON_PORT env\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const char* conf_dir = getenv("CONFIG");
|
||||
if (conf_dir == NULL) {
|
||||
fprintf(stderr, "error: missing $CONFIG env");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int rcon_socket = rcon_open(port);
|
||||
if (rcon_socket == -1) {
|
||||
fprintf(stderr, "error: could not connect\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!rcon_auth(rcon_socket, read_password(conf_dir))) {
|
||||
fprintf(stderr, "error: login failed\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
packet pkt;
|
||||
rcon_create(&pkt, RCON_TYPE_EXECCOMMAND, combine_args(argc, argv));
|
||||
if (!rcon_send(rcon_socket, &pkt)) {
|
||||
fprintf(stderr, "error: send command failed\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (rcon_recv(rcon_socket, &pkt, RCON_TYPE_RESPONSE) && pkt.length > 0) {
|
||||
puts(pkt.body);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
char* combine_args(int argc, char* argv[]) {
|
||||
// combine all cli arguments
|
||||
char* command = malloc(MAX_BODY);
|
||||
memset(command, 0, MAX_BODY);
|
||||
strcat(command, argv[1]);
|
||||
|
||||
for (int idx = 2; idx < argc; idx++) {
|
||||
strcat(command, " ");
|
||||
strcat(command, argv[idx]);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
char* read_password(const char* conf_dir) {
|
||||
char* path = malloc(strlen(conf_dir) + 64);
|
||||
strcpy(path, conf_dir);
|
||||
strcat(path, "/rconpw");
|
||||
|
||||
FILE* fptr = fopen(path, "r");
|
||||
fseek(fptr, 0, SEEK_END);
|
||||
long fsize = ftell(fptr);
|
||||
fseek(fptr, 0, SEEK_SET); /* same as rewind(f); */
|
||||
|
||||
char *password = malloc(fsize + 1);
|
||||
fread(password, fsize, 1, fptr);
|
||||
fclose(fptr);
|
||||
|
||||
password[fsize] = 0;
|
||||
if (password[fsize-1] == '\n') {
|
||||
password[fsize-1] = 0;
|
||||
}
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
int rcon_open(const char *port) {
|
||||
struct sockaddr_in address = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(atoi(port))
|
||||
};
|
||||
inet_aton(RCON_HOST, &address.sin_addr);
|
||||
|
||||
int rcon_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (connect(rcon_socket, (struct sockaddr*) &address, sizeof(address)) < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return rcon_socket;
|
||||
}
|
||||
}
|
||||
|
||||
void rcon_create(packet* pkt, packet_type type, const char* body) {
|
||||
size_t body_length = strlen(body);
|
||||
if (body_length >= MAX_BODY - 2) {
|
||||
fprintf(stderr, "error: command to long");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
pkt->id = abs(rand());
|
||||
pkt->type = type;
|
||||
pkt->length = (uint32_t)(sizeof(pkt->id) + sizeof(pkt->type) + body_length + 2);
|
||||
|
||||
memset(pkt->body, 0, MAX_BODY);
|
||||
strncpy(pkt->body, body, MAX_BODY);
|
||||
}
|
||||
|
||||
bool rcon_recv(int rcon_socket, packet* pkt, packet_type expected_type) {
|
||||
memset(pkt, 0, sizeof(*pkt));
|
||||
|
||||
// Read response packet length
|
||||
ssize_t expected_length_bytes = sizeof(pkt->length);
|
||||
ssize_t rx_bytes = recv(rcon_socket, &(pkt->length), expected_length_bytes, 0);
|
||||
|
||||
if (rx_bytes == -1) {
|
||||
perror("error: socket error");
|
||||
return false;
|
||||
} else if (rx_bytes == 0) {
|
||||
fprintf(stderr, "error: no data recieved\n");
|
||||
return false;
|
||||
} else if (rx_bytes < expected_length_bytes || pkt->length < MIN_PACKET || pkt->length > MAX_PACKET) {
|
||||
fprintf(stderr, "error: invalid data\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t received = 0;
|
||||
while (received < pkt->length) {
|
||||
rx_bytes = recv(rcon_socket, (char *)pkt + sizeof(pkt->length) + received, pkt->length - received, 0);
|
||||
if (rx_bytes < 0) {
|
||||
perror("error: socket error");
|
||||
return false;
|
||||
} else if (rx_bytes == 0) {
|
||||
fprintf(stderr, "error: connection lost\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
received += rx_bytes;
|
||||
}
|
||||
|
||||
return pkt->type == expected_type;
|
||||
}
|
||||
|
||||
bool rcon_send(int rcon_socket, const packet* pkt) {
|
||||
size_t length = sizeof(pkt->length) + pkt->length;
|
||||
char *ptr = (char*) pkt;
|
||||
|
||||
while (length > 0) {
|
||||
ssize_t ret = send(rcon_socket, ptr, length, 0);
|
||||
|
||||
if (ret == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr += ret;
|
||||
length -= ret;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rcon_auth(int rcon_socket, const char* password) {
|
||||
packet pkt;
|
||||
rcon_create(&pkt, RCON_TYPE_AUTH, password);
|
||||
|
||||
if (!rcon_send(rcon_socket, &pkt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rcon_recv(rcon_socket, &pkt, RCON_TYPE_AUTH_RESPONSE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user