haiwen/seafile-docker

Fix broken deployment when served using HTTP behind a HTTPs proxy

undergroundwires opened this issue · 3 comments

Using Seafile on HTTP mode and having a HTTP proxy on front of it does not work.

Following PRs solve this issue: #325 and #324, this issue organizes these PRs.

Community has come up with own solutions such as ggogel/seafile-containerized to support container best-practices such as allowing this kind of scenario, however this is an important feature/bug fix that can be and should be supported in the official Docker image.

If anyone is interested in getting this working before the official solution is there. Here's my workaround:

Click here to expand and see
  1. Create patch-seafile.Dockerfile:
FROM python:3.11
WORKDIR /var/app
COPY patch.py .
CMD [ "python", "./patch.py"]
  1. Create patch.py:
# This scripts is workaround to get HTTP working for Seafile docker, see https://github.com/haiwen/seafile-docker/issues/326.
# It's here until following PRs are merged:
# - "Allow specifying FILE_SERVER_ROOT", https://github.com/haiwen/seafile-docker/pull/324
# - "Add: enable .well-known/acme-challenge when https", https://github.com/haiwen/seafile-docker/pull/325

import datetime
import os
import logging
import re
from time import sleep

SEAFILE_DATA_DIR='/seafile-data'

def main():
    setup_logger()
    logging.info('Running script.')
    fix_file_server_root()
    fix_nginx_configuration()
    logging.info('Completed running script.')

def setup_logger():
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(message)s",
        handlers=[
            logging.FileHandler("debug.log"),
            logging.StreamHandler()
        ]
    )

def fix_file_server_root():
    # "Allow specifying FILE_SERVER_ROOT", https://github.com/haiwen/seafile-docker/pull/324
    logging.info('Fixing FILE_SERVER_ROOT configuration')
    if is_https():
        logging.info('Skipping because https is desired.')
        return
    seafile_conf_file=f'{SEAFILE_DATA_DIR}/seafile/conf/seahub_settings.py'
    content = read_config_file(seafile_conf_file)
    if 'FILE_SERVER_ROOT = "https' in content:
        logging.info('Skipping because FILE_SERVER_ROOT is already set to HTTPS.')
        return
    save_backup(seafile_conf_file, content)
    content = content.replace('FILE_SERVER_ROOT = "http', 'FILE_SERVER_ROOT = "https')
    save_new_content(seafile_conf_file, content)
    cache_file=f'{SEAFILE_DATA_DIR}/seafile/conf/seahub_settings.pyc' # https://manual.seafile.com/config/seahub_settings_py/#note
    if os.path.isfile(cache_file):
        logging.error(f'Deleting cache file {cache_file} so changes take effect.')
        os.remove(cache_file)

def fix_nginx_configuration():
    # Fixes "Add: enable .well-known/acme-challenge when https", https://github.com/haiwen/seafile-docker/pull/325
    logging.info('Fixing nginx configuration')
    if is_https():
        logging.info('Skipping because https is desired.')
        return
    nginx_conf_file = f"{SEAFILE_DATA_DIR}/nginx/conf/seafile.nginx.conf"
    logging.info(f'Fixing nginx configuration in ${nginx_conf_file}')
    content = read_config_file(nginx_conf_file)
    if 'For letsencrypt' not in content:
        logging.info('Skipping because letsencrypt is not configured')
        return
    save_backup(nginx_conf_file, content)
    pattern = re.compile(r"^\s*# For letsencrypt[\S\s]*\;\s*\}", re.MULTILINE)
    content = pattern.sub('', content)
    save_new_content(nginx_conf_file, content)

def save_new_content(file_path: str, content: str):
    if not os.path.isfile(file_path):
        logging.error(f'File {file_path} does not exist.')
    logging.info(f'Saving new configuration:\n\n---\n{content}\n---')
    with open(file_path, 'w') as file:
        file.write(content)
    logging.info(f'Saved new configuration to {file_path}.')

def save_backup(original_file_path: str, original_content: str):
    timestamp = (datetime.datetime.utcnow().strftime("%m%d%y-%H%M%S"))
    backup_file_name=f"{original_file_path}.backup.{timestamp}"
    with open(backup_file_name, 'w') as file:
        file.write(original_content)
        logging.info(f'Saved original file as backup file: {backup_file_name}')

def read_config_file(file_path: str) -> str:
    if not os.path.isfile(file_path):
        wait_in_seconds=1
        logging.info(f'File {file_path} does not exist, might not be created yet, waiting for {wait_in_seconds} seconds.')
        sleep(wait_in_seconds)
        return read_config_file(file_path)
    with open(file_path, 'r') as file:
        content = file.read()
        logging.info(f'Current configuration ({file_path}):\n\n---\n{content}\n---')
        return content

def is_https():
    return get_conf('SEAFILE_SERVER_LETSENCRYPT', 'false').lower() == 'true'

def get_conf(key: str, default=None):
    key = key.upper()
    return os.environ.get(key, default)

main()
  1. Run the container along with official seafile image, e.g. if you're using docker-compose in docker-compose.yml:
  seafile:
    image: seafileltd/seafile-mc:latest
    volumes:
      - {YOUR-LOCAL-SEAFILE-DATA-DIR}:/shared 
      SEAFILE_SERVER_LETSENCRYPT: false
      # ...

  # Add this:
  patch-seafile:
    build:
      context: ./
      dockerfile: patch-seafile.Dockerfile
    environment:
      SEAFILE_SERVER_LETSENCRYPT: false
    volumes:
      - {YOUR-LOCAL-SEAFILE-DATA-DIR}:/seafile-data

nice workaround @undergroundwires!

however, I'm a little surprised you didn't include a depends: directive to the patch service to make sure it doesn't fire before the seafile-* services

nice workaround @undergroundwires!

however, I'm a little surprised you didn't include a depends: directive to the patch service to make sure it doesn't fire before the seafile-* services

Since depends on: is only ensuring service order, this might not do what you expect. You could add a health check to the seafile container and add that as a condition to the patch service. Since seafile can take a very long time to be ready (chmodding the files in case of "run as user", e.g.), that's the safer alternative, I believe, but correct me if I'm wrong.