diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index e1f4c87f1..efd74552f 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -14,22 +14,22 @@ jobs: matrix: include: - app: smp-server - app_port: 5223 + app_port: "443 5223" - app: xftp-server - app_port: 443 + app_port: 443 steps: - name: Clone project - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Log in to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Extract metadata for Docker image id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }} flavor: | @@ -40,7 +40,7 @@ jobs: type=semver,pattern=v{{major}} - name: Build and push Docker image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: push: true build-args: | diff --git a/Dockerfile b/Dockerfile index e81db17c3..06b59e6e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,20 @@ -ARG TAG=22.04 +# syntax=docker/dockerfile:1.7.0-labs +ARG TAG=24.04 FROM ubuntu:${TAG} AS build ### Build stage # Install curl and git and simplexmq dependencies -RUN apt-get update && apt-get install -y curl git build-essential libgmp3-dev zlib1g-dev llvm-12 llvm-12-dev libnuma-dev libssl-dev +RUN apt-get update && apt-get install -y curl git build-essential libgmp3-dev zlib1g-dev llvm-18 llvm-18-dev libnuma-dev libssl-dev # Specify bootstrap Haskell versions ENV BOOTSTRAP_HASKELL_GHC_VERSION=9.6.3 -ENV BOOTSTRAP_HASKELL_CABAL_VERSION=3.10.1.0 +ENV BOOTSTRAP_HASKELL_CABAL_VERSION=3.12.1.0 + +# Do not install Stack +ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK=true +ENV BOOTSTRAP_HASKELL_INSTALL_NO_STACK_HOOK=true # Install ghcup RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 sh @@ -21,26 +26,42 @@ ENV PATH="/root/.cabal/bin:/root/.ghcup/bin:$PATH" RUN ghcup set ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}" && \ ghcup set cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}" -COPY . /project +# Copy only the source code +COPY apps /project/apps/ +COPY cbits /project/cbits/ +COPY src /project/src/ + +COPY cabal.project Setup.hs simplexmq.cabal LICENSE /project + WORKDIR /project +# Debug +#ARG CACHEBUST=1 + +#ADD --chmod=755 https://github.com/MShekow/directory-checksum/releases/download/v1.4.6/directory-checksum_1.4.6_linux_amd64 /usr/local/bin/directory-checksum +#RUN directory-checksum --max-depth 2 . + +# Set build arguments and check if they exist ARG APP -ARG APP_PORT -RUN if [ -z "$APP" ] || [ -z "$APP_PORT" ]; then printf "Please spcify \$APP and \$APP_PORT build-arg.\n"; exit 1; fi +RUN if [ -z "$APP" ]; then printf "Please spcify \$APP build-arg.\n"; exit 1; fi # Compile app RUN cabal update RUN cabal build exe:$APP +# Copy scripts +COPY scripts /project/scripts/ + # Create new path containing all files needed RUN mkdir /final WORKDIR /final # Strip the binary from debug symbols to reduce size -RUN bin=$(find /project/dist-newstyle -name "$APP" -type f -executable) && \ +RUN bin="$(find /project/dist-newstyle -name "$APP" -type f -executable)" && \ mv "$bin" ./ && \ strip ./"$APP" &&\ - mv /project/scripts/docker/entrypoint-"$APP" ./entrypoint + mv /project/scripts/docker/entrypoint-"$APP" ./entrypoint &&\ + mv /project/scripts/main/simplex-servers-stopscript ./simplex-servers-stopscript ### Final stage FROM ubuntu:${TAG} @@ -53,6 +74,8 @@ COPY --from=build /final /usr/local/bin/ # Open app listening port ARG APP_PORT +RUN if [ -z "$APP_PORT" ]; then printf "Please spcify \$APP_PORT build-arg.\n"; exit 1; fi + EXPOSE $APP_PORT # simplexmq requires using SIGINT to correctly preserve undelivered messages and restore them on restart diff --git a/scripts/docker/docker-compose-smp-complete.yml b/scripts/docker/docker-compose-smp-complete.yml new file mode 100644 index 000000000..be871983a --- /dev/null +++ b/scripts/docker/docker-compose-smp-complete.yml @@ -0,0 +1,67 @@ +name: SimpleX Chat - smp-server + +services: + oneshot: + image: ubuntu:latest + environment: + CADDYCONF: | + ${CADDY_OPTS:-} + + http://{$$ADDR} { + redir https://{$$ADDR}{uri} permanent + } + + {$$ADDR}:8443 { + tls { + key_type rsa4096 + } + } + command: sh -c 'if [ ! -f /etc/caddy/Caddyfile ]; then printf "$${CADDYCONF}" > /etc/caddy/Caddyfile; fi' + volumes: + - ./caddy_conf:/etc/caddy + + caddy: + image: caddy:latest + depends_on: + oneshot: + condition: service_completed_successfully + cap_add: + - NET_ADMIN + environment: + ADDR: ${ADDR?"Please specify the domain."} + volumes: + - ./caddy_conf:/etc/caddy + - caddy_data:/data + - caddy_config:/config + ports: + - 80:80 + restart: unless-stopped + healthcheck: + test: "test -d /data/caddy/certificates/${CERT_PATH:-acme-v02.api.letsencrypt.org-directory}/${ADDR} || exit 1" + interval: 1s + retries: 60 + + smp-server: + image: ${SIMPLEX_IMAGE:-simplexchat/smp-server:latest} + depends_on: + caddy: + condition: service_healthy + environment: + ADDR: ${ADDR?"Please specify the domain."} + PASS: ${PASS:-} + volumes: + - ./smp_configs:/etc/opt/simplex + - ./smp_state:/var/opt/simplex + - type: volume + source: caddy_data + target: /certificates + volume: + subpath: "caddy/certificates/${CERT_PATH:-acme-v02.api.letsencrypt.org-directory}/${ADDR}" + ports: + - 443:443 + - 5223:5223 + restart: unless-stopped + +volumes: + caddy_data: + caddy_config: diff --git a/scripts/docker/docker-compose-smp-manual.yml b/scripts/docker/docker-compose-smp-manual.yml new file mode 100644 index 000000000..391219b15 --- /dev/null +++ b/scripts/docker/docker-compose-smp-manual.yml @@ -0,0 +1,15 @@ +name: SimpleX Chat - smp-server + +services: + smp-server: + image: ${SIMPLEX_IMAGE:-simplexchat/smp-server:latest} + environment: + WEB_MANUAL: ${WEB_MANUAL:-1} + ADDR: ${ADDR?"Please specify the domain."} + PASS: ${PASS:-} + volumes: + - ./smp_configs:/etc/opt/simplex + - ./smp_state:/var/opt/simplex + ports: + - 5223:5223 + restart: unless-stopped diff --git a/scripts/docker/docker-compose-smp.env b/scripts/docker/docker-compose-smp.env new file mode 100644 index 000000000..17c51cf9a --- /dev/null +++ b/scripts/docker/docker-compose-smp.env @@ -0,0 +1,11 @@ +# Mandatory +ADDR=your_ip_or_addr + +# Optional +#PASS='123123' +#WEB_MANUAL=1 + +# Debug +#SIMPLEX_SMP_IMAGE=smp-server-dev +#CERT_PATH=acme-staging-v02.api.letsencrypt.org-directory +#CADDY_OPTS='{\n acme_ca https://acme-staging-v02.api.letsencrypt.org/directory\n}' diff --git a/scripts/docker/docker-compose-xftp.env b/scripts/docker/docker-compose-xftp.env new file mode 100644 index 000000000..425b769a3 --- /dev/null +++ b/scripts/docker/docker-compose-xftp.env @@ -0,0 +1,9 @@ +# Mandatory +ADDR=your_ip_or_addr +QUOTA=120gb + +# Optional +#PASS='123123' + +# Debug +#SIMPLEX_XFTP_IMAGE=xftp-server-dev diff --git a/scripts/docker/docker-compose-xftp.yml b/scripts/docker/docker-compose-xftp.yml new file mode 100644 index 000000000..0686412f3 --- /dev/null +++ b/scripts/docker/docker-compose-xftp.yml @@ -0,0 +1,16 @@ +name: SimpleX Chat - xftp-server + +services: + xftp-server: + image: ${SIMPLEX_XFTP_IMAGE:-simplexchat/xftp-server:latest} + environment: + ADDR: ${ADDR?"Please specify the domain."} + QUOTA: ${QUOTA?"Please specify disk quota."} + PASS: ${PASS:-} + volumes: + - ./xftp_configs:/etc/opt/simplex-xftp + - ./xftp_state:/var/opt/simplex-xftp + - ./xftp_files:/srv/xftp + ports: + - 443:443 + restart: unless-stopped diff --git a/scripts/docker/entrypoint-smp-server b/scripts/docker/entrypoint-smp-server index 5817b7b56..eeea3582d 100755 --- a/scripts/docker/entrypoint-smp-server +++ b/scripts/docker/entrypoint-smp-server @@ -1,48 +1,87 @@ #!/usr/bin/env sh +set -e + confd='/etc/opt/simplex' -logd='/var/opt/simplex/' +cert_path='/certificates' # Check if server has been initialized if [ ! -f "${confd}/smp-server.ini" ]; then # If not, determine ip or domain case "${ADDR}" in - '') printf 'Please specify $ADDR environment variable.\n'; exit 1 ;; + '') + printf 'Please specify $ADDR environment variable.\n' + exit 1 + ;; + + # Determine domain or IPv6 *[a-zA-Z]*) case "${ADDR}" in - *:*) set -- --ip "${ADDR}" ;; - *) set -- -n "${ADDR}" ;; + # IPv6 + *:*) + set -- --ip "${ADDR}" + ;; + + # Domain + *) + case "${ADDR}" in + # It's in domain format + *.*) + # Determine the base domain + ADDR_BASE="$(printf '%s' "$ADDR" | awk -F. '{print $(NF-1)"."$NF}')" + set -- --fqdn "${ADDR}" --own-domains="${ADDR_BASE}" + ;; + + # Incorrect domain + *) + printf 'Incorrect $ADDR environment variable. Please specify the correct one in format: smp1.example.org / example.org \n' + exit 1 + ;; + esac esac ;; - *) set -- --ip "${ADDR}" ;; + + # Assume everything else is IPv4 + *) + set -- --ip "${ADDR}" ;; esac # Optionally, set password case "${PASS}" in - '') set -- "$@" --no-password ;; - *) set -- "$@" --password "${PASS}" ;; + # Empty value = no password + '') + set -- "$@" --no-password + ;; + + # Assume that everything else is a password + *) + set -- "$@" --password "${PASS}" + ;; esac # And init certificates and configs - smp-server init -y -l "$@" + smp-server init --yes \ + --store-log \ + --daily-stats \ + --source-code \ + "$@" > /dev/null 2>&1 + + # Fix path to certificates + if [ -n "${WEB_MANUAL}" ]; then + sed -i -e 's|^[^#]*https: |#&|' \ + -e 's|^[^#]*cert: |#&|' \ + -e 's|^[^#]*key: |#&|' \ + -e 's|^port:.*|port: 5223|' \ + "${confd}/smp-server.ini" + else + sed -i -e "s|cert: /etc/opt/simplex/web.crt|cert: $cert_path/$ADDR.crt|" \ + -e "s|key: /etc/opt/simplex/web.key|key: $cert_path/$ADDR.key|" \ + "${confd}/smp-server.ini" + fi fi # Backup store log just in case -# -# Uses the UTC (universal) time zone and this -# format: YYYY-mm-dd'T'HH:MM:SS -# year, month, day, letter T, hour, minute, second -# -# This is the ISO 8601 format without the time zone at the end. -# -_file="${logd}/smp-server-store.log" -if [ -f "${_file}" ]; then - _backup_extension="$(date -u '+%Y-%m-%dT%H:%M:%S')" - cp -v -p "${_file}" "${_file}.${_backup_extension:-date-failed}" - unset -v _backup_extension -fi -unset -v _file +DOCKER=true /usr/local/bin/simplex-servers-stopscript smp-server # Finally, run smp-sever. Notice that "exec" here is important: # smp-server replaces our helper script, so that it can catch INT signal exec smp-server start +RTS -N -RTS - diff --git a/scripts/docker/entrypoint-xftp-server b/scripts/docker/entrypoint-xftp-server index 9e5bf5ac1..31d75362e 100755 --- a/scripts/docker/entrypoint-xftp-server +++ b/scripts/docker/entrypoint-xftp-server @@ -1,50 +1,90 @@ #!/usr/bin/env sh +set -eu + confd='/etc/opt/simplex-xftp' -logd='/var/opt/simplex-xftp' # Check if server has been initialized if [ ! -f "${confd}/file-server.ini" ]; then # If not, determine ip or domain case "${ADDR}" in - '') printf 'Please specify $ADDR environment variable.\n'; exit 1 ;; + '') + printf 'Please specify $ADDR environment variable.\n' + exit 1 + ;; + + # Determine domain or IPv6 *[a-zA-Z]*) case "${ADDR}" in - *:*) set -- --ip "${ADDR}" ;; - *) set -- -n "${ADDR}" ;; + # IPv6 + *:*) + set -- --ip "${ADDR}" + ;; + + # Domain + *) + case "${ADDR}" in + # Check if format is correct + *.*) + set -- --fqdn "${ADDR}" + ;; + + # Incorrect domain + *) + printf 'Incorrect $ADDR environment variable. Please specify the correct one in format: smp1.example.org / example.org \n' + exit 1 + ;; + esac + ;; esac ;; - *) set -- --ip "${ADDR}" ;; + + # Assume everything else is IPv4 + *) + set -- --ip "${ADDR}" + ;; esac - # Set quota + # Set global disk quota case "${QUOTA}" in - '') printf 'Please specify $QUOTA environment variable.\n'; exit 1 ;; - *GB) QUOTA="$(printf ${QUOTA} | tr '[:upper:]' '[:lower:]')"; set -- "$@" --quota "${QUOTA}" ;; - *gb) set -- "$@" --quota "${QUOTA}" ;; - *) printf 'Wrong format. Format should be: 1gb, 10gb, 100gb.\n'; exit 1 ;; + '') + printf 'Please specify $QUOTA environment variable.\n' + exit 1 + ;; + + # Incorrect format in uppercase, but automagically workaround this, replacing characters to lowercase + *GB) + QUOTA="$(printf '%s' "${QUOTA}" | tr '[:upper:]' '[:lower:]')" + set -- "$@" --quota "${QUOTA}" + ;; + + # Correct format + *gb) + set -- "$@" --quota "${QUOTA}" + ;; + + # Incorrect format + *) + printf 'Wrong format. Format should be: 1gb, 10gb, 100gb.\n' + exit 1 + ;; esac # Init the certificates and configs - xftp-server init -l -p /srv/xftp "$@" + xftp-server init --store-log \ + --path /srv/xftp \ + "$@" > /dev/null 2>&1 + + # Optionally, set password + if [ -n "${PASS}" ]; then + sed -i -e "/^# create_password:/a create_password: $PASS" \ + "${confd}/file-server.ini" + fi fi # Backup store log just in case -# -# Uses the UTC (universal) time zone and this -# format: YYYY-mm-dd'T'HH:MM:SS -# year, month, day, letter T, hour, minute, second -# -# This is the ISO 8601 format without the time zone at the end. -# -_file="${logd}/file-server-store.log" -if [ -f "${_file}" ]; then - _backup_extension="$(date -u '+%Y-%m-%dT%H:%M:%S')" - cp -v -p "${_file}" "${_file}.${_backup_extension:-date-failed}" - unset -v _backup_extension -fi -unset -v _file + +DOCKER=true /usr/local/bin/simplex-servers-stopscript xftp-server # Finally, run xftp-sever. Notice that "exec" here is important: # smp-server replaces our helper script, so that it can catch INT signal exec xftp-server start +RTS -N -RTS - diff --git a/scripts/main/simplex-servers-stopscript b/scripts/main/simplex-servers-stopscript index acfed3431..0b7003ead 100755 --- a/scripts/main/simplex-servers-stopscript +++ b/scripts/main/simplex-servers-stopscript @@ -148,8 +148,10 @@ xftp_cleanup() { main() { type="${1:-}" - - checks + + if [ -z "${DOCKER+x}" ]; then + checks + fi case "$type" in smp-server)