Merge pull request #56 from haiwen/seafile-pro

Seafile pro
This commit is contained in:
Daniel Pan 2018-05-08 10:21:56 +08:00 committed by GitHub
commit 7bdb0e10a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1300 additions and 431 deletions

View file

@ -2,26 +2,41 @@
Imagine the previous version is 6.0.5 and we have released 6.0.7. Here are the steps to do the upgrade. Imagine the previous version is 6.0.5 and we have released 6.0.7. Here are the steps to do the upgrade.
* Switch to a branch "unstable" * Switch to a branch "master"
```sh ```sh
git branch -f unstable origin/master git branch -f master origin/master
git checkout unstable git checkout master
``` ```
* Update the version number in all the files/scripts from "6.0.5" to "6.0.7" and push it to github, then wait for travis ci (https://travis-ci.org/haiwen/seafile-docker/builds) to pass * Update the version number in all the files/scripts from "6.0.5" to "6.0.7" and push it to github, then wait for travis ci (https://travis-ci.org/haiwen/seafile-docker/builds) to pass
```sh ```sh
git push origin unstable:unstable git push origin master
``` ```
* Normal
* Create a tag "seafile-base" and push it to github. Wait for travis ci to finish: this time it would push the image seafileltd/base:16.04 to docker hub since it's triggered by a tag.
```sh
git tag seafile-base
git push origin seafile-base
```
* Create a tag "v6.0.7" and push it to github. Wait for travis ci to finish: this time it would push the image seafileltd/seafile:6.0.7 to docker hub since it's triggered by a tag. * Create a tag "v6.0.7" and push it to github. Wait for travis ci to finish: this time it would push the image seafileltd/seafile:6.0.7 to docker hub since it's triggered by a tag.
```sh ```sh
git tag v6.0.7 git tag v6.0.7
git push origin v6.0.7 git push origin v6.0.7
``` ```
* Ensure the new image is available in https://hub.docker.com/r/seafileltd/seafile/tags/ * Ensure the new image is available in https://hub.docker.com/r/seafileltd/seafile/tags/
* Now update the master branch.
``` * Pro
git push origin unstable:master
``` * Create a tag "seafile-pro-base" and push it to github. Wait for travis ci to finish: this time it would push the image ${registry}/seafileltd/pro-base:16.04 to docker Registry since it's triggered by a tag.
* Delete the unstable branch
```sh ```sh
git push origin :unstable git tag seafile-pro-base
git push origin seafile-pro-base
```
* Create a tag "v6.0.7-pro" and push it to github. Wait for travis ci to finish: this time it would push the image ${registry}/seafileltd/pro-seafile:6.0.7 to docker Registry since it's triggered by a tag.
```sh
git tag v6.0.7-pro
git push origin v6.0.7
``` ```

147
README.pro.md Normal file
View file

@ -0,0 +1,147 @@
[![Build Status](https://secure.travis-ci.org/haiwen/seafile-docker.png?branch=master)](http://travis-ci.org/haiwen/seafile-docker)
### About
- [Docker](https://docker.com/) is an open source project to pack, ship and run any Linux application in a lighter weight, faster container than a traditional virtual machine.
- Docker makes it much easier to deploy [a Seafile server](https://github.com/haiwen/seafile) on your servers and keep it updated.
- The base image configures Seafile with the Seafile team's recommended optimal defaults.
If you are not familiar with docker commands, please refer to [docker documentation](https://docs.docker.com/engine/reference/commandline/cli/).
### Getting Started
To login the seafile private registry:
```sh
docker login {pro-host}
```
You can see the private registry information on the [customer center](https://customer.seafile.com/downloads/)
To run the seafile server container:
```sh
docker run -d --name seafile \
-e SEAFILE_SERVER_HOSTNAME=seafile.example.com \
-v /opt/seafile-data:/shared \
-p 80:80 \
{pro-host}/seafileltd/pro-seafile:latest
```
Wait for a few minutes for the first time initialization, then visit `http://seafile.example.com` to open Seafile Web UI.
This command will mount folder `/opt/seafile-data` at the local server to the docker instance. You can find logs and other data under this folder.
### More configuration Options
#### Custom Admin Username and Password
The default admin account is `me@example.com` and the password is `asecret`. You can use a different password by setting the container's environment variables:
e.g.
```sh
docker run -d --name seafile \
-e SEAFILE_SERVER_HOSTNAME=seafile.example.com \
-e SEAFILE_ADMIN_EMAIL=me@example.com \
-e SEAFILE_ADMIN_PASSWORD=a_very_secret_password \
-v /opt/seafile-data:/shared \
-p 80:80 \
{pro-host}/seafileltd/pro-seafile:latest
```
If you forget the admin password, you can add a new admin account and then go to the sysadmin panel to reset user password.
#### Let's encrypt SSL certificate
If you set `SEAFILE_SERVER_LETSENCRYPT` to `true`, the container would request a letsencrypt-signed SSL certificate for you automatically.
e.g.
```
docker run -d --name seafile \
-e SEAFILE_SERVER_LETSENCRYPT=true \
-e SEAFILE_SERVER_HOSTNAME=seafile.example.com \
-e SEAFILE_ADMIN_EMAIL=me@example.com \
-e SEAFILE_ADMIN_PASSWORD=a_very_secret_password \
-v /opt/seafile-data:/shared \
-p 80:80 \
-p 443:443 \
{pro-host}/seafileltd/pro-seafile:latest
```
If you want to use your own SSL certificate:
- create a folder `/opt/seafile-data/ssl`, and put your certificate and private key under the ssl directory.
- Assume your site name is `seafile.example.com`, then your certificate must have the name `seafile.example.com.crt`, and the private key must have the name `seafile.example.com.key`.
#### Modify Seafile Server Configurations
The config files are under `shared/seafile/conf`. You can modify the configurations according to [Seafile manual](https://manual.seafile.com/)
After modification, you need to restart the container:
```
docker restart seafile
```
#### Find logs
The seafile logs are under `/shared/logs/seafile` in the docker, or `/opt/seafile-data/logs/seafile` in the server that run the docker.
The system logs are under `/shared/logs/var-log`, or `/opt/seafile-data/logs/var-log` in the server that run the docker.
#### Add a new Admin
Ensure the container is running, then enter this command:
```
docker exec -it seafile /opt/seafile/seafile-server-latest/reset-admin.sh
```
Enter the username and password according to the prompts. You now have a new admin account.
### Directory Structure
#### `/shared`
Placeholder spot for shared volumes. You may elect to store certain persistent information outside of a container, in our case we keep various logfiles and upload directory outside. This allows you to rebuild containers easily without losing important information.
- /shared/db: This is the data directory for mysql server
- /shared/seafile: This is the directory for seafile server configuration and data.
- /shared/logs: This is the directory for logs.
- /shared/logs/var-log: This is the directory that would be mounted as `/var/log` inside the container. For example, you can find the nginx logs in `shared/logs/var-log/nginx/`.
- /shared/logs/seafile: This is the directory that would contain the log files of seafile server processes. For example, you can find seaf-server logs in `shared/logs/seafile/seafile.log`.
- /shared/ssl: This is directory for certificate, which does not exist by default.
- /shared/bootstrap.conf: This file does not exist by default. You can create it by your self, and write the configuration of files similar to the `samples` folder.
### Upgrading Seafile Server
TO upgrade to latest version of seafile server:
```sh
docker pull {pro-host}/seafileltd/pro-seafile:latest
docker rm -f seafile
docker run -d --name seafile \
-e SEAFILE_SERVER_LETSENCRYPT=true \
-e SEAFILE_SERVER_HOSTNAME=seafile.example.com \
-e SEAFILE_ADMIN_EMAIL=me@example.com \
-e SEAFILE_ADMIN_PASSWORD=a_very_secret_password \
-v /opt/seafile-data:/shared \
-p 80:80 \
-p 443:443 \
{pro-host}/seafileltd/pro-seafile:latest
```
If you are one of the early users who use the `launcher` script, you should refer to [upgrade from old format](https://github.com/haiwen/seafile-docker/blob/master/upgrade_from_old_format.md) document.
### Troubleshooting
You can run docker commands like "docker logs" or "docker exec" to find errors.
```sh
docker logs -f seafile
# or
docker exec -it seafile bash
```

View file

@ -1,25 +1,49 @@
#!/bin/bash #!/bin/bash
version=6.2.5 version=6.2.10
set -e -x set -e -x
./ci/install_deps.sh
( (
cd image cd image
# pip install docker-squash # pip install docker-squash
# make base squash-base server # make base squash-base server
make base make base
make server make pro-base
make pro-server
) )
mkdir -p /opt/seafile-data mkdir -p /opt/seafile-data
docker run -d --name seafile -v /opt/seafile-data:/shared -p 80:80 -p 443:443 seafileltd/seafile:$version docker run -d --name seafile -e SEAFILE_SERVER_HOSTNAME=127.0.0.1 -v /opt/seafile-data:/shared -p 80:80 -p 443:443 seafileltd/pro-seafile:$version
docker stop seafile
docker start seafile
docker restart seafile
if [[ $TRAVIS_TAG != "" ]]; then
cat > doc.md <<EOF
# Doc
Hello world.
EOF
sleep 50
python ci/upload.py doc.md
python ci/validate_file.py doc.md
docker restart seafile
sleep 30
python ci/validate_file.py doc.md
docker rm -f seafile
docker run -d --name seafile -e SEAFILE_SERVER_HOSTNAME=127.0.0.1 -v /opt/seafile-data:/shared -p 80:80 -p 443:443 seafileltd/pro-seafile:$version
sleep 30
python ci/validate_file.py doc.md
rm -rf doc.md
if [[ $TRAVIS_TAG =~ ^v([0-9]*?)(\.([0-9])*?){2}-pro$ ]]; then
ci/publish-pro-image.sh
elif [[ $TRAVIS_TAG =~ ^v([0-9]*?)(\.([0-9])*?){2}$ ]]; then
ci/publish-image.sh ci/publish-image.sh
elif [[ $TRAVIS_TAG =~ ^seafile-pro-base$ ]]; then
ci/publish-pro-base.sh
elif [[ $TRAVIS_TAG =~ ^seafile-base$ ]]; then
ci/publish-base.sh
else else
echo "Not going to push the image to docker hub, since it's not a build triggered by a tag" echo "Not going to push the image to docker hub, since it's not a build triggered by a tag"
fi fi

3
ci/install_deps.sh Executable file
View file

@ -0,0 +1,3 @@
pip install requests
pip install docker-squash
pip install docker==2.7.0

19
ci/publish-base.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
######################################
# Publish the seafile base image to docker
# registry. This script should only be called during a travis build trigger by a tag.
######################################
# Nerver use "set -x" or it would expose the docker credentials in the travis logs!
set -e
set -o pipefail
## Always use the base image we build manually to reduce the download size of the end user.
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
(
cd image
make push-base
)

View file

@ -12,6 +12,7 @@ set -o pipefail
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
## Always use the base image we build manually to reduce the download size of the end user. ## Always use the base image we build manually to reduce the download size of the end user.
docker rmi -f $(docker images | awk {'print $3'})
docker pull seafileltd/base:16.04 docker pull seafileltd/base:16.04
( (

24
ci/publish-pro-base.sh Executable file
View file

@ -0,0 +1,24 @@
#!/bin/bash
######################################
# Publish the seafile pro-base image to docker
# registry. This script should only be called during a travis build trigger by a tag.
######################################
# Nerver use "set -x" or it would expose the docker credentials in the travis logs!
set -e
set -o pipefail
docker login -u="$DOCKER_PRO_REGISTRY_USER" -p="$DOCKER_PRO_REGISTRY_PASSWORD" docker-internal.seadrive.org
(
cd image
make host=docker-internal.seadrive.org push-pro-base
)
docker login -u="$LOCAL_DOCKER_PRO_REGISTRY_USER" -p="$LOCAL_DOCKER_PRO_REGISTRY_PASSWORD" docker-internal.seafile.top
(
cd image
make host=docker-internal.seafile.top push-pro-base
)

34
ci/publish-pro-image.sh Executable file
View file

@ -0,0 +1,34 @@
#!/bin/bash
######################################
# Publish the seafile pro-server image (e.g. seafileltd/pro-seafile:6.2.3) to docker
# registry. This script should only be called during a travis build trigger by a tag.
######################################
# Nerver use "set -x" or it would expose the docker credentials in the travis logs!
set -e
set -o pipefail
docker login -u="$DOCKER_PRO_REGISTRY_USER" -p="$DOCKER_PRO_REGISTRY_PASSWORD" docker-internal.seadrive.org
## Always use the base image we build manually to reduce the download size of the end user.
docker rmi -f $(docker images | awk {'print $3'})
docker pull docker-internal.seadrive.org/seafileltd/pro-base:16.04
docker tag docker-internal.seadrive.org/seafileltd/pro-base:16.04 seafileltd/pro-base:16.04
(
cd image
make host=docker-internal.seadrive.org pro-server push-pro-server
)
docker login -u="$LOCAL_DOCKER_PRO_REGISTRY_USER" -p="$LOCAL_DOCKER_PRO_REGISTRY_PASSWORD" docker-internal.seafile.top
docker rmi -f $(docker images | awk {'print $3'})
docker pull docker-internal.seafile.top/seafileltd/pro-base:16.04
docker tag docker-internal.seafile.top/seafileltd/pro-base:16.04 seafileltd/pro-base:16.04
(
cd image
make host=docker-internal.seafile.top pro-server push-pro-server
)

64
ci/upload.py Executable file
View file

@ -0,0 +1,64 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import urllib2
import json
import requests
import sys
"""
Get token
"""
hostname = 'http://127.0.0.1'
username = 'me@example.com'
password = 'asecret'
get_token_url = '{}/api2/auth-token/'.format(hostname)
data = urllib.urlencode({'username': username, 'password': password})
request = urllib2.Request(get_token_url,data)
response = urllib2.urlopen(request)
_js_py = json.load(response)
token = _js_py['token']
response.close()
"""
Generate default repo_id
"""
get_library_url = '{}/api2/default-repo/'.format(hostname)
headers = {'Authorization': 'Token ' + token, 'Connection':'close'}
r = requests.post(get_library_url, headers=headers)
assert r.status_code == 200
"""
Get default repo_id
"""
get_library_url = '{}/api2/default-repo/'.format(hostname)
get_repo_id = urllib2.Request(get_library_url)
get_repo_id.add_header('Authorization','Token ' + token)
response_repo_id = urllib2.urlopen(get_repo_id)
_js_py = json.load(response_repo_id)
repo_id = _js_py['repo_id']
response_repo_id.close()
"""
Get upload link
"""
upload_link_url = '{}/api2/repos/{}/upload-link/'.format(hostname, repo_id)
get_upload_link = urllib2.Request(upload_link_url)
get_upload_link.add_header('Authorization','Token ' + token)
response_upload_link = urllib2.urlopen(get_upload_link)
upload_link = json.load(response_upload_link).replace('seafile.example.com', '127.0.0.1').replace('https', 'http')
response_upload_link.close()
"""
Upload file
"""
filename = sys.argv[1]
url = upload_link
files = {'file': open(filename, 'rb')}
r = requests.post(
url, data={'filename': filename, 'parent_dir': '/'},
files=files, headers={'Authorization': 'Token ' + token})
files['file'].close()

48
ci/validate_file.py Executable file
View file

@ -0,0 +1,48 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import urllib2
import json
import sys
"""
Get token
"""
hostname = 'http://127.0.0.1'
username = 'me@example.com'
password = 'asecret'
get_token_url = '{}/api2/auth-token/'.format(hostname)
data = urllib.urlencode({'username': username, 'password': password})
request = urllib2.Request(get_token_url,data)
response = urllib2.urlopen(request)
_js_py = json.load(response)
token = _js_py['token']
response.close()
"""
Get default repo_id
"""
get_library_url = '{}/api2/default-repo/'.format(hostname)
get_repo_id = urllib2.Request(get_library_url)
get_repo_id.add_header('Authorization','Token ' + token)
response_repo_id = urllib2.urlopen(get_repo_id)
_js_py = json.load(response_repo_id)
repo_id = _js_py['repo_id']
response_repo_id.close()
"""
Get upload link
"""
filename = sys.argv[1]
view_file_url = '{}/api2/repos/{}/file/?p={}'.format(hostname, repo_id, filename)
view_file_link = urllib2.Request(view_file_url)
view_file_link.add_header('Authorization','Token ' + token)
try:
response_view_file_link = urllib2.urlopen(view_file_link)
res = json.load(response_view_file_link)
except Exception as e:
print e
sys.exit(1)
else:
code = 0 if repo_id in res and filename in res else 1

View file

@ -1,8 +1,14 @@
server_version=6.2.5 server_version=6.2.10
base_image=seafileltd/base:16.04 base_image=seafileltd/base:16.04
base_image_squashed=seafileltd/base:16.04-squashed base_image_squashed=seafileltd/base:16.04-squashed
pro_base_image=seafileltd/pro-base:16.04
pro_base_image_squashed=seafileltd/pro-base:16.04-squashed
server_image=seafileltd/seafile:$(server_version) server_image=seafileltd/seafile:$(server_version)
server_image_squashed=seafileltd/seafile:$(server_version)-squashed
pro_server_image=seafileltd/pro-seafile:$(server_version)
pro_server_image_squashed=seafileltd/pro-seafile:$(server_version)-squashed
latest_pro_server_image=seafileltd/pro-seafile:latest
latest_server_image=seafileltd/seafile:latest latest_server_image=seafileltd/seafile:latest
all: all:
@ -11,23 +17,47 @@ all:
@echo @echo
base: base:
docker pull phusion/baseimage:0.9.19
docker-squash --tag phusion/baseimage:latest phusion/baseimage:0.9.19
docker tag phusion/baseimage:latest phusion/baseimage:0.9.19
cd base && docker build -t $(base_image) . cd base && docker build -t $(base_image) .
docker-squash --tag $(base_image_squashed) $(base_image)
squash-base:
docker-squash --tag $(base_image_squashed) $(base_image) --from-layer phusion/baseimage:0.10.1
docker tag $(base_image_squashed) $(base_image) docker tag $(base_image_squashed) $(base_image)
server: server:
cd seafile && docker build -t $(server_image) . cd seafile && docker build -t $(server_image) .
docker-squash --tag $(server_image_squashed) $(server_image) --from-layer=$(base_image)
docker tag $(server_image_squashed) $(server_image)
docker tag $(server_image) $(latest_server_image) docker tag $(server_image) $(latest_server_image)
pro-base:
cd pro_base && docker build -t $(pro_base_image) .
docker-squash --tag $(pro_base_image_squashed) $(pro_base_image)
docker tag $(pro_base_image_squashed) $(pro_base_image)
pro-server:
cd pro_seafile && docker build -t $(pro_server_image) .
docker-squash --tag $(pro_server_image_squashed) $(pro_server_image) --from-layer=$(pro_base_image)
docker tag $(pro_server_image_squashed) $(pro_server_image)
docker tag $(pro_server_image) $(latest_pro_server_image)
push-base: push-base:
docker push $(base_image) docker push $(base_image)
push-pro-base:
docker tag $(pro_base_image) ${host}/$(pro_base_image)
docker push ${host}/$(pro_base_image)
push-server: push-server:
docker push $(server_image) docker push $(server_image)
docker push $(latest_server_image) docker push $(latest_server_image)
push-pro-server:
docker tag $(pro_server_image) ${host}/$(pro_server_image)
docker tag $(pro_server_image) ${host}/$(latest_pro_server_image)
docker push ${host}/$(pro_server_image)
docker push ${host}/$(latest_pro_server_image)
push: push-base push-server push: push-base push-server
.PHONY: base server push push-base push-server .PHONY: base server push push-base push-server

12
image/pro_base/Dockerfile Normal file
View file

@ -0,0 +1,12 @@
FROM seafileltd/base:16.04
# syslog-ng and syslog-forwarder would mess up the container stdout, not good
# when debugging/upgrading.
RUN apt update
RUN apt-get install -y openjdk-8-jre libmemcached-dev zlib1g-dev pwgen curl openssl poppler-utils libpython2.7 libreoffice \
libreoffice-script-provider-python ttf-wqy-microhei ttf-wqy-zenhei xfonts-wqy python-requests
RUN apt-get install -y python-pip python-setuptools python-urllib3
RUN apt clean

View file

@ -0,0 +1,18 @@
FROM seafileltd/pro-base:16.04
WORKDIR /opt/seafile
ENV SEAFILE_VERSION=6.2.10 SEAFILE_SERVER=seafile-pro-server
RUN mkdir -p /etc/my_init.d
RUN mkdir -p /opt/seafile/
RUN curl -sSL -G -d "p=/seafile-pro-server_${SEAFILE_VERSION}_x86-64.tar.gz&dl=1" https://download.seafile.top/d/8c29766a64d24122936f/files/ \
| tar xzf - -C /opt/seafile/
ADD create_data_links.sh /etc/my_init.d/01_create_data_links.sh
COPY scripts /scripts
COPY templates /templates
CMD ["/sbin/my_init", "--", "/scripts/start.py"]

View file

@ -0,0 +1,60 @@
#!/bin/bash
set -e
set -o pipefail
if [[ $SEAFILE_BOOTSRAP != "" ]]; then
exit 0
fi
dirs=(
conf
ccnet
seafile-data
seahub-data
pro-data
seafile-license.txt
)
for d in ${dirs[*]}; do
src=/shared/seafile/$d
if [[ -e $src ]]; then
rm -rf /opt/seafile/$d && ln -sf $src /opt/seafile
fi
done
if [[ ! -e /shared/logs/seafile ]]; then
mkdir -p /shared/logs/seafile
fi
rm -rf /opt/seafile/logs && ln -sf /shared/logs/seafile/ /opt/seafile/logs
current_version_dir=/opt/seafile/seafile-pro-server-${SEAFILE_VERSION}
latest_version_dir=/opt/seafile/seafile-server-latest
seahub_data_dir=/shared/seafile/seahub-data
if [[ ! -e $seahub_data_dir ]]; then
mkdir -p $seahub_data_dir
fi
media_dirs=(
avatars
custom
)
for d in ${media_dirs[*]}; do
source_media_dir=${current_version_dir}/seahub/media/$d
if [ -e ${source_media_dir} ] && [ ! -e ${seahub_data_dir}/$d ]; then
mv $source_media_dir ${seahub_data_dir}/$d
fi
rm -rf $source_media_dir && ln -sf ${seahub_data_dir}/$d $source_media_dir
done
rm -rf /var/lib/mysql
if [[ ! -e /shared/db ]];then
mkdir -p /shared/db
fi
ln -sf /shared/db /var/lib/mysql
if [[ ! -e /shared/logs/var-log ]]; then
mv /var/log /shared/logs/var-log
fi
rm -rf /var/log && ln -sf /shared/logs/var-log /var/log

View file

@ -0,0 +1,156 @@
#!/usr/bin/env python
#coding: UTF-8
"""
Bootstraping seafile server, letsencrypt (verification & cron job).
"""
import argparse
import os
from os.path import abspath, basename, exists, dirname, join, isdir
import shutil
import sys
import uuid
import time
from utils import (
call, get_conf, get_install_dir, loginfo,
get_script, render_template, get_seafile_version, eprint,
cert_has_valid_days, get_version_stamp_file, update_version_stamp,
wait_for_mysql, wait_for_nginx, read_version_stamp
)
seafile_version = get_seafile_version()
installdir = get_install_dir()
topdir = dirname(installdir)
shared_seafiledir = '/shared/seafile'
ssl_dir = '/shared/ssl'
generated_dir = '/bootstrap/generated'
def init_letsencrypt():
loginfo('Preparing for letsencrypt ...')
wait_for_nginx()
if not exists(ssl_dir):
os.mkdir(ssl_dir)
domain = get_conf('SEAFILE_SERVER_HOSTNAME', 'seafile.example.com')
context = {
'ssl_dir': ssl_dir,
'domain': domain,
}
render_template(
'/templates/letsencrypt.cron.template',
join(generated_dir, 'letsencrypt.cron'),
context
)
ssl_crt = '/shared/ssl/{}.crt'.format(domain)
if exists(ssl_crt):
loginfo('Found existing cert file {}'.format(ssl_crt))
if cert_has_valid_days(ssl_crt, 30):
loginfo('Skip letsencrypt verification since we have a valid certificate')
return
loginfo('Starting letsencrypt verification')
# Create a temporary nginx conf to start a server, which would accessed by letsencrypt
context = {
'https': False,
'domain': domain,
}
render_template('/templates/seafile.nginx.conf.template',
'/etc/nginx/sites-enabled/seafile.nginx.conf', context)
call('nginx -s reload')
time.sleep(2)
call('/scripts/ssl.sh {0} {1}'.format(ssl_dir, domain))
# if call('/scripts/ssl.sh {0} {1}'.format(ssl_dir, domain), check_call=False) != 0:
# eprint('Now waiting 1000s for postmortem')
# time.sleep(1000)
# sys.exit(1)
def generate_local_nginx_conf():
# Now create the final nginx configuratin
domain = get_conf('SEAFILE_SERVER_HOSTNAME', 'seafile.example.com')
context = {
'https': is_https(),
'domain': domain,
}
render_template(
'/templates/seafile.nginx.conf.template',
'/etc/nginx/sites-enabled/seafile.nginx.conf',
context
)
def is_https():
return get_conf('SEAFILE_SERVER_LETSENCRYPT', 'false').lower() == 'true'
def parse_args():
ap = argparse.ArgumentParser()
ap.add_argument('--parse-ports', action='store_true')
return ap.parse_args()
def init_seafile_server():
version_stamp_file = get_version_stamp_file()
if exists(join(shared_seafiledir, 'seafile-data')):
if not exists(version_stamp_file):
update_version_stamp(os.environ['SEAFILE_VERSION'])
# sysbol link unlink after docker finish.
latest_version_dir='/opt/seafile/seafile-server-latest'
current_version_dir='/opt/seafile/seafile-pro-server-' + read_version_stamp()
if not exists(latest_version_dir):
call('ln -sf ' + current_version_dir + ' ' + latest_version_dir)
loginfo('Skip running setup-seafile-mysql.py because there is existing seafile-data folder.')
return
loginfo('Now running setup-seafile-mysql.py in auto mode.')
env = {
'SERVER_NAME': 'seafile',
'SERVER_IP': get_conf('SEAFILE_SERVER_HOSTNAME', 'seafile.example.com'),
'MYSQL_USER': 'seafile',
'MYSQL_USER_PASSWD': str(uuid.uuid4()),
'MYSQL_USER_HOST': '127.0.0.1',
# Default MariaDB root user has empty password and can only connect from localhost.
'MYSQL_ROOT_PASSWD': '',
}
# Change the script to allow mysql root password to be empty
call('''sed -i -e 's/if not mysql_root_passwd/if not mysql_root_passwd and "MYSQL_ROOT_PASSWD" not in os.environ/g' {}'''
.format(get_script('setup-seafile-mysql.py')))
setup_script = get_script('setup-seafile-mysql.sh')
call('{} auto -n seafile'.format(setup_script), env=env)
domain = get_conf('SEAFILE_SERVER_HOSTNAME', 'seafile.example.com')
proto = 'https' if is_https() else 'http'
with open(join(topdir, 'conf', 'seahub_settings.py'), 'a+') as fp:
fp.write('\n')
fp.write('FILE_SERVER_ROOT = "{proto}://{domain}/seafhttp"'.format(proto=proto, domain=domain))
fp.write('\n')
# By default ccnet-server binds to the unix socket file
# "/opt/seafile/ccnet/ccnet.sock", but /opt/seafile/ccnet/ is a mounted
# volume from the docker host, and on windows and some linux environment
# it's not possible to create unix sockets in an external-mounted
# directories. So we change the unix socket file path to
# "/opt/seafile/ccnet.sock" to avoid this problem.
with open(join(topdir, 'conf', 'ccnet.conf'), 'a+') as fp:
fp.write('\n')
fp.write('[Client]\n')
fp.write('UNIX_SOCKET = /opt/seafile/ccnet.sock\n')
fp.write('\n')
files_to_copy = ['conf', 'ccnet', 'seafile-data', 'seahub-data', 'pro-data']
for fn in files_to_copy:
src = join(topdir, fn)
dst = join(shared_seafiledir, fn)
if not exists(dst) and exists(src):
shutil.move(src, shared_seafiledir)
call('ln -sf ' + join(shared_seafiledir, fn) + ' ' + src)
loginfo('Updating version stamp')
update_version_stamp(os.environ['SEAFILE_VERSION'])

30
image/pro_seafile/scripts/gc.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/bash
set -e
# Before
SEAFILE_DIR=/opt/seafile/seafile-server-latest
$SEAFILE_DIR/seafile.sh stop
echo "Waiting for the server to shut down properly..."
sleep 5
# Do it
(
set +e
$SEAFILE_DIR/seaf-gc.sh | tee -a /var/log/gc.log
# We want to presevent the exit code of seaf-gc.sh
exit "${PIPESTATUS[0]}"
)
gc_exit_code=$?
# After
echo "Giving the server some time..."
sleep 3
$SEAFILE_DIR/seafile.sh start
exit $gc_exit_code

View file

@ -0,0 +1,46 @@
#!/bin/bash
set -e
ssldir=${1:?"error params"}
domain=${2:?"error params"}
letsencryptdir=$ssldir/letsencrypt
letsencrypt_script=$letsencryptdir/acme_tiny.py
ssl_account_key=${domain}.account.key
ssl_csr=${domain}.csr
ssl_key=${domain}.key
ssl_crt=${domain}.crt
mkdir -p /var/www/challenges && chmod -R 777 /var/www/challenges
mkdir -p ssldir
if ! [[ -d $letsencryptdir ]]; then
git clone git://github.com/diafygi/acme-tiny.git $letsencryptdir
else
cd $letsencryptdir
git pull origin master:master
fi
cd $ssldir
if [[ ! -e ${ssl_account_key} ]]; then
openssl genrsa 4096 > ${ssl_account_key}
fi
if [[ ! -e ${ssl_key} ]]; then
openssl genrsa 4096 > ${ssl_key}
fi
if [[ ! -e ${ssl_csr} ]]; then
openssl req -new -sha256 -key ${ssl_key} -subj "/CN=$domain" > $ssl_csr
fi
python $letsencrypt_script --account-key ${ssl_account_key} --csr $ssl_csr --acme-dir /var/www/challenges/ > ./signed.crt
curl -sSL -o intermediate.pem https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
cat signed.crt intermediate.pem > ${ssl_crt}
nginx -s reload
echo "Nginx reloaded."

View file

@ -0,0 +1,85 @@
#!/usr/bin/env python
#coding: UTF-8
"""
Starts the seafile/seahub server and watches the controller process. It is
the entrypoint command of the docker container.
"""
import json
import os
from os.path import abspath, basename, exists, dirname, join, isdir
import shutil
import sys
import time
from utils import (
call, get_conf, get_install_dir, get_script, get_command_output,
render_template, wait_for_mysql
)
from upgrade import check_upgrade
from bootstrap import init_seafile_server, is_https, init_letsencrypt, generate_local_nginx_conf
shared_seafiledir = '/shared/seafile'
ssl_dir = '/shared/ssl'
generated_dir = '/bootstrap/generated'
installdir = get_install_dir()
topdir = dirname(installdir)
def watch_controller():
maxretry = 4
retry = 0
while retry < maxretry:
controller_pid = get_command_output('ps aux | grep seafile-controller | grep -v grep || true').strip()
garbage_collector_pid = get_command_output('ps aux | grep /scripts/gc.sh | grep -v grep || true').strip()
if not controller_pid and not garbage_collector_pid:
retry += 1
else:
retry = 0
time.sleep(5)
print 'seafile controller exited unexpectedly.'
sys.exit(1)
def main():
if not exists(shared_seafiledir):
os.mkdir(shared_seafiledir)
if not exists(generated_dir):
os.makedirs(generated_dir)
if is_https():
init_letsencrypt()
generate_local_nginx_conf()
call('nginx -s reload')
wait_for_mysql()
init_seafile_server()
check_upgrade()
os.chdir(installdir)
admin_pw = {
'email': get_conf('SEAFILE_ADMIN_EMAIL', 'me@example.com'),
'password': get_conf('SEAFILE_ADMIN_PASSWORD', 'asecret'),
}
password_file = join(topdir, 'conf', 'admin.txt')
with open(password_file, 'w') as fp:
json.dump(admin_pw, fp)
try:
call('{} start'.format(get_script('seafile.sh')))
call('{} start'.format(get_script('seahub.sh')))
finally:
if exists(password_file):
os.unlink(password_file)
print 'seafile server is running now.'
try:
watch_controller()
except KeyboardInterrupt:
print 'Stopping seafile server.'
sys.exit(0)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,82 @@
#!/usr/bin/env python
#coding: UTF-8
"""
This script is used to run proper upgrade scripts automatically.
"""
import json
import re
import glob
import os
from os.path import abspath, basename, exists, dirname, join, isdir
import shutil
import sys
import time
from utils import (
call, get_install_dir, get_script, get_command_output, replace_file_pattern,
read_version_stamp, wait_for_mysql, update_version_stamp, loginfo
)
installdir = get_install_dir()
topdir = dirname(installdir)
def collect_upgrade_scripts(from_version, to_version):
"""
Give the current installed version, calculate which upgrade scripts we need
to run to upgrade it to the latest verison.
For example, given current version 5.0.1 and target version 6.1.0, and these
upgrade scripts:
upgrade_4.4_5.0.sh
upgrade_5.0_5.1.sh
upgrade_5.1_6.0.sh
upgrade_6.0_6.1.sh
We need to run upgrade_5.0_5.1.sh, upgrade_5.1_6.0.sh, and upgrade_6.0_6.1.sh.
"""
from_major_ver = '.'.join(from_version.split('.')[:2])
to_major_ver = '.'.join(to_version.split('.')[:2])
scripts = []
for fn in glob.glob(join(installdir, 'upgrade', 'upgrade_*_*.sh')):
va, vb = parse_upgrade_script_version(fn)
if va >= from_major_ver and vb <= to_major_ver:
scripts.append(fn)
return scripts
def parse_upgrade_script_version(script):
script = basename(script)
m = re.match(r'upgrade_([0-9+.]+)_([0-9+.]+).sh', basename(script))
return m.groups()
def check_upgrade():
last_version = read_version_stamp()
current_version = os.environ['SEAFILE_VERSION']
if last_version == current_version:
return
scripts_to_run = collect_upgrade_scripts(from_version=last_version, to_version=current_version)
for script in scripts_to_run:
loginfo('Running scripts {}'.format(script))
# Here we use a trick: use a version stamp like 6.1.0 to prevent running
# all upgrade scripts before 6.1 again (because 6.1 < 6.1.0 in python)
new_version = parse_upgrade_script_version(script)[1] + '.0'
replace_file_pattern(script, 'read dummy', '')
call(script)
update_version_stamp(new_version)
update_version_stamp(current_version)
def main():
wait_for_mysql()
os.chdir(installdir)
check_upgrade()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,287 @@
# coding: UTF-8
from __future__ import print_function
from ConfigParser import ConfigParser
from contextlib import contextmanager
import os
import datetime
from os.path import abspath, basename, exists, dirname, join, isdir, expanduser
import platform
import sys
import subprocess
import time
import logging
import logging.config
import click
import termcolor
import colorlog
logger = logging.getLogger('.utils')
DEBUG_ENABLED = os.environ.get('SEAFILE_DOCKER_VERBOSE', '').lower() in ('true', '1', 'yes')
def eprint(*a, **kw):
kw['file'] = sys.stderr
print(*a, **kw)
def identity(msg, *a, **kw):
return msg
colored = identity if not os.isatty(sys.stdin.fileno()) else termcolor.colored
red = lambda s: colored(s, 'red')
green = lambda s: colored(s, 'green')
def underlined(msg):
return '\x1b[4m{}\x1b[0m'.format(msg)
def sudo(*a, **kw):
call('sudo ' + a[0], *a[1:], **kw)
def _find_flag(args, *opts, **kw):
is_flag = kw.get('is_flag', False)
if is_flag:
return any([opt in args for opt in opts])
else:
for opt in opts:
try:
return args[args.index(opt) + 1]
except ValueError:
pass
def call(*a, **kw):
dry_run = kw.pop('dry_run', False)
quiet = kw.pop('quiet', DEBUG_ENABLED)
cwd = kw.get('cwd', os.getcwd())
check_call = kw.pop('check_call', True)
reduct_args = kw.pop('reduct_args', [])
if not quiet:
toprint = a[0]
args = [x.strip('"') for x in a[0].split() if '=' not in x]
for arg in reduct_args:
value = _find_flag(args, arg)
toprint = toprint.replace(value, '{}**reducted**'.format(value[:3]))
logdbg('calling: ' + green(toprint))
logdbg('cwd: ' + green(cwd))
kw.setdefault('shell', True)
if not dry_run:
if check_call:
return subprocess.check_call(*a, **kw)
else:
return subprocess.Popen(*a, **kw).wait()
@contextmanager
def cd(path):
path = expanduser(path)
olddir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(olddir)
def must_makedir(p):
p = expanduser(p)
if not exists(p):
logger.info('created folder %s', p)
os.makedirs(p)
else:
logger.debug('folder %s already exists', p)
def setup_colorlog():
logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
'colored': {
'()': 'colorlog.ColoredFormatter',
'format': "%(log_color)s[%(asctime)s]%(reset)s %(blue)s%(message)s",
'datefmt': '%m/%d/%Y %H:%M:%S',
},
},
'handlers': {
'default': {
'level': 'INFO',
'formatter': 'colored',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'': {
'handlers': ['default'],
'level': 'INFO',
'propagate': True
},
'django.request': {
'handlers': ['default'],
'level': 'WARN',
'propagate': False
},
}
})
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(
logging.WARNING)
def setup_logging(level=logging.INFO):
kw = {
'format': '[%(asctime)s][%(module)s]: %(message)s',
'datefmt': '%m/%d/%Y %H:%M:%S',
'level': level,
'stream': sys.stdout
}
logging.basicConfig(**kw)
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(
logging.WARNING)
def get_process_cmd(pid, env=False):
env = 'e' if env else ''
try:
return subprocess.check_output('ps {} -o command {}'.format(env, pid),
shell=True).strip().splitlines()[1]
# except Exception, e:
# print(e)
except:
return None
def get_match_pids(pattern):
pgrep_output = subprocess.check_output(
'pgrep -f "{}" || true'.format(pattern),
shell=True).strip()
return [int(pid) for pid in pgrep_output.splitlines()]
def ask_for_confirm(msg):
confirm = click.prompt(msg, default='Y')
return confirm.lower() in ('y', 'yes')
def confirm_command_to_run(cmd):
if ask_for_confirm('Run the command: {} ?'.format(green(cmd))):
call(cmd)
else:
sys.exit(1)
def git_current_commit():
return get_command_output('git rev-parse --short HEAD').strip()
def get_command_output(cmd):
shell = not isinstance(cmd, list)
return subprocess.check_output(cmd, shell=shell)
def ask_yes_or_no(msg, prompt='', default=None):
print('\n' + msg + '\n')
while True:
answer = raw_input(prompt + ' [yes/no] ').lower()
if not answer:
continue
if answer not in ('yes', 'no', 'y', 'n'):
continue
if answer in ('yes', 'y'):
return True
else:
return False
def git_branch_exists(branch):
return call('git rev-parse --short --verify {}'.format(branch)) == 0
def to_unicode(s):
if isinstance(s, str):
return s.decode('utf-8')
else:
return s
def to_utf8(s):
if isinstance(s, unicode):
return s.encode('utf-8')
else:
return s
def git_commit_time(refspec):
return int(get_command_output('git log -1 --format="%ct" {}'.format(
refspec)).strip())
def get_seafile_version():
return os.environ['SEAFILE_VERSION']
def get_install_dir():
return join('/opt/seafile/seafile-pro-server-{}'.format(get_seafile_version()))
def get_script(script):
return join(get_install_dir(), script)
_config = None
def get_conf(key, default=None):
key = key.upper()
return os.environ.get(key, default)
def _add_default_context(context):
default_context = {
'current_timestr': datetime.datetime.now().strftime('%m/%d/%Y %H:%M:%S'),
}
for k in default_context:
context.setdefault(k, default_context[k])
def render_template(template, target, context):
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader(dirname(template)))
_add_default_context(context)
content = env.get_template(basename(template)).render(**context)
with open(target, 'w') as fp:
fp.write(content)
def logdbg(msg):
if DEBUG_ENABLED:
msg = '[debug] ' + msg
loginfo(msg)
def loginfo(msg):
msg = '[{}] {}'.format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), green(msg))
eprint(msg)
def cert_has_valid_days(cert, days):
assert exists(cert)
secs = 86400 * int(days)
retcode = call('openssl x509 -checkend {} -noout -in {}'.format(secs, cert), check_call=False)
return retcode == 0
def get_version_stamp_file():
return '/shared/seafile/seafile-data/current_version'
def read_version_stamp(fn=get_version_stamp_file()):
assert exists(fn), 'version stamp file {} does not exist!'.format(fn)
with open(fn, 'r') as fp:
return fp.read().strip()
def update_version_stamp(version, fn=get_version_stamp_file()):
with open(fn, 'w') as fp:
fp.write(version + '\n')
def wait_for_mysql():
while not exists('/var/run/mysqld/mysqld.sock'):
logdbg('waiting for mysql server to be ready')
time.sleep(2)
logdbg('mysql server is ready')
def wait_for_nginx():
while True:
logdbg('waiting for nginx server to be ready')
output = get_command_output('netstat -nltp')
if ':80 ' in output:
logdbg(output)
logdbg('nginx is ready')
return
time.sleep(2)
def replace_file_pattern(fn, pattern, replacement):
with open(fn, 'r') as fp:
content = fp.read()
with open(fn, 'w') as fp:
fp.write(content.replace(pattern, replacement))

View file

@ -0,0 +1,3 @@
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# min hour dayofmonth month dayofweek command
0 0 1 * * root /scripts/ssl.sh {{ ssl_dir }} {{ domain }}

View file

@ -0,0 +1,81 @@
# -*- mode: nginx -*-
# Auto generated at {{ current_timestr }}
{% if https -%}
server {
listen 80;
server_name _ default_server;
rewrite ^ https://{{ domain }}$request_uri? permanent;
}
{% endif -%}
server {
{% if https -%}
listen 443;
ssl on;
ssl_certificate /shared/ssl/{{ domain }}.crt;
ssl_certificate_key /shared/ssl/{{ domain }}.key;
ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS;
# TODO: More SSL security hardening: ssl_session_tickets & ssl_dhparam
# ssl_session_tickets on;
# ssl_session_ticket_key /etc/nginx/sessionticket.key;
# ssl_session_cache shared:SSL:10m;
# ssl_session_timeout 10m;
{% else -%}
listen 80;
{% endif -%}
server_name {{ domain }};
client_max_body_size 10m;
location / {
proxy_pass http://127.0.0.1:8000/;
proxy_read_timeout 310s;
proxy_set_header Host $host;
proxy_set_header Forwarded "for=$remote_addr;proto=$scheme";
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Connection "";
proxy_http_version 1.1;
}
location /seafhttp {
rewrite ^/seafhttp(.*)$ $1 break;
proxy_pass http://127.0.0.1:8082;
client_max_body_size 0;
proxy_connect_timeout 36000s;
proxy_read_timeout 36000s;
}
location /seafdav {
client_max_body_size 0;
fastcgi_pass 127.0.0.1:8080;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
access_log /var/log/nginx/seafdav.access.log;
error_log /var/log/nginx/seafdav.error.log;
}
location /media {
root /opt/seafile/seafile-server-latest/seahub;
}
# For letsencrypt
location /.well-known/acme-challenge/ {
alias /var/www/challenges/;
try_files $uri =404;
}
}

View file

@ -3,7 +3,7 @@ WORKDIR /opt/seafile
RUN mkdir -p /etc/my_init.d RUN mkdir -p /etc/my_init.d
ENV SEAFILE_VERSION=6.2.5 ENV SEAFILE_VERSION=6.2.5 SEAFILE_SERVER=seafile-pro-server
RUN mkdir -p /opt/seafile/ && \ RUN mkdir -p /opt/seafile/ && \
curl -sSL -o - https://download.seadrive.org/seafile-server_${SEAFILE_VERSION}_x86-64.tar.gz \ curl -sSL -o - https://download.seadrive.org/seafile-server_${SEAFILE_VERSION}_x86-64.tar.gz \

View file

@ -1,382 +0,0 @@
#coding: UTF-8
'''This script would check if there is admin, and prompt the user to create a new one if non exist'''
import json
import sys
import os
import time
import re
import shutil
import glob
import subprocess
import hashlib
import getpass
import uuid
import warnings
from ConfigParser import ConfigParser
try:
import readline # pylint: disable=W0611
except ImportError:
pass
SERVER_MANUAL_HTTP = 'https://github.com/haiwen/seafile/wiki'
class Utils(object):
'''Groups all helper functions here'''
@staticmethod
def welcome():
'''Show welcome message'''
welcome_msg = '''\
-----------------------------------------------------------------
This script will guide you to setup your seafile server using MySQL.
Make sure you have read seafile server manual at
%s
Press ENTER to continue
-----------------------------------------------------------------''' % SERVER_MANUAL_HTTP
print welcome_msg
raw_input()
@staticmethod
def highlight(content):
'''Add ANSI color to content to get it highlighted on terminal'''
return '\x1b[33m%s\x1b[m' % content
@staticmethod
def info(msg):
print msg
@staticmethod
def error(msg):
'''Print error and exit'''
print
print 'Error: ' + msg
sys.exit(1)
@staticmethod
def run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):
'''Run a program and wait it to finish, and return its exit code. The
standard output of this program is supressed.
'''
with open(os.devnull, 'w') as devnull:
if suppress_stdout:
stdout = devnull
else:
stdout = sys.stdout
if suppress_stderr:
stderr = devnull
else:
stderr = sys.stderr
proc = subprocess.Popen(argv,
cwd=cwd,
stdout=stdout,
stderr=stderr,
env=env)
return proc.wait()
@staticmethod
def run(cmdline, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):
'''Like run_argv but specify a command line string instead of argv'''
with open(os.devnull, 'w') as devnull:
if suppress_stdout:
stdout = devnull
else:
stdout = sys.stdout
if suppress_stderr:
stderr = devnull
else:
stderr = sys.stderr
proc = subprocess.Popen(cmdline,
cwd=cwd,
stdout=stdout,
stderr=stderr,
env=env,
shell=True)
return proc.wait()
@staticmethod
def prepend_env_value(name, value, env=None, seperator=':'):
'''prepend a new value to a list'''
if env is None:
env = os.environ
try:
current_value = env[name]
except KeyError:
current_value = ''
new_value = value
if current_value:
new_value += seperator + current_value
env[name] = new_value
@staticmethod
def must_mkdir(path):
'''Create a directory, exit on failure'''
try:
os.mkdir(path)
except OSError, e:
Utils.error('failed to create directory %s:%s' % (path, e))
@staticmethod
def must_copy(src, dst):
'''Copy src to dst, exit on failure'''
try:
shutil.copy(src, dst)
except Exception, e:
Utils.error('failed to copy %s to %s: %s' % (src, dst, e))
@staticmethod
def find_in_path(prog):
if 'win32' in sys.platform:
sep = ';'
else:
sep = ':'
dirs = os.environ['PATH'].split(sep)
for d in dirs:
d = d.strip()
if d == '':
continue
path = os.path.join(d, prog)
if os.path.exists(path):
return path
return None
@staticmethod
def get_python_executable():
'''Return the python executable. This should be the PYTHON environment
variable which is set in setup-seafile-mysql.sh
'''
return os.environ['PYTHON']
@staticmethod
def read_config(fn):
'''Return a case sensitive ConfigParser by reading the file "fn"'''
cp = ConfigParser()
cp.optionxform = str
cp.read(fn)
return cp
@staticmethod
def write_config(cp, fn):
'''Return a case sensitive ConfigParser by reading the file "fn"'''
with open(fn, 'w') as fp:
cp.write(fp)
@staticmethod
def ask_question(desc,
key=None,
note=None,
default=None,
validate=None,
yes_or_no=False,
password=False):
'''Ask a question, return the answer.
@desc description, e.g. "What is the port of ccnet?"
@key a name to represent the target of the question, e.g. "port for
ccnet server"
@note additional information for the question, e.g. "Must be a valid
port number"
@default the default value of the question. If the default value is
not None, when the user enter nothing and press [ENTER], the default
value would be returned
@validate a function that takes the user input as the only parameter
and validate it. It should return a validated value, or throws an
"InvalidAnswer" exception if the input is not valid.
@yes_or_no If true, the user must answer "yes" or "no", and a boolean
value would be returned
@password If true, the user input would not be echoed to the
console
'''
assert key or yes_or_no
# Format description
print
if note:
desc += '\n' + note
desc += '\n'
if yes_or_no:
desc += '[ yes or no ]'
else:
if default:
desc += '[ default "%s" ]' % default
else:
desc += '[ %s ]' % key
desc += ' '
while True:
# prompt for user input
if password:
answer = getpass.getpass(desc).strip()
else:
answer = raw_input(desc).strip()
# No user input: use default
if not answer:
if default:
answer = default
else:
continue
# Have user input: validate answer
if yes_or_no:
if answer not in ['yes', 'no']:
print Utils.highlight('\nPlease answer yes or no\n')
continue
else:
return answer == 'yes'
else:
if validate:
try:
return validate(answer)
except InvalidAnswer, e:
print Utils.highlight('\n%s\n' % e)
continue
else:
return answer
@staticmethod
def validate_port(port):
try:
port = int(port)
except ValueError:
raise InvalidAnswer('%s is not a valid port' % Utils.highlight(port))
if port <= 0 or port > 65535:
raise InvalidAnswer('%s is not a valid port' % Utils.highlight(port))
return port
class InvalidAnswer(Exception):
def __init__(self, msg):
Exception.__init__(self)
self.msg = msg
def __str__(self):
return self.msg
### END of Utils
####################
class RPC(object):
def __init__(self):
import ccnet
ccnet_dir = os.environ['CCNET_CONF_DIR']
central_config_dir = os.environ['SEAFILE_CENTRAL_CONF_DIR']
self.rpc_client = ccnet.CcnetThreadedRpcClient(
ccnet.ClientPool(ccnet_dir, central_config_dir=central_config_dir))
def get_db_email_users(self):
return self.rpc_client.get_emailusers('DB', 0, 1)
def create_admin(self, email, user):
return self.rpc_client.add_emailuser(email, user, 1, 1)
def need_create_admin():
users = rpc.get_db_email_users()
return len(users) == 0
def create_admin(email, passwd):
if rpc.create_admin(email, passwd) < 0:
raise Exception('failed to create admin')
else:
print '\n\n'
print '----------------------------------------'
print 'Successfully created seafile admin'
print '----------------------------------------'
print '\n\n'
def ask_admin_email():
print
print '----------------------------------------'
print 'It\'s the first time you start the seafile server. Now let\'s create the admin account'
print '----------------------------------------'
def validate(email):
# whitespace is not allowed
if re.match(r'[\s]', email):
raise InvalidAnswer('%s is not a valid email address' % Utils.highlight(email))
# must be a valid email address
if not re.match(r'^.+@.*\..+$', email):
raise InvalidAnswer('%s is not a valid email address' % Utils.highlight(email))
return email
key = 'admin email'
question = 'What is the ' + Utils.highlight('email') + ' for the admin account?'
return Utils.ask_question(question,
key=key,
validate=validate)
def ask_admin_password():
def validate(password):
key = 'admin password again'
question = 'Enter the ' + Utils.highlight('password again:')
password_again = Utils.ask_question(question,
key=key,
password=True)
if password_again != password:
raise InvalidAnswer('password mismatch')
return password
key = 'admin password'
question = 'What is the ' + Utils.highlight('password') + ' for the admin account?'
return Utils.ask_question(question,
key=key,
password=True,
validate=validate)
rpc = RPC()
def main():
if not need_create_admin():
return
password_file = os.path.join(os.environ['SEAFILE_CENTRAL_CONF_DIR'], 'admin.txt')
if os.path.exists(password_file):
with open(password_file, 'r') as fp:
pwinfo = json.load(fp)
email = pwinfo['email']
passwd = pwinfo['password']
os.unlink(password_file)
else:
email = ask_admin_email()
passwd = ask_admin_password()
create_admin(email, passwd)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print '\n\n\n'
print Utils.highlight('Aborted.')
print
sys.exit(1)
except Exception, e:
print
print Utils.highlight('Error happened during creating seafile admin.')
print

View file

@ -1,18 +0,0 @@
# -*- mode: dockerfile -*-
# This is a jinja2 template to generate the real Dockerfile to build the local image
FROM seafileltd/seafile:{{ seafile_version }}
CMD ["/sbin/my_init", "--", "/scripts/start.py"]
# shared/ are ignored in .dockerignore
ADD . /app
RUN cp -rp /app/scripts /scripts && \
cp -rp /app/bootstrap /bootstrap && \
{%- if seafile_version <= '6.0.6' %}
cp /app/scripts/tmp/check_init_admin.py /opt/seafile/seafile-server-{{ seafile_version }}/check_init_admin.py && \
{%- endif %}
{%- if https %}
cp /app/bootstrap/generated/letsencrypt.cron /etc/cron.d/letsencrypt.cron && \
{%- endif %}
cp /app/bootstrap/generated/seafile.nginx.conf /etc/nginx/sites-enabled/seafile.nginx.conf