diff options
| -rw-r--r-- | ychat/DOCKER.md | 87 | ||||
| -rw-r--r-- | ychat/Dockerfile | 65 | ||||
| -rw-r--r-- | ychat/src/glob.h | 4 | ||||
| -rw-r--r-- | ychat/src/logd.cpp | 2 | ||||
| -rw-r--r-- | ychat/src/modl.cpp | 4 | ||||
| -rw-r--r-- | ychat/src/sock/sock.cpp | 6 |
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); |
