Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
#temp folder
/tmp/

# runtime data for Docker deployment
/docker/etc/
/docker/home/
/docker/root/

#access
/access/

Expand Down
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM almalinux:9

RUN dnf -y install epel-release \
&& dnf -y install \
openssh-server nginx supervisor python3 python3-pip cronie screen certbot \
&& dnf clean all

RUN mkdir -p /var/run/sshd /var/log/nginx /var/cld /root/sbin

COPY requirements.txt /tmp/requirements.txt
RUN pip3 install --no-cache-dir -r /tmp/requirements.txt && rm -f /tmp/requirements.txt

COPY supervisord.conf /etc/supervisord.conf
COPY docker/nginx/cld.conf /etc/nginx/conf.d/cld.conf
COPY docker/nginx/sslgen.conf /etc/nginx/sslgen.conf
RUN touch /etc/nginx/accesslist /etc/nginx/deny.conf

COPY docker/scripts/lets_auto_sign /root/sbin/lets_auto_sign
COPY docker/scripts/lets_renew /root/sbin/lets_renew
COPY docker/scripts/lets_clean_subdomains /root/sbin/lets_clean_subdomains
COPY docker/scripts/lets_auto_gen /etc/cron.d/lets_auto_gen
COPY docker/init_creds.sh /docker/init_creds.sh
RUN chmod 700 /root/sbin/lets_auto_sign /root/sbin/lets_renew /root/sbin/lets_clean_subdomains \
&& chmod 644 /etc/cron.d/lets_auto_gen

COPY docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 22 80 443
VOLUME ["/var/cld"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,4 +521,32 @@ bash -x <(wget -qO- "https://raw.githubusercontent.com/classicdevops/cld/master/
During the installation process, all init scripts of the system and modules will be executed, for each of them in interactive mode, you will need to specify the initialization data necessary for the operation of the system and modules
An example input will be provided for each type of data requested

Upon completion of the installation, a `password` for the `admin` user and a `link` to the `web interface` will be provided in console.
Upon completion of the installation, a `password` for the `admin` user and a `link` to the `web interface` will be provided in console.
### Running via Docker
1. Build the image and export default credentials using the helper script:
```bash
sudo ./docker/docker_init.sh /var/cld
```
This exports the container's `/etc`, `/home`, and `/root` into `/var/cld/docker`.
A default credentials file is created at `/var/cld/creds/creds_static` with a
symlink `/var/cld/creds/creds` pointing to it.
2. Start the container using docker compose:
```bash
docker-compose up -d
```
The compose file mounts `/var/cld` to `/var/cld`, `/var/cld/docker/etc` to `/etc`,
`/var/cld/docker/home` to `/home`, and `/var/cld/docker/root` to `/root`.
Ports `22`, `80` and `443` are exposed. The image runs sshd, nginx, cron and all
CLD services under Supervisor. Let’s Encrypt helper scripts placed under
`/root/sbin` handle automatic certificate generation for detected domains.

You can provide initial credentials through environment variables when running
the container. Any variable with the `CLD_CFG_` prefix will be written to
`/var/cld/creds/creds_env` and used instead of the default
`creds_static` file. For example:

```yaml
environment:
- CLD_CFG_CLD_DOMAIN=cld.example.com
- CLD_CFG_ADMIN_PASSWORD=secret
```
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
cld:
build: .
volumes:
- /var/cld:/var/cld
- /var/cld/docker/etc:/etc
- /var/cld/docker/home:/home
- /var/cld/docker/root:/root
environment:
# Example credentials passed via environment variables
- CLD_CFG_CLD_DOMAIN=cld.example.com
# CLD_CFG_* variables will be written to /var/cld/creds/creds_env
ports:
- "22:22"
- "80:80"
- "443:443"
14 changes: 14 additions & 0 deletions docker/docker_init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
# Build the CLD image and extract default credentials.
set -e
IMAGE_NAME=cld-image
DATA_DIR=${1:-/var/cld}

docker build -t $IMAGE_NAME .
mkdir -p "$DATA_DIR"
# Use the container to populate default credentials and home dirs
# into the target directory.
docker run --rm -v "$DATA_DIR":/var/cld $IMAGE_NAME bash -c "/docker/init_creds.sh /var/cld"

echo "Credentials prepared under $DATA_DIR"

33 changes: 33 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
set -e
mkdir -p /var/cld/creds

# If an existing creds file is present from a non-containerized setup, convert
# it into creds_static so future runs use symlinks.
if [ -f /var/cld/creds/creds ] && [ ! -L /var/cld/creds/creds ]; then
[ -f /var/cld/creds/creds_static ] || mv /var/cld/creds/creds /var/cld/creds/creds_static
fi

# If environment variables prefixed with CLD_CFG_ are provided,
# write them into creds_env and use it. Otherwise fall back to creds_static.
if env | grep -q '^CLD_CFG_' ; then
CREDS_ENV=/var/cld/creds/creds_env
: > "$CREDS_ENV"
for VAR in $(env | grep '^CLD_CFG_' | sort); do
KEY=${VAR%%=*}
VALUE=${VAR#*=}
KEY=${KEY#CLD_CFG_}
echo "${KEY}=${VALUE}" >> "$CREDS_ENV"
done
ln -sf creds_env /var/cld/creds/creds
else
# Ensure a static credentials file exists. If a plain creds file exists from
# a previous setup, move it so future runs use the symlink.
if [ -f /var/cld/creds/creds ] && [ ! -L /var/cld/creds/creds ]; then
[ -f /var/cld/creds/creds_static ] || mv /var/cld/creds/creds /var/cld/creds/creds_static
fi
[ -f /var/cld/creds/creds_static ] || touch /var/cld/creds/creds_static
ln -sf creds_static /var/cld/creds/creds
fi

exec /usr/bin/supervisord -c /etc/supervisord.conf
17 changes: 17 additions & 0 deletions docker/init_creds.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash
# Copy container credentials and home directories to the provided directory.
# Usage: ./docker/init_creds.sh [TARGET_DIR]
set -e
TARGET=${1:-/var/cld}

mkdir -p "$TARGET/docker/etc" "$TARGET/docker/home" "$TARGET/docker/root" "$TARGET/creds"

cp -a /etc/. "$TARGET/docker/etc/"
cp -a /home/. "$TARGET/docker/home/" 2>/dev/null || true
cp -a /root/. "$TARGET/docker/root/" 2>/dev/null || true

# prepare default credentials file
touch "$TARGET/creds/creds_static"
ln -sf creds_static "$TARGET/creds/creds"

echo "Credentials prepared under $TARGET/docker"
82 changes: 82 additions & 0 deletions docker/nginx/cld.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
map $http_x_forwarded_proto $real_scheme {
default $http_x_forwarded_proto;
'' $scheme;
}
upstream cldweb {
server 127.0.0.1:8080;
}
upstream cldapi {
server 127.0.0.1:8085;
}
limit_req_zone $binary_remote_addr zone=apiall:10m rate=1r/s;
server {
listen 80;
include sslgen.conf;
access_log /var/log/nginx-main-access.log;
server_name ${CLD_DOMAIN};

add_header X-Content-Type-Options nosniff;

location / {
if ($real_scheme = http) {
return 301 https://$host$request_uri;
}
include accesslist;
deny all;
proxy_pass http://cldweb;
proxy_redirect off;
proxy_buffering off;
proxy_request_buffering off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $real_scheme;
}

location /api {
if ($real_scheme = http) {
return 301 https://$host$request_uri;
}
include accesslist;
deny all;
rewrite ^/api(.*)$ $1 break;
proxy_pass http://cldapi;
proxy_redirect off;
proxy_buffering off;
proxy_request_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $real_scheme;
}

location /api/all {
include /etc/nginx/deny.conf;
if ($real_scheme = http) {
return 301 https://$host$request_uri;
}
add_header Access-Control-Allow-Origin *;
limit_req zone=apiall burst=5 nodelay;
rewrite ^/api(.*)$ $1 break;
proxy_pass http://cldapi;
proxy_redirect off;
proxy_buffering off;
proxy_request_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $real_scheme;
}

location /documentation {
if ($real_scheme = http) {
return 301 https://$host$request_uri;
}
limit_req zone=apiall burst=5 nodelay;
alias /var/www/cld/doc/;
}
}
9 changes: 9 additions & 0 deletions docker/nginx/sslgen.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
listen *:443 ssl http2;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_certificate /etc/letsencrypt/live/$ssl_server_name/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$ssl_server_name/privkey.pem;
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
alias /usr/share/nginx/html/.well-known/acme-challenge/;
}
error_log /var/log/nginx-ssl-error.log;
3 changes: 3 additions & 0 deletions docker/scripts/lets_auto_gen
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
MAILTO=""
* * * * * root /root/sbin/lets_auto_sign
0 14 * * 1 root /root/sbin/lets_renew
11 changes: 11 additions & 0 deletions docker/scripts/lets_auto_sign
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
if ps axfu | grep -v grep | grep -q "certonly"; then
echo There is already exist running letsencrypt instance
exit 1
else
for DOMAIN in $(grep 'cannot load certificate' /var/log/nginx-ssl-error.log | cut -d '"' -f 2 | cut -d / -f 5 | egrep "^[0-9a-z-]+\.([0-9a-z-]+\.)?[a-z]+$" | sort -u)
do
letsencrypt certonly -a webroot -n -m certbot@cldcloud.com --agree-tos --webroot-path=/usr/share/nginx/html -d ${DOMAIN} ; chmod 755 /etc/letsencrypt /etc/letsencrypt/{live,archive} ; chmod -R 755 /etc/letsencrypt/live/$DOMAIN ; chmod -R 755 /etc/letsencrypt/archive/$DOMAIN
done
truncate -s 0 /var/log/nginx-ssl-error.log
fi
6 changes: 6 additions & 0 deletions docker/scripts/lets_clean_subdomains
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
for DOMAIN in $(ls /etc/letsencrypt/live/ | egrep "[a-z0-9-]+\.[a-z0-9-]+\.[a-z0-9-]+")
do
echo "${DOMAIN}"
find /etc/letsencrypt/ -name "${DOMAIN}*" | egrep -o "/etc/letsencrypt/.*" | xargs -d "\n" -I {} rm -rf "{}"
done
27 changes: 27 additions & 0 deletions docker/scripts/lets_renew
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash
check_ssl()
{
expr \( `echo|timeout 5s openssl s_client -connect ${DOMAIN}:443 -servername ${DOMAIN} 2>/dev/null| timeout 5s openssl x509 -noout -enddate|cut -d'=' -f2|xargs -I ^ date +%s -d "^"` - `date +%s` \) / 24 / 3600
}

truncate -s 0 /tmp/ssl_domains
for DOMAIN in $(ls /etc/letsencrypt/live/ | grep -v README)
do
echo Check $DOMAIN
unset DAYS_LEFT
DAYS_LEFT=$(check_ssl 2>/dev/null)
[ "$DAYS_LEFT" ] || { echo Certificate not defined for $DOMAIN - skip ; continue ; }
echo ${DAYS_LEFT}_$DOMAIN >> /tmp/ssl_domains
done

cat /tmp/ssl_domains

for EXDOMAIN in $(cat /tmp/ssl_domains | uniq)
do
EXDAYS=$(echo $EXDOMAIN | cut -d _ -f 1)
DOMAIN=$(echo $EXDOMAIN | cut -d _ -f 2)
if [ "$EXDAYS" -lt "30" ]
then
letsencrypt certonly -a webroot -n -m certbot@cldcloud.com --agree-tos --webroot-path=/usr/share/nginx/html -d ${DOMAIN} ; chmod -R 755 /etc/letsencrypt
fi
done
18 changes: 18 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
cryptography==36.0.1
Flask==1.1.2
Flask-Session==0.3.2
Flask-SocketIO==5.0.1
itsdangerous==1.1.0
Jinja2==2.10.1
lxml==4.6.5
MarkupSafe==0.23
PySocks==1.7.1
pyTelegramBotAPI==4.14.0
python-dateutil==2.8.1
python-engineio==4.11.2
python-linux-procfs==0.7.3
python-pam==2.0.2
python-socketio==5.2.1
urllib3==1.26.5
Werkzeug==1.0.1
wsproto==1.2.0
60 changes: 60 additions & 0 deletions supervisord.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[supervisord]
nodaemon=false

[program:sshd]
command=/usr/sbin/sshd -D
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr

[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr

[program:cld_web]
command=python3 /var/cld/web/dashboard.py
directory=/var/cld/web
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
environment=PYTHONUNBUFFERED=1

[program:cld_api]
command=python3 /var/cld/api/api.py
directory=/var/cld/api
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
environment=PYTHONUNBUFFERED=1

[program:cld_auditor]
command=/var/cld/bin/cld-auditor
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
environment=PYTHONUNBUFFERED=1

[program:tgbot]
command=python3 /var/cld/bot/telegram/tgbot.py
directory=/var/cld/bot/telegram
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
environment=PYTHONUNBUFFERED=1

[program:dcbot]
command=python3 /var/cld/bot/discord/dcbot.py
directory=/var/cld/bot/discord
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
environment=PYTHONUNBUFFERED=1

[program:cron]
command=/usr/sbin/crond -n
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr