mirror of
https://github.com/ggogel/seafile-containerized.git
synced 2024-11-16 17:05:32 +00:00
commit
7bdb0e10a2
51
MAINT.md
51
MAINT.md
|
@ -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
|
||||||
```
|
|
||||||
* 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
|
|
||||||
git tag v6.0.7
|
|
||||||
git push origin v6.0.7
|
|
||||||
```
|
|
||||||
* Ensure the new image is available in https://hub.docker.com/r/seafileltd/seafile/tags/
|
|
||||||
* Now update the master branch.
|
|
||||||
```
|
|
||||||
git push origin unstable:master
|
|
||||||
```
|
|
||||||
* Delete the unstable branch
|
|
||||||
```sh
|
|
||||||
git push origin :unstable
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
```sh
|
||||||
|
git tag v6.0.7
|
||||||
|
git push origin v6.0.7
|
||||||
|
```
|
||||||
|
* Ensure the new image is available in https://hub.docker.com/r/seafileltd/seafile/tags/
|
||||||
|
|
||||||
|
* Pro
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
```sh
|
||||||
|
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
147
README.pro.md
Normal 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
|
||||||
|
```
|
40
ci/ci.sh
40
ci/ci.sh
|
@ -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
3
ci/install_deps.sh
Executable 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
19
ci/publish-base.sh
Executable 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
|
||||||
|
)
|
|
@ -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
24
ci/publish-pro-base.sh
Executable 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
34
ci/publish-pro-image.sh
Executable 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
64
ci/upload.py
Executable 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
48
ci/validate_file.py
Executable 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
|
|
@ -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
12
image/pro_base/Dockerfile
Normal 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
|
18
image/pro_seafile/Dockerfile
Normal file
18
image/pro_seafile/Dockerfile
Normal 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"]
|
60
image/pro_seafile/create_data_links.sh
Executable file
60
image/pro_seafile/create_data_links.sh
Executable 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
|
156
image/pro_seafile/scripts/bootstrap.py
Executable file
156
image/pro_seafile/scripts/bootstrap.py
Executable 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
30
image/pro_seafile/scripts/gc.sh
Executable 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
|
46
image/pro_seafile/scripts/ssl.sh
Executable file
46
image/pro_seafile/scripts/ssl.sh
Executable 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."
|
85
image/pro_seafile/scripts/start.py
Executable file
85
image/pro_seafile/scripts/start.py
Executable 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()
|
82
image/pro_seafile/scripts/upgrade.py
Executable file
82
image/pro_seafile/scripts/upgrade.py
Executable 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()
|
287
image/pro_seafile/scripts/utils/__init__.py
Normal file
287
image/pro_seafile/scripts/utils/__init__.py
Normal 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))
|
3
image/pro_seafile/templates/letsencrypt.cron.template
Normal file
3
image/pro_seafile/templates/letsencrypt.cron.template
Normal 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 }}
|
81
image/pro_seafile/templates/seafile.nginx.conf.template
Normal file
81
image/pro_seafile/templates/seafile.nginx.conf.template
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 \
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
Loading…
Reference in a new issue