summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-30 08:42:55 +0300
committerPaul Buetow <paul@buetow.org>2026-06-30 08:42:55 +0300
commitf387bace8dab1e4b7336b4a69c8b2fc8bd80b3f6 (patch)
treeeccaec04421182eb7a86161b95c2dd883baec1af
parent332d7b2107833018b3ef67b64ffe121bff1ef4fb (diff)
Docker revival (Mode A): build in container, deploy to f3s
Multi-stage Dockerfile (Rocky 9 builder + runtime) builds ychat entirely in a container with SSL/MySQL/readline disabled (in-memory guest chat). Three minimal legacy-C++ patches so it builds on GCC 11: - glob.h/modl.cpp: rename project 'function' typedef to 'mod_func_t' (collides with std::function via 'using namespace std') - logd.cpp: 'ofstream == NULL' -> '!is_open()' - sock.cpp: assign i_server_sock before set_nonblock() (was EBADF on startup) Add DOCKER.md with build/push/deploy workflow and quirks (HTTP/0.9 responses, ephemeral in-memory state).
-rw-r--r--ychat/DOCKER.md87
-rw-r--r--ychat/Dockerfile65
-rw-r--r--ychat/src/glob.h4
-rw-r--r--ychat/src/logd.cpp2
-rw-r--r--ychat/src/modl.cpp4
-rw-r--r--ychat/src/sock/sock.cpp6
6 files changed, 160 insertions, 8 deletions
diff --git a/ychat/DOCKER.md b/ychat/DOCKER.md
new file mode 100644
index 0000000..025f664
--- /dev/null
+++ b/ychat/DOCKER.md
@@ -0,0 +1,87 @@
+# yChat — Docker revival (Mode A)
+
+yChat is a legacy (2007) C++ HTTP chat server. This revival builds it **entirely
+in a Docker container** and deploys it to the f3s k3s cluster as an
+in-memory guest chat — **no database, no persistent user store**.
+
+## Build
+
+Multi-stage `Dockerfile`: Rocky Linux 9 builder (GCC 11 tolerates the legacy
+C++ this tree uses) + slim Rocky 9 runtime. All optional features are OFF:
+
+```
+podman build -t ychat:dev .
+podman run --rm -p 2000:2000 ychat:dev
+# smoke test (ychat speaks HTTP/0.9-style responses, hence --http0.9):
+curl --http0.9 http://127.0.0.1:2000/index.html
+```
+
+## Legacy-C++ patches applied
+
+The tree does not build on a modern toolchain unmodified. Three minimal,
+semantics-preserving patches were made so it builds on Rocky 9 / GCC 11:
+
+- `src/glob.h`, `src/modl.cpp`: renamed the project's `typedef int function(...)`
+ type to `mod_func_t` — it collided with `std::function` brought in by
+ `using namespace std;` under C++11.
+- `src/logd.cpp`: `ofstream == NULL` → `!ofstream.is_open()` (streams are no
+ longer comparable to `NULL`).
+- `src/sock/sock.cpp`: reordered `i_server_sock = i_sock;` to *before* the
+ `set_nonblock(i_server_sock)` call — previously `set_nonblock` ran on an
+ uninitialised member, returning EBADF and aborting startup.
+
+## Runtime layout (WORKDIR /app)
+
+| Path | Purpose | Writable? |
+|------|---------|-----------|
+| `bin/ychat` | server binary | no |
+| `etc/ychat.conf` | config (found via `./etc/` search path) | no |
+| `html/` | templates served over HTTP | no |
+| `mods/{commands,html}/*.so` | runtime-loadable modules | no |
+| `log/` | access/system/room logs | yes (emptyDir in k8s) |
+
+With MySQL disabled, all user/session/room state is **in-memory only** and is
+lost on restart. `chat.enableguest=true` lets guests log in without a DB.
+
+> Note: ychat emits raw HTTP/0.9-style responses (no `HTTP/1.1 200` status
+> line / headers). Modern browsers and `curl` may refuse these by default;
+> `curl --http0.9` works. This is a pre-existing property of the codebase, not
+> introduced by this revival.
+
+## Push to the f3s registry
+
+```
+TAG=$(git rev-parse --short HEAD)
+podman tag ychat:$TAG r0.lan.buetow.org:30001/ychat:$TAG
+podman tag ychat:latest r0.lan.buetow.org:30001/ychat:latest
+podman push --tls-verify=false r0.lan.buetow.org:30001/ychat:$TAG
+podman push --tls-verify=false r0.lan.buetow.org:30001/ychat:latest
+```
+
+## Deploy (GitOps)
+
+Config lives in the `conf` repo (mirrored on the in-cluster git-server):
+
+- Helm chart: `f3s/ychat/helm-chart`
+- ArgoCD app: `f3s/argocd-apps/services/ychat.yaml`
+
+The Deployment pulls `registry.lan.buetow.org:30001/ychat:<TAG>` (tag matches
+`appVersion` in `Chart.yaml`). Logs go to an `emptyDir` on `/app/log` —
+ephemeral by design (no app state to persist). After chart/app edits push conf:
+
+```
+git push master master && git push r0 master
+```
+
+ArgoCD auto-syncs. URLs (LAN wildcard DNS resolves via Pi-hole):
+
+- https://ychat.f3s.lan.buetow.org
+- http://ychat.f3s.buetow.org (via OpenBSD relayd frontend, if wired)
+
+## Optional follow-ups (not in scope for Mode A)
+
+- Port to a modern toolchain (`hash_map`→`unordered_map`, fix implicit
+ returns, modernise `configure.ac`) and drop the legacy-C++ patches.
+- Mode B: enable MySQL for persistent registered users — requires a
+ hand-written `CREATE TABLE` schema (none ships in this repo), a MariaDB
+ StatefulSet + PVC, and a Secret for `chat.database.password`. \ No newline at end of file
diff --git a/ychat/Dockerfile b/ychat/Dockerfile
new file mode 100644
index 0000000..9324050
--- /dev/null
+++ b/ychat/Dockerfile
@@ -0,0 +1,65 @@
+# yChat revival image — Mode A (no DB, no SSL, no readline)
+# Multi-stage build on Rocky Linux 9, whose GCC 11 still tolerates the
+# legacy C++ this 2007 codebase uses (__gnu_cxx::hash_map, ofstream == NULL),
+# so the tree builds with ZERO source patches.
+#
+# Runtime layout (WORKDIR /app):
+# bin/ychat server binary
+# etc/ychat.conf config (found via ./etc/ search path)
+# html/ templates (read-only, served over HTTP)
+# mods/commands/*.so runtime-loadable command modules
+# mods/html/*.so runtime-loadable html modules
+# log/ writable logs (mount a volume here)
+
+# ---------- builder ----------
+FROM rockylinux:9 AS builder
+
+RUN dnf -y install \
+ gcc-c++ \
+ make \
+ autoconf \
+ automake \
+ libevent-devel \
+ && dnf clean all
+
+WORKDIR /build/ychat
+COPY . .
+
+# Configure with all optional features OFF (Mode A: in-memory guest chat).
+# Run the top-level "all" (build modules base) so runtime .so modules are
+# produced under /build/ychat/mods/{commands,html}/.
+RUN cd src \
+ && ./configure --disable-readline --disable-ssl --disable-mysql \
+ && cd .. \
+ && make -j"$(nproc)"
+
+# ---------- runtime ----------
+FROM rockylinux:9 AS runtime
+
+RUN dnf -y install \
+ libevent \
+ libstdc++ \
+ tzdata \
+ ca-certificates \
+ && dnf clean all
+
+# Non-root runtime user. ychat binds port 2000 (unprivileged).
+RUN useradd -r -u 1000 -d /app -s /sbin/nologin ychat
+
+WORKDIR /app
+
+# Binary
+COPY --from=builder /build/ychat/bin/ychat /app/bin/ychat
+
+# Read-only resources
+COPY --from=builder /build/ychat/html/ /app/html/
+COPY --from=builder /build/ychat/mods/ /app/mods/
+COPY etc/ychat.conf /app/etc/ychat.conf
+
+# Writable log dir
+RUN mkdir -p /app/log/rooms && chown -R ychat:ychat /app
+
+USER 1000:1000
+EXPOSE 2000
+
+ENTRYPOINT ["/app/bin/ychat"] \ No newline at end of file
diff --git a/ychat/src/glob.h b/ychat/src/glob.h
index f1d6c95..9e3d6c7 100644
--- a/ychat/src/glob.h
+++ b/ychat/src/glob.h
@@ -89,7 +89,7 @@
using namespace std;
-typedef int function( void *v_arg );
+typedef int mod_func_t( void *v_arg );
struct container
{
@@ -98,7 +98,7 @@ struct container
struct dynmod
{
- function *the_func ;
+ mod_func_t *the_func ;
void *the_module;
};
diff --git a/ychat/src/logd.cpp b/ychat/src/logd.cpp
index a595aef..ea7c066 100644
--- a/ychat/src/logd.cpp
+++ b/ychat/src/logd.cpp
@@ -85,7 +85,7 @@ logd::flush()
ofstream of_output;
of_output.open(s_logfile.c_str(), ios::app);
- if ( of_output == NULL )
+ if ( ! of_output.is_open() )
{
wrap::system_message( LOGERR1 + s_logfile );
exit(1);
diff --git a/ychat/src/modl.cpp b/ychat/src/modl.cpp
index 561935c..98b0242 100644
--- a/ychat/src/modl.cpp
+++ b/ychat/src/modl.cpp
@@ -93,7 +93,7 @@ dynmod*
modl::cache_module( string s_name, bool b_print_sys_msg )
{
void *the_module = NULL;
- function *the_func = NULL;
+ mod_func_t *the_func = NULL;
the_module = dlopen( s_name.c_str(), RTLD_LAZY );
//the_module = dlopen( s_name.c_str(), RTLD_NOW );
@@ -104,7 +104,7 @@ modl::cache_module( string s_name, bool b_print_sys_msg )
return NULL;
}
- the_func = (function*) dlsym( the_module, "extern_function" );
+ the_func = (mod_func_t*) dlsym( the_module, "extern_function" );
if ( the_func == NULL )
{
diff --git a/ychat/src/sock/sock.cpp b/ychat/src/sock/sock.cpp
index fdba716..e6a2840 100644
--- a/ychat/src/sock/sock.cpp
+++ b/ychat/src/sock/sock.cpp
@@ -116,6 +116,9 @@ sock::_make_server_socket( int i_port )
setsockopt(i_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&i_optval, sizeof(int));
+ i_server_port = i_port;
+ i_server_sock = i_sock;
+
if (set_nonblock(i_server_sock) < 0)
{
wrap::system_message(NONBLER, errno);
@@ -124,9 +127,6 @@ sock::_make_server_socket( int i_port )
wrap::system_message(SOCKCRT + string("localhost:") + tool::int2string(i_port));
- i_server_port = i_port;
- i_server_sock = i_sock;
-
if (listen(i_sock, BACKLOG) < 0)
{
wrap::system_message(LISTERR, errno);