Compare commits
No commits in common. "master" and "0.0.8" have entirely different histories.
38
README.md
38
README.md
@ -1,5 +1,5 @@
|
|||||||
# docker-seafile-client
|
# docker-seafile-client
|
||||||
Docker image for [Seafile CLI client](https://help.seafile.com/syncing_client/linux-cli/).
|
Docker image for Seafile terminal client.
|
||||||
|
|
||||||
### Docker-compose example:
|
### Docker-compose example:
|
||||||
```yaml
|
```yaml
|
||||||
@ -25,20 +25,10 @@ volumes:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Environment variables:
|
### Environment variables:
|
||||||
- `LIBRARY_ID` - library to sync, ID or name. Multiple libraries could be
|
- `LIBRARY_ID` - library to sync, ID or name. Multiple libraries could be separated by colon `:`.
|
||||||
separated by colon `:`.
|
- `SERVER_HOST` - hostname of your Seafile server, eg: `seafile.example.com`. If you're using non-standard port, you can specify it here, eg: `seafile.example.com:8080`.
|
||||||
- `SERVER_HOST` - hostname of your Seafile server, eg: _seafile.example.com_.
|
|
||||||
If you're using non-standard port, you can specify it here,
|
|
||||||
eg: _seafile.example.com:8080_.
|
|
||||||
- `USERNAME`/ `PASSWORD` - credentials to access Seafile server.
|
- `USERNAME`/ `PASSWORD` - credentials to access Seafile server.
|
||||||
- `SEAFILE_UID` / `SEAFILE_GID` - UID/GID of user inside container. You can
|
- `SEAFILE_UID` / `SEAFILE_GID` - UID/GID of user inside container. You can use it to set permissions on synced files. Default values are `1000`/`1000`.
|
||||||
use it to set permissions on synced files. Default values are _1000_ / _1000_.
|
|
||||||
- `DELETE_CONFIRM_THRESHOLD` - represents the number of files that require
|
|
||||||
confirmation before being deleted simultaneously. Default value is _500_.
|
|
||||||
- `DISABLE_VERIFY_CERTIFICATE` - set to _true_ to disable server's certificate
|
|
||||||
verification. Default value is _false_.
|
|
||||||
- `UPLOAD_LIMIT` / `DOWNLOAD_LIMIT` - upload/download speed limit in B/s
|
|
||||||
(bytes per second). Default values are _0_ (unlimited).
|
|
||||||
|
|
||||||
### Volumes:
|
### Volumes:
|
||||||
- `/dsc/seafile-data` Seafile client data directory (sync status, etc).
|
- `/dsc/seafile-data` Seafile client data directory (sync status, etc).
|
||||||
@ -46,23 +36,11 @@ volumes:
|
|||||||
|
|
||||||
|
|
||||||
### Some notes
|
### Some notes
|
||||||
`LIBRARY_ID` could be library ID or library name. Library ID is a 36-character
|
`LIBRARY_ID` could be library ID or library name. Library ID is a 36-character string, which is a part of URI when you open library in webUI. Library name is a name you gave to library when you created it.
|
||||||
string, which is a part of URI when you open library in webUI. Library name is
|
|
||||||
a name you gave to library when you created it.
|
|
||||||
|
|
||||||
Libraries will be synced in subdirectories of `/dsc/seafile` directory inside
|
Libraries will be synced in subdirectories of `/dsc/seafile` directory inside container. You can mount it to host directory to access files.
|
||||||
container. You can mount it to host directory to access files.
|
|
||||||
|
|
||||||
`hostname` parameter is optional, but it's recommended to set it to some unique
|
`hostname` parameter is optional, but it's recommended to set it to some unique value, it will be shown in Seafile webUI as client name (`terminal-dsc` in given example).
|
||||||
value, it will be shown in Seafile webUI as client name (`terminal-dsc` in
|
|
||||||
given example).
|
|
||||||
|
|
||||||
`sync-data` volume is optional too, but it's recommended to use it. Otherwise,
|
`sync-data` volume is optional too, but it's recommended to use it. Otherwise, sync status will be lost when container is recreated.
|
||||||
sync status will be lost when container is recreated.
|
|
||||||
|
|
||||||
At the moment there is no suitable way to confirm deletion of large number of
|
|
||||||
files. So, if you're going to delete a lot of files, you should set
|
|
||||||
`DELETE_CONFIRM_THRESHOLD` to some larger value.
|
|
||||||
|
|
||||||
### Links
|
|
||||||
- [Official Seafile CLI client documentation](https://help.seafile.com/syncing_client/linux-cli/)
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import argparse
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -91,19 +90,6 @@ class SeafileClient:
|
|||||||
|
|
||||||
_lg.info("Seafile daemon is ready")
|
_lg.info("Seafile daemon is ready")
|
||||||
|
|
||||||
def stop_daemon(self):
|
|
||||||
cmd = "seaf-cli stop"
|
|
||||||
_lg.info("Stopping seafile daemon: %s", cmd)
|
|
||||||
subprocess.run(self.__gen_cmd(cmd))
|
|
||||||
_lg.info("Waiting for seafile daemon to stop")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if not self.daemon_ready:
|
|
||||||
break
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
_lg.info("Seafile daemon is stopped")
|
|
||||||
|
|
||||||
def get_library_id(self, library) -> Optional[str]:
|
def get_library_id(self, library) -> Optional[str]:
|
||||||
for lib_id, lib_name in self.remote_libraries.items():
|
for lib_id, lib_name in self.remote_libraries.items():
|
||||||
if library in (lib_id, lib_name):
|
if library in (lib_id, lib_name):
|
||||||
@ -121,7 +107,7 @@ class SeafileClient:
|
|||||||
"-s", self.url,
|
"-s", self.url,
|
||||||
"-d", lib_dir,
|
"-d", lib_dir,
|
||||||
"-u", self.user,
|
"-u", self.user,
|
||||||
"-p", '"' + self.password + '"',
|
"-p", self.password,
|
||||||
]
|
]
|
||||||
_lg.info(
|
_lg.info(
|
||||||
"Syncing library %s: %s", lib_name,
|
"Syncing library %s: %s", lib_name,
|
||||||
@ -166,37 +152,3 @@ class SeafileClient:
|
|||||||
lib_name, lib_id, lib_path = line.rsplit(maxsplit=3)
|
lib_name, lib_id, lib_path = line.rsplit(maxsplit=3)
|
||||||
local_libs.add(lib_id)
|
local_libs.add(lib_id)
|
||||||
return local_libs
|
return local_libs
|
||||||
|
|
||||||
def configure(self, args: argparse.Namespace, check_for_daemon: bool = True):
|
|
||||||
need_restart = False
|
|
||||||
# Options can be fetched or set only when daemon is running
|
|
||||||
if check_for_daemon and not self.daemon_ready:
|
|
||||||
self.start_daemon()
|
|
||||||
|
|
||||||
for key, value in args.__dict__.items():
|
|
||||||
if key not in const.AVAILABLE_SEAFCLI_OPTIONS:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# check current value
|
|
||||||
cmd = f"seaf-cli config -k {key}"
|
|
||||||
_lg.info("Checking seafile client option: %s", cmd)
|
|
||||||
proc = subprocess.run(self.__gen_cmd(cmd), stdout=subprocess.PIPE)
|
|
||||||
# stdout looks like "option = value"
|
|
||||||
cur_value = proc.stdout.decode().strip()
|
|
||||||
try:
|
|
||||||
cur_value = cur_value.split(sep="=")[1].strip()
|
|
||||||
except IndexError:
|
|
||||||
cur_value = None
|
|
||||||
if cur_value == str(value):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# set new value
|
|
||||||
cmd = f"seaf-cli config -k {key} -v {value}"
|
|
||||||
_lg.info("Setting seafile client option: %s", cmd)
|
|
||||||
subprocess.run(self.__gen_cmd(cmd))
|
|
||||||
need_restart = True
|
|
||||||
|
|
||||||
if need_restart:
|
|
||||||
_lg.info("Restarting seafile daemon")
|
|
||||||
self.stop_daemon()
|
|
||||||
self.start_daemon()
|
|
||||||
|
|||||||
@ -3,10 +3,3 @@ DEFAULT_LIBS_DIR = "/dsc/seafile"
|
|||||||
DEPRECATED_LIBS_DIR = "/data"
|
DEPRECATED_LIBS_DIR = "/data"
|
||||||
DEFAULT_USERNAME = "seafile"
|
DEFAULT_USERNAME = "seafile"
|
||||||
STATUS_POLL_PERIOD = 1
|
STATUS_POLL_PERIOD = 1
|
||||||
|
|
||||||
AVAILABLE_SEAFCLI_OPTIONS = {
|
|
||||||
"delete_confirm_threshold",
|
|
||||||
"disable_verify_certificate",
|
|
||||||
"upload_limit",
|
|
||||||
"download_limit",
|
|
||||||
}
|
|
||||||
|
|||||||
@ -43,6 +43,6 @@ def create_dir(dir_path: str):
|
|||||||
def hide_password(cmd: list, password: str) -> list:
|
def hide_password(cmd: list, password: str) -> list:
|
||||||
cmd = cmd.copy()
|
cmd = cmd.copy()
|
||||||
for i, arg in enumerate(cmd):
|
for i, arg in enumerate(cmd):
|
||||||
if password in arg:
|
if arg == password:
|
||||||
cmd[i] = arg.replace(password, "********")
|
cmd[i] = '********'
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
45
start.py
45
start.py
@ -16,54 +16,28 @@ def main():
|
|||||||
logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
|
logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("-s", "--server")
|
parser.add_argument("--uid", default=os.getenv("SEAFILE_UID", default=1000), type=int)
|
||||||
parser.add_argument("-u", "--username")
|
parser.add_argument("--gid", default=os.getenv("SEAFILE_GID", default=1000), type=int)
|
||||||
parser.add_argument("-p", "--password")
|
parser.add_argument("--host", default=os.getenv("SERVER_HOST"))
|
||||||
parser.add_argument("-l", "--libraries")
|
parser.add_argument("--username", default=os.getenv("USERNAME"))
|
||||||
parser.add_argument("--uid", type=int)
|
parser.add_argument("--password", default=os.getenv("PASSWORD"))
|
||||||
parser.add_argument("--gid", type=int)
|
parser.add_argument("--libs", default=os.getenv("LIBRARY_ID"))
|
||||||
parser.add_argument("--upload-limit", type=int, default=0)
|
|
||||||
parser.add_argument("--download-limit", type=int, default=0)
|
|
||||||
parser.add_argument("--disable-verify-certificate", action="store_true")
|
|
||||||
parser.add_argument("--delete-confirm-threshold", type=int, default=500)
|
|
||||||
|
|
||||||
parser.set_defaults(
|
|
||||||
server=os.getenv("SERVER_HOST"),
|
|
||||||
username=os.getenv("USERNAME"),
|
|
||||||
password=os.getenv("PASSWORD"),
|
|
||||||
libraries=os.getenv("LIBRARY_ID"),
|
|
||||||
uid=os.getenv("SEAFILE_UID", default=1000),
|
|
||||||
gid=os.getenv("SEAFILE_GID", default=1000),
|
|
||||||
upload_limit=os.getenv("UPLOAD_LIMIT"),
|
|
||||||
download_limit=os.getenv("DOWNLOAD_LIMIT"),
|
|
||||||
disable_verify_certificate=os.getenv("DISABLE_VERIFY_CERTIFICATE") in ("true", "1", "True"),
|
|
||||||
delete_confirm_threshold=os.getenv("DELETE_CONFIRM_THRESHOLD"),
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if not args.server:
|
|
||||||
parser.error("Seafile server is not specified")
|
|
||||||
if not args.username:
|
|
||||||
parser.error("username is not specified")
|
|
||||||
if not args.password:
|
|
||||||
parser.error("password is not specified")
|
|
||||||
if not args.libraries:
|
|
||||||
parser.error("library is not specified")
|
|
||||||
|
|
||||||
setup_uid(args.uid, args.gid)
|
setup_uid(args.uid, args.gid)
|
||||||
create_dir(const.DEFAULT_APP_DIR)
|
create_dir(const.DEFAULT_APP_DIR)
|
||||||
|
|
||||||
client = SeafileClient(args.server, args.username, args.password, const.DEFAULT_APP_DIR)
|
client = SeafileClient(args.host, args.username, args.password, const.DEFAULT_APP_DIR)
|
||||||
client.init_config()
|
client.init_config()
|
||||||
client.start_daemon()
|
client.start_daemon()
|
||||||
client.configure(args, check_for_daemon=False)
|
|
||||||
|
|
||||||
libs_to_sync = set()
|
libs_to_sync = set()
|
||||||
for arg_lib in args.libraries.split(sep=":"):
|
for arg_lib in args.libs.split(sep=":"):
|
||||||
lib_id = client.get_library_id(arg_lib)
|
lib_id = client.get_library_id(arg_lib)
|
||||||
if lib_id:
|
if lib_id:
|
||||||
libs_to_sync.add(lib_id)
|
libs_to_sync.add(lib_id)
|
||||||
else:
|
else:
|
||||||
_lg.warning("Library %s is not found on server %s", arg_lib, args.server)
|
_lg.warning("Library %s is not found on server %s", arg_lib, args.host)
|
||||||
|
|
||||||
# don't start to sync libraries already in sync
|
# don't start to sync libraries already in sync
|
||||||
libs_to_sync -= client.get_local_libraries()
|
libs_to_sync -= client.get_local_libraries()
|
||||||
@ -81,7 +55,6 @@ def main():
|
|||||||
client.sync_lib(lib_id, libs_dir)
|
client.sync_lib(lib_id, libs_dir)
|
||||||
client.watch_status()
|
client.watch_status()
|
||||||
|
|
||||||
client.stop_daemon()
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user