Compare commits

..

11 Commits

Author SHA1 Message Date
b03d7bb1a3 Fix concealing a password in logs 2023-10-20 18:07:54 -07:00
ffc972662e
Merge pull request #6 from snegov/5-add-delete-confirm-threshold-option
Add DELETE_CONFIRM_THRESHOLD option
2023-10-20 17:39:03 -07:00
452c29ac95 Add DELETE_CONFIRM_THRESHOLD option 2023-10-20 17:32:34 -07:00
ca40cce9e9
Merge pull request #4 from subvillion/patch-1
fix: client.py sync_lib password escaping
2023-10-18 09:21:00 -07:00
subvillion
9305c636e1
fix: client.py sync_lib password escaping
Please add some escaping in password env, I have password similar "xxxx&zzzz", and "&" broke syncing:

```
2023-10-16 12:50:04,655 Listing local libraries: seaf-cli list
2023-10-16 12:50:05,108 Syncing library btsync: seaf-cli sync -l 3XX2-XXX-2eXXea -s https://xxxxxxxx.xx -d /dsc/seafile/btsync -u xxx@xx.xx -p ********
-bash: line 1: zzzz: command not found
Traceback (most recent call last):
  File "/usr/bin/seaf-cli", line 1023, in <module>
    main()
  File "/usr/bin/seaf-cli", line 1019, in main
    args.func(args)
  File "/usr/bin/seaf-cli", line 675, in seaf_sync
    token = get_token(url, username, password, tfa, conf_dir)
```
2023-10-16 13:11:26 +03:00
7f39e0f271 Initialize seafile client during container start 2023-09-16 01:27:46 -07:00
9f6c417147 Use debian as base docker image 2023-09-15 20:00:04 -07:00
639265f718
Merge pull request #3 from snegov/dependabot/pip/requests-2.31.0
Bump requests from 2.27.1 to 2.31.0
2023-09-14 16:24:59 -07:00
6af5eb1b26 Wait for seafile daemon to start 2023-09-14 16:22:02 -07:00
dependabot[bot]
43237dc1f3
Bump requests from 2.27.1 to 2.31.0
Bumps [requests](https://github.com/psf/requests) from 2.27.1 to 2.31.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.27.1...v2.31.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-23 03:15:40 +00:00
724166b4a4 Support library names in configuration 2022-01-30 18:27:37 +03:00
14 changed files with 408 additions and 190 deletions

View File

@ -1,25 +1,36 @@
FROM python:3-slim
FROM debian:bookworm-slim
RUN apt-get update && apt-get install gnupg curl -y && rm -rf /var/lib/apt/lists/*
# Install seafile client
RUN apt-get update && \
apt-get install gnupg curl python3.11-venv -y && \
rm -rf /var/lib/apt/lists/*
RUN curl https://linux-clients.seafile.com/seafile.asc | apt-key add - && \
echo 'deb [arch=amd64] https://linux-clients.seafile.com/seafile-deb/buster/ stable main' > /etc/apt/sources.list.d/seafile.list && \
echo 'deb [arch=amd64] https://linux-clients.seafile.com/seafile-deb/bookworm/ stable main' \
> /etc/apt/sources.list.d/seafile.list && \
apt-get update -y && \
apt-get install -y seafile-cli procps grep && \
apt-get install -y seafile-cli && \
rm -rf /var/lib/apt/lists/*
WORKDIR /seafile-client
# Use virtual environment
ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install app requirements
WORKDIR /dsc
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY seafile_client ./seafile_client/
# Copy app
COPY dsc ./dsc/
COPY start.py ./start.py
RUN chmod +x /seafile-client/start.py && \
useradd -U -d /seafile-client -s /bin/bash seafile && \
# Create seafile user and init seafile client
RUN chmod +x /dsc/start.py && \
useradd -U -d /dsc -s /bin/bash seafile && \
usermod -G users seafile && \
chown seafile:seafile -R /seafile-client && \
su - seafile -c "seaf-cli init -d /seafile-client"
VOLUME /seafile-client/seafile-data
mkdir -p /dsc/seafile-data && \
chown seafile:seafile -R /dsc
VOLUME /dsc/seafile-data
CMD ["./start.py"]

View File

@ -1,7 +1,7 @@
MIT License
Copyright (c) 2018 Robin Grönberg
Copyright (c) 2019 Maks Snegov
Copyright (c) 2019-2023 Maks Snegov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,42 +1,68 @@
# docker-seafile-client
Runs a seafile client in docker with possibility to sync seafile repositories.
Docker image for [Seafile CLI client](https://help.seafile.com/syncing_client/linux-cli/).
## Docker-compose example:
### Docker-compose example:
```yaml
version: '3'
services:
seafile-client:
restart: always
image: snegov/seafile-client
environment:
- LIBRARY_ID=<your-library-id-here>
- SERVER_HOST=<server-host>
- USERNAME=<username>
- PASSWORD=<password>
- SEAFILE_UID=<your_uid>
- SEAFILE_GID=<your_gid>
hostname: docker-seafile-client
- LIBRARY_ID="79867cbf-2944-488d-9105-852463ecdf9e:my_library"
- SERVER_HOST=seafile.example.com
- USERNAME=user
- PASSWORD=password
- SEAFILE_UID=1000
- SEAFILE_GID=100
hostname: dsc
volumes:
- seafile-data:/seafile-client/seafile-data
- <host-volume-path>:/data
- /home/johndow/seafile:/dsc/seafile
- sync-data:/dsc/seafile-data
container_name: seafile-client
volumes:
seafile-data:
sync-data:
```
Library id could be found from "My Libraries" page in Seafile webUI - link to each library contains library ID in it.
### Environment variables:
- `LIBRARY_ID` - library to sync, ID or name. Multiple libraries could be
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_.
- `USERNAME` / `PASSWORD` - credentials to access Seafile server.
- `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_.
- `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).
Inside container libraries' content will be put in `/data` directory, so map your host directory to it.
### Volumes:
- `/dsc/seafile-data` Seafile client data directory (sync status, etc).
- `/dsc/seafile` Seafile libraries content.
`hostname` parameter in docker-compose will set client name in Seafile's "Linked devices" admin page. Resulting name will be prefixed by "terminal-".
Also you could check [docker-compose example](docker-compose.example.yml).
### Some notes
`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.
## Environment variables:
- `LIBRARY_ID=<your-library-id-here>` ID of library to sync; multiple libraries could be separated by colon `:`.
- `SERVER_HOST=<server-host>` Hostname of your seafile server, eg: `seafile.example.com`. If you're using non-standart port, specify it here, eg: `seafile.example.com:8080`.
- `USERNAME=<username>` Seafile account username.
- `PASSWORD=<password>` Seafile account password.
- `SEAFILE_UID=<uid>` Downloaded files will have this uid.
- `SEAFILE_GID=<gid>` Downloaded files will have this gid.
Libraries will be synced in subdirectories of `/dsc/seafile` directory inside
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
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 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/)

View File

@ -1,21 +0,0 @@
version: '3'
services:
seafile-client:
restart: always
image: snegov/seafile-client
environment:
- LIBRARY_ID="79867cbf-2944-488d-9105-859463ecdf9e:8078e3ff-b2a0-450a-b4dd-c1ed9ef18294"
- SERVER_HOST=seafile.example.com
- USERNAME=user
- PASSWORD=password
- SEAFILE_UID=1000
- SEAFILE_GID=100
hostname: docker-seafcli
volumes:
- seafile-data:/seafile-client/seafile-data
- /home/johndow/seafile:/data
volumes:
seafile-data:

1
dsc/__init__.py Normal file
View File

@ -0,0 +1 @@
from .client import SeafileClient, const

202
dsc/client.py Normal file
View File

@ -0,0 +1,202 @@
import argparse
import logging
import os
import subprocess
import time
from typing import Optional
from urllib.parse import urlparse
from cached_property import cached_property_with_ttl
import requests
from dsc import const
from dsc.misc import create_dir, hide_password
_lg = logging.getLogger(__name__)
class SeafileClient:
def __init__(self,
host: str,
user: str,
passwd: str,
app_dir: str = const.DEFAULT_APP_DIR):
up = urlparse(requests.get(f"http://{host}").url)
self.url = f"{up.scheme}://{up.netloc}"
self.user = user
self.password = passwd
self.app_dir = os.path.abspath(app_dir)
self.__token = None
def __str__(self):
return f"SeafileClient({self.user}@{self.url})"
def __gen_cmd(self, cmd: str) -> list:
return ["su", "-", const.DEFAULT_USERNAME, "-c", cmd]
@property
def token(self):
if self.__token is None:
url = f"{self.url}/api2/auth-token/"
_lg.info("Fetching token: %s", url)
r = requests.post(url, data={"username": self.user, "password": self.password})
if r.status_code != 200:
raise RuntimeError(f"Can't get token: {r.text}")
self.__token = r.json()["token"]
return self.__token
@cached_property_with_ttl(ttl=60)
def remote_libraries(self) -> dict:
url = f"{self.url}/api2/repos/"
_lg.info("Fetching remote libraries: %s", url)
auth_header = {"Authorization": f"Token {self.token}"}
r = requests.get(url, headers=auth_header)
if r.status_code != 200:
raise RuntimeError(r.text)
r_libs = {lib["id"]: lib["name"] for lib in r.json()}
return r_libs
@property
def config_initialized(self) -> bool:
return os.path.isdir(os.path.join(self.app_dir, ".ccnet"))
@property
def daemon_ready(self) -> bool:
cmd = "seaf-cli status"
_lg.info("Checking seafile daemon status: %s", cmd)
proc = subprocess.run(
self.__gen_cmd(cmd),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return proc.returncode == 0
def init_config(self):
if self.config_initialized:
return
cmd = "seaf-cli init -d %s" % self.app_dir
_lg.info("Initializing seafile config: %s", cmd)
subprocess.run(self.__gen_cmd(cmd))
def start_daemon(self):
cmd = "seaf-cli start"
_lg.info("Starting seafile daemon: %s", cmd)
subprocess.run(self.__gen_cmd(cmd))
_lg.info("Waiting for seafile daemon to start")
while True:
if self.daemon_ready:
break
time.sleep(5)
_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]:
for lib_id, lib_name in self.remote_libraries.items():
if library in (lib_id, lib_name):
return lib_id
return None
def sync_lib(self, lib_id: str, parent_dir: str = const.DEFAULT_LIBS_DIR):
lib_name = self.remote_libraries[lib_id]
lib_dir = os.path.join(parent_dir, lib_name.replace(" ", "_"))
create_dir(lib_dir)
cmd = [
"seaf-cli",
"sync",
"-l", lib_id,
"-s", self.url,
"-d", lib_dir,
"-u", self.user,
"-p", '"' + self.password + '"',
]
_lg.info(
"Syncing library %s: %s", lib_name,
" ".join(hide_password(cmd, self.password)),
)
subprocess.run(self.__gen_cmd(" ".join(cmd)))
def get_status(self):
cmd = "seaf-cli status"
_lg.debug("Fetching seafile client status: %s", cmd)
out = subprocess.check_output(self.__gen_cmd(cmd))
out = out.decode().splitlines()
statuses = dict()
for line in out:
if line.startswith("#") or not line.strip():
continue
lib, status = line.split(sep="\t", maxsplit=1)
lib = lib.strip()
status = " ".join(status.split())
statuses[lib] = status
return statuses
def watch_status(self):
prev_status = dict()
while True:
time.sleep(const.STATUS_POLL_PERIOD)
cur_status = self.get_status()
for folder, state in cur_status.items():
if state != prev_status.get(folder):
logging.info("Library %s:\t%s", folder, state)
prev_status[folder] = cur_status[folder]
def get_local_libraries(self) -> set:
cmd = "seaf-cli list"
_lg.info("Listing local libraries: %s", cmd)
out = subprocess.check_output(self.__gen_cmd(cmd))
out = out.decode().splitlines()[1:] # first line is a header
local_libs = set()
for line in out:
lib_name, lib_id, lib_path = line.rsplit(maxsplit=3)
local_libs.add(lib_id)
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()

12
dsc/const.py Normal file
View File

@ -0,0 +1,12 @@
DEFAULT_APP_DIR = "/dsc"
DEFAULT_LIBS_DIR = "/dsc/seafile"
DEPRECATED_LIBS_DIR = "/data"
DEFAULT_USERNAME = "seafile"
STATUS_POLL_PERIOD = 1
AVAILABLE_SEAFCLI_OPTIONS = {
"delete_confirm_threshold",
"disable_verify_certificate",
"upload_limit",
"download_limit",
}

48
dsc/misc.py Normal file
View File

@ -0,0 +1,48 @@
import os
import pwd
import subprocess
from dsc.const import DEFAULT_USERNAME
def setup_uid(uid: int, gid: int):
"""
Set GID and UID of default user so that seafile client creates files with
correct permissions.
If GID does not match, create a new group with the given GID.
Then update UID and GID of default user to match the given ones.
"""
user_pwinfo = pwd.getpwnam(DEFAULT_USERNAME)
create_group(gid)
if user_pwinfo.pw_uid != uid or user_pwinfo.pw_gid != gid:
subprocess.call(["usermod", "-o", "-u", str(uid), "-g", str(gid), DEFAULT_USERNAME])
def create_group(gid: int):
"""Search for a group with the given GID. If not found, create a new one."""
if not os.path.exists(f"/etc/group"):
raise RuntimeError(f"File /etc/group does not exist")
with open("/etc/group", "r") as f:
for line in f.readlines():
cur_gid = line.split(sep=":", maxsplit=3)[2]
if int(cur_gid) == gid:
return
subprocess.call(["groupadd", "-g", str(gid), DEFAULT_USERNAME + "-data"])
def create_dir(dir_path: str):
if not os.path.exists(dir_path):
os.mkdir(dir_path)
user_pwinfo = pwd.getpwnam(DEFAULT_USERNAME)
os.chown(dir_path, user_pwinfo.pw_uid, user_pwinfo.pw_gid)
else:
if not os.path.isdir(dir_path):
raise RuntimeError(f"Data dir {dir_path} is not a directory")
def hide_password(cmd: list, password: str) -> list:
cmd = cmd.copy()
for i, arg in enumerate(cmd):
if password in arg:
cmd[i] = arg.replace(password, "********")
return cmd

View File

@ -1 +1,2 @@
requests
cached_property==1.5.2
requests==2.31.0

View File

@ -1 +0,0 @@
from .client import SeafileClient, start_seaf_daemon

View File

@ -1,89 +0,0 @@
import logging
import os
import subprocess
import time
from urllib.parse import urlparse
import requests
from .consts import DEFAULT_USERNAME
from .misc import create_dir
logging.basicConfig(format="%(asctime)s %(message)s",
level=logging.INFO)
class SeafileClient:
def __init__(self, host: str, user: str, passwd: str):
up = urlparse(requests.get(f"http://{host}").url)
self.url = f"{up.scheme}://{up.netloc}"
self.user = user
self.password = passwd
self.__token = None
def __str__(self):
return f"SeafileClient({self.user}@{self.url})"
@property
def token(self):
if self.__token is None:
url = f"{self.url}/api2/auth-token/"
r = requests.post(url, data={"username": self.user,
"password": self.password})
if r.status_code != 200:
raise RuntimeError(f"Can't get token: {r.text}")
self.__token = r.json()['token']
return self.__token
def get_lib_name(self, lib_id: str) -> str:
url = f"{self.url}/api2/repos/{lib_id}"
auth_header = {"Authorization": f"Token {self.token}"}
r = requests.get(url, headers=auth_header)
if r.status_code != 200:
raise RuntimeError(r.text)
return r.json()['name']
def sync_lib(self, lib_id: str, data_dir: str):
lib_name = self.get_lib_name(lib_id)
lib_dir = os.path.join(data_dir, lib_name.replace(' ', '_'))
create_dir(lib_dir)
cmd = ['seaf-cli', 'sync',
'-l', lib_id,
'-s', self.url,
'-d', lib_dir,
'-u', self.user,
'-p', self.password]
cmd = ' '.join(cmd)
subprocess.run(['su', '-', DEFAULT_USERNAME, '-c', cmd])
def get_status(self):
cmd = 'seaf-cli status'
out = subprocess.check_output(['su', '-', DEFAULT_USERNAME, '-c', cmd])
out = out.decode().splitlines()
statuses = dict()
for line in out:
if line.startswith('#') or not line.strip():
continue
lib, status = line.split(sep='\t', maxsplit=1)
lib = lib.strip()
status = " ".join(status.split())
statuses[lib] = status
return statuses
def watch_status(self):
prev_status = dict()
while True:
time.sleep(5)
cur_status = self.get_status()
for folder, state in cur_status.items():
if state != prev_status.get(folder):
logging.info(f"Library {folder}:\t{state}")
prev_status[folder] = cur_status[folder]
def start_seaf_daemon():
cmd = 'seaf-cli start'
subprocess.run(['su', '-', DEFAULT_USERNAME, '-c', cmd])
time.sleep(5)

View File

@ -1 +0,0 @@
DEFAULT_USERNAME = "seafile"

View File

@ -1,25 +0,0 @@
import os
import pwd
import subprocess
from .consts import DEFAULT_USERNAME
def setup_uid(uid: int, gid: int):
user_pwinfo = pwd.getpwnam(DEFAULT_USERNAME)
if user_pwinfo.pw_uid != uid or user_pwinfo.pw_gid != gid:
subprocess.call(["usermod", "-o", "-u", str(uid), "-g", str(gid), DEFAULT_USERNAME])
def create_dir(dir_path: str):
if not os.path.exists(dir_path):
os.mkdir(dir_path)
user_pwinfo = pwd.getpwnam(DEFAULT_USERNAME)
os.chown(dir_path, user_pwinfo.pw_uid, user_pwinfo.pw_gid)
else:
if not os.path.isdir(dir_path):
raise RuntimeError(f"Data dir {dir_path} is not a directory")
def tail_f(fpath):
os.system(f"tail -f {fpath}")

View File

@ -1,33 +1,87 @@
#!/usr/bin/env python3
import argparse
import logging
import os
import os.path
import sys
from seafile_client import SeafileClient, start_seaf_daemon
from seafile_client.misc import setup_uid, create_dir
from dsc import SeafileClient, const
from dsc.misc import setup_uid, create_dir
_lg = logging.getLogger('dsc')
def main():
logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO)
parser = argparse.ArgumentParser()
parser.add_argument("--uid", default=os.getenv("SEAFILE_UID", default=1000), type=int)
parser.add_argument("--gid", default=os.getenv("SEAFILE_GID", default=100), type=int)
parser.add_argument("--data-dir", default=os.getenv("DATA_DIR", default="/data"))
parser.add_argument("--host", default=os.getenv("SERVER_HOST"))
parser.add_argument("--username", default=os.getenv("USERNAME"))
parser.add_argument("--password", default=os.getenv("PASSWORD"))
parser.add_argument("--libs", default=os.getenv("LIBRARY_ID"))
parser.add_argument("-s", "--server")
parser.add_argument("-u", "--username")
parser.add_argument("-p", "--password")
parser.add_argument("-l", "--libraries")
parser.add_argument("--uid", type=int)
parser.add_argument("--gid", type=int)
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()
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)
start_seaf_daemon()
create_dir(args.data_dir)
client = SeafileClient(args.host, args.username, args.password)
for lib_id in args.libs.split(sep=":"):
client.sync_lib(lib_id, args.data_dir)
create_dir(const.DEFAULT_APP_DIR)
client = SeafileClient(args.server, args.username, args.password, const.DEFAULT_APP_DIR)
client.init_config()
client.start_daemon()
client.configure(args, check_for_daemon=False)
libs_to_sync = set()
for arg_lib in args.libraries.split(sep=":"):
lib_id = client.get_library_id(arg_lib)
if lib_id:
libs_to_sync.add(lib_id)
else:
_lg.warning("Library %s is not found on server %s", arg_lib, args.server)
# don't start to sync libraries already in sync
libs_to_sync -= client.get_local_libraries()
# check for deprecated /data directory
if os.path.isdir(const.DEPRECATED_LIBS_DIR):
_lg.warning("*** DEPRECATED DIRECTORY FOUND ***")
_lg.warning("Deprecated directory %s is found, please mount your host directory with"
" libraries to %s instead", const.DEPRECATED_LIBS_DIR, const.DEFAULT_LIBS_DIR)
libs_dir = const.DEPRECATED_LIBS_DIR
else:
libs_dir = const.DEFAULT_LIBS_DIR
for lib_id in libs_to_sync:
client.sync_lib(lib_id, libs_dir)
client.watch_status()
client.stop_daemon()
return 0