Revived this old project to run Citadel in a Docker container. It uses 'ctdlvisor...
authorArt Cancro <ajc@citadel.org>
Thu, 5 Aug 2021 05:09:37 +0000 (01:09 -0400)
committerArt Cancro <ajc@citadel.org>
Thu, 5 Aug 2021 05:09:37 +0000 (01:09 -0400)
Dockerfile
README.txt
citadel-docker-startup.sh [deleted file]
ctdlvisor.c [new file with mode: 0644]
run-citadel.sh
supervisor.conf [deleted file]

index 38711f29ad96a0dfc17e8e79f7c9641af124a295..96547e0b086c3be3a05ba8f92b4190eab3e2df1f 100644 (file)
@@ -3,10 +3,7 @@ FROM bitnami/minideb:latest
 VOLUME /citadel-data
 
 # Install prerequisites
 VOLUME /citadel-data
 
 # Install prerequisites
-RUN install_packages make build-essential zlib1g-dev libldap2-dev libssl-dev gettext libical-dev libexpat1-dev curl libcurl4-openssl-dev git autoconf automake netbase supervisor
-
-# Download and build libsieve
-RUN sh -c '( curl http://easyinstall.citadel.org/libsieve-2.2.7-ctdl2.tar.gz | tar xvzf - ) && cd libsieve-2.2.7/src && ./configure --prefix=/usr && make && make install'
+RUN install_packages make build-essential zlib1g-dev libldap2-dev libssl-dev gettext libical-dev libexpat1-dev curl libcurl4-openssl-dev git autoconf automake netbase libreadline-dev
 
 # Download and build Citadel
 WORKDIR /tmp/ctdl_build
 
 # Download and build Citadel
 WORKDIR /tmp/ctdl_build
@@ -18,13 +15,12 @@ RUN sh -c 'cd /tmp/ctdl_build/citadel/textclient && ./bootstrap && ./configure -
 RUN sh -c 'cd /tmp && rm -vfr /tmp/ctdl_build'
 RUN sh -c 'rm -vrf /usr/local/citadel/data /usr/local/citadel/files /usr/local/citadel/keys /usr/local/webcit/keys'
 
 RUN sh -c 'cd /tmp && rm -vfr /tmp/ctdl_build'
 RUN sh -c 'rm -vrf /usr/local/citadel/data /usr/local/citadel/files /usr/local/citadel/keys /usr/local/webcit/keys'
 
-# Supervisor
-ADD supervisor.conf /etc/
-ADD citadel-docker-startup.sh /usr/local/bin/
-RUN sh -c 'chmod 755 /usr/local/bin/citadel-docker-startup.sh'
+# ctdlvisor is a supervisor process that handles all of the components
+ADD ctdlvisor.c /tmp/ctdl_build
+RUN sh -c 'cd /tmp/ctdl_build && cc ctdlvisor.c -o /usr/local/bin/ctdlvisor'
 
 # Ports
 EXPOSE 25 80 110 119 143 443 465 504 563 587 993 995 2020 5222
 
 # Let's go!
 
 # Ports
 EXPOSE 25 80 110 119 143 443 465 504 563 587 993 995 2020 5222
 
 # Let's go!
-CMD /usr/local/bin/citadel-docker-startup.sh
+ENTRYPOINT /usr/local/bin/ctdlvisor
index dab15a58b337a26b4d37e704acd66f5067c86897..19f131a31d90f1212e1092182f86751c86dd4091 100644 (file)
@@ -2,7 +2,7 @@
 THIS IS CURRENTLY AN EXPERIMENTAL BUILD.
 IF YOU USE IT IN PRODUCTION, DO NOT EXPECT SUPPORT.
 
 THIS IS CURRENTLY AN EXPERIMENTAL BUILD.
 IF YOU USE IT IN PRODUCTION, DO NOT EXPECT SUPPORT.
 
-Copyright (c) 2019 by Art Cancro
+Copyright (c) 2019-2021 by Art Cancro
 This program is distributed under the terms of the GNU General Public
 License , version 3.  We furthermore assert that it's called "open
 source", not "free software", and that it's called "Linux", not
 This program is distributed under the terms of the GNU General Public
 License , version 3.  We furthermore assert that it's called "open
 source", not "free software", and that it's called "Linux", not
diff --git a/citadel-docker-startup.sh b/citadel-docker-startup.sh
deleted file mode 100755 (executable)
index b9558e6..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-# This is the script that is run when the Citadel Docker Container starts up.
-
-# Create directories within the volume, if they do not already exist.
-mkdir -p /citadel-data         2>/dev/null
-mkdir -p /citadel-data/data    2>/dev/null
-mkdir -p /citadel-data/files   2>/dev/null
-mkdir -p /citadel-data/keys    2>/dev/null
-
-# Create symlinks into the volume subdirectories, from where our programs expect them to be.
-ln -sfv /citadel-data/data /usr/local/citadel/
-ln -sfv /citadel-data/files /usr/local/citadel/
-ln -sfv /citadel-data/keys /usr/local/citadel/
-ln -sfv /citadel-data/keys /usr/local/webcit/
-
-# supervisord will take it from here.
-exec /usr/bin/supervisord -c /etc/supervisor.conf
diff --git a/ctdlvisor.c b/ctdlvisor.c
new file mode 100644 (file)
index 0000000..086f1a6
--- /dev/null
@@ -0,0 +1,288 @@
+// This is a supervisor program that handles start/stop/restart of
+// the various Citadel System components, when we are running in
+// a Docker container.
+//
+// Copyright (c) 2021 by the citadel.org team
+//
+// This program is open source software.  Use, duplication, or disclosure
+// is subject to the terms of the GNU General Public License, version 3.
+// The program is distributed without any warranty, expressed or implied.
+
+#define CTDL_DIR       "/citadel-data"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+pid_t citserver_pid;
+pid_t webcit_pid;
+pid_t webcits_pid;
+int shutting_down = 0;
+
+// Call this instead of exit() just for common diagnostics etc.
+void ctdlvisor_exit(int code) {
+       printf("ctdlvisor: exit code %d\n", code);
+       exit(code);
+}
+
+
+// Interrupting this program with a signal will begin an orderly shutdown.
+void signal_handler(int signal) {
+       fprintf(stderr, "ctdlvisor: caught signal %d\n", signal);
+
+       while(shutting_down) {
+               fprintf(stderr, "ctdlvisor: already shutting down\n");
+               sleep(1);
+       }
+
+       int status;
+       pid_t who_exited;
+       char *what_exited = NULL;
+
+       shutting_down = 1;
+       kill(citserver_pid, SIGTERM);
+       kill(webcit_pid, SIGTERM);
+       kill(webcits_pid, SIGTERM);
+       do {
+               fprintf(stderr, "ctdlvisor: waiting for all child process to exit...\n");
+               who_exited = waitpid(-1, &status, 0);
+               if (who_exited == citserver_pid) {
+                       what_exited = "Citadel Server";
+               }
+               else if (who_exited == webcit_pid) {
+                       what_exited = "WebCit HTTP";
+               }
+               else if (who_exited == webcits_pid) {
+                       what_exited = "WebCit HTTPS";
+               }
+               else {
+                       what_exited = "unknown";
+               }
+               if (who_exited >= 0) {
+                       fprintf(stderr, "ctdlvisor: %d (%s) ended, status=%d\n", who_exited, what_exited, status);
+               }
+       } while (who_exited >= 0);
+       ctdlvisor_exit(0);
+}
+
+
+void detach_from_tty(void) {
+       signal(SIGHUP, SIG_IGN);
+       signal(SIGINT, SIG_IGN);
+       signal(SIGQUIT, SIG_IGN);
+
+       setsid();       // become our own process group leader
+       umask(0);
+       if (    (freopen("/dev/null", "r", stdin) != stdin) ||
+               (freopen("/dev/null", "w", stdout) != stdout) ||
+               (freopen("/dev/null", "w", stderr) != stderr)
+       ) {
+               fprintf(stderr, "sysdep: unable to reopen stdio: %s\n", strerror(errno));
+       }
+}
+
+
+pid_t start_citadel() {
+       pid_t pid = fork();
+       if (pid == 0) {
+               fprintf(stderr, "ctdlvisor: executing citserver\n");
+               //detach_from_tty();
+               execlp("/usr/local/citadel/citserver", "citserver", "-x9", "-h", CTDL_DIR, NULL);
+               exit(errno);
+       }
+       else {
+               fprintf(stderr, "ctdlvisor: citserver running on pid=%d\n", pid);
+               return(pid);
+       }
+}
+
+
+pid_t start_webcit() {
+       pid_t pid = fork();
+       if (pid == 0) {
+               fprintf(stderr, "ctdlvisor: executing webcit (http)\n");
+               //detach_from_tty();
+               execlp("/usr/local/webcit/webcit", "webcit", "-x9", "-p", "80", "uds", CTDL_DIR, NULL);
+               exit(errno);
+       }
+       else {
+               fprintf(stderr, "ctdlvisor: webcit (HTTP) running on pid=%d\n", pid);
+               return(pid);
+       }
+}
+
+
+pid_t start_webcits() {
+       pid_t pid = fork();
+       if (pid == 0) {
+               fprintf(stderr, "ctdlvisor: executing webcit (https)\n");
+               //detach_from_tty();
+               execlp("/usr/local/webcit/webcit", "webcit", "-x9", "-s", "-p", "443", "uds", CTDL_DIR, NULL);
+               exit(errno);
+       }
+       else {
+               fprintf(stderr, "ctdlvisor: webcit (HTTPS) running on pid=%d\n", pid);
+               return(pid);
+       }
+}
+
+
+void main_loop(void) {
+       int status;
+       pid_t who_exited;
+       int citserver_exit_code = 0;
+
+       do {
+               who_exited = waitpid(-1, &status, 0);
+               fprintf(stderr, "ctdlvisor: pid=%d exited, status=%d, exitcode=%d\n", who_exited, status, WEXITSTATUS(status));
+
+               // A *deliberate* exit of citserver will cause ctdlvisor to shut the whole AppImage down.
+               // If it crashes, however, we will start it back up.
+               if (who_exited == citserver_pid) {
+                       citserver_exit_code = WEXITSTATUS(status);
+                       if ((WIFEXITED(status)) && (citserver_exit_code == 0)) {
+                               fprintf(stderr, "ctdlvisor: citserver exited normally - ending AppImage session\n");
+                               shutting_down = 1;
+                               kill(webcit_pid, SIGTERM);
+                               kill(webcits_pid, SIGTERM);
+                       }
+                       else if ((WIFEXITED(status)) && (citserver_exit_code >= 101) && (citserver_exit_code <= 109)) {
+                               fprintf(stderr, "ctdlvisor: citserver exited intentionally - ending AppImage session\n");
+                               shutting_down = 1;
+                               kill(webcit_pid, SIGTERM);
+                               kill(webcits_pid, SIGTERM);
+                       }
+                       else {
+                               if (WIFSIGNALED(status)) {
+                                       fprintf(stderr, "ctdlvisor: citserver crashed on signal %d\n", WTERMSIG(status));
+                               }
+                               citserver_pid = start_citadel();
+                       }
+               }
+
+               // WebCit processes are restarted if they exit for any reason.
+               if ((who_exited == webcit_pid) && (!shutting_down))     webcit_pid = start_webcit();
+               if ((who_exited == webcits_pid) && (!shutting_down))    webcits_pid = start_webcits();
+
+               // If we somehow end up in an endless loop, at least slow it down.
+               sleep(1);
+
+       } while (who_exited >= 0);
+       ctdlvisor_exit(citserver_exit_code);
+}
+
+
+void install_client_link(void) {                       // FIXME this is all furkokt and needs to be rethought now that it's docker and not appimage
+
+       FILE *fp;
+       char path_to_link[PATH_MAX];
+       snprintf(path_to_link, sizeof path_to_link, CTDL_DIR "citadel");
+       fp = fopen(path_to_link, "w");
+       if (!fp) {
+               fprintf(stderr, "%s\n", strerror(errno));
+               return;
+       }
+
+       fprintf(fp,     "#!/bin/bash\n"
+                       "export APPDIR=%s\n"
+                       "export LD_LIBRARY_PATH=${APPDIR}/usr/bin:$LD_LIBRARY_PATH\n"
+                       "export PATH=${APPDIR}/usr/bin:$PATH\n"
+                       "exec citadel\n"
+       ,
+                       getenv("APPDIR")
+       );
+
+       fchmod(fileno(fp), 0755);
+       fclose(fp);
+}
+
+
+int main(int argc, char **argv) {
+       int a;
+       int migrate_mode = 0;
+
+       fprintf(stderr, "ctdlvisor: Welcome to the Citadel System running in a container.\n");
+       fprintf(stderr, "ctdlvisor: command line arguments: ");
+       for (a=0; a<argc; ++a) {
+               fprintf(stderr, "%s ", argv[a]);
+       }
+       fprintf(stderr, "\n");
+
+       char *dirs[] = {
+               CTDL_DIR,
+               CTDL_DIR "/data",
+               CTDL_DIR "/files",
+               CTDL_DIR "/keys"
+       };
+
+       for (a=0; a<4; ++a) {
+               mkdir(dirs[a], 0777);
+               if (access(dirs[a], R_OK|W_OK|X_OK)) {
+                       fprintf(stderr, "ctdlvisor: %s: %s\n", dirs[a], strerror(errno));
+                       ctdlvisor_exit(errno);
+               }
+               else {
+                       fprintf(stderr, "ctdlvisor: %s is writable\n", dirs[a]);
+               }
+       }
+
+       symlink(CTDL_DIR "/keys", "/usr/local/webcit/keys");
+
+       /* parse command-line arguments */
+       while ((a=getopt(argc, argv, "cm")) != EOF) switch(a) {
+
+               // test this binary for compatibility and exit
+               case 'c':
+                       fprintf(stderr, "%s: binary compatibility confirmed\n", argv[0]);
+                       exit(0);
+                       break;
+
+               // run ctdlmigrate only
+               case 'm':
+                       migrate_mode = 1;
+                       break;
+
+               // any other parameter makes it crash and burn
+               default:
+                       fprintf(stderr, "usage\n");
+                       exit(1);
+       }
+
+
+       signal(SIGHUP, signal_handler);
+
+       // "migrate mode" means we just start the server and then run ctdlmigrate interactively.
+       if (migrate_mode) {
+               citserver_pid = start_citadel();
+               fprintf(stderr, "ctdlvisor: waiting a moment for citserver to initialize...\n");
+               sleep(5);
+               char bin[1024];
+               sprintf(bin, "/usr/local/citadel/ctdlmigrate -h %s", CTDL_DIR);
+               system(bin);
+               kill(citserver_pid, SIGTERM);
+       }
+
+       // Otherwise, it's just a normal happy day in Citadel land.
+       else {
+               signal(SIGTERM, signal_handler);
+               signal(SIGINT, signal_handler);
+               signal(SIGQUIT, signal_handler);
+       
+               citserver_pid = start_citadel();                // start Citadel Server
+               webcit_pid = start_webcit();                    // start WebCit HTTP
+               webcits_pid = start_webcits();                  // start WebCit HTTPS
+
+               install_client_link();
+       
+               main_loop();
+       }
+
+       ctdlvisor_exit(0);
+}
index f021bf5e52e8f7099a06dd41ab79cf10f9a1f8a7..486a2e4a277fc22df95ea47b35c065f45b57cf8f 100755 (executable)
@@ -1,7 +1,26 @@
 #!/bin/bash
 
 #!/bin/bash
 
+# This is a simple startup script to run Citadel in a Docker container.
+# If you know your way around Docker, you don't have to use it; feel free to do whatever you want.
+# The container expects a persistent volume called "citadel-data" in which it will keep everything.
+# The remainder of the container is ephermal and can be deleted at any time.
+
 docker run \
        --name citadel \
 docker run \
        --name citadel \
-       -v citadel-data:/citadel-data \
-       -d \
-       $1
+       -it \
+       --rm \
+       -p 25:25 \
+       -p 80:80 \
+       -p 110:110 \
+       -p 119:119 \
+       -p 143:143 \
+       -p 443:443 \
+       -p 465:465 \
+       -p 504:504 \
+       -p 563:563 \
+       -p 587:587 \
+       -p 993:993 \
+       -p 995:995 \
+       -p 5222:5222 \
+       --mount type=bind,source=/root/citadel/citadel,target=/citadel-data \
+       $*
diff --git a/supervisor.conf b/supervisor.conf
deleted file mode 100644 (file)
index 9038b22..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# 
-# supervisord configuration file for Citadel in a container
-# Copyright (c) 2019 by Art Cancro
-# Open Source for Linux/Linux licensed as GNU GPL v3
-#
-
-[supervisord]
-nodaemon=true
-
-[program:citserver]
-command=/usr/local/citadel/citserver
-
-[program:webcit]
-command=/usr/local/webcit/webcit -p80 uds /usr/local/citadel
-
-[program:webcits]
-command=/usr/local/webcit/webcit -s -p443 uds /usr/local/citadel
-