Initialize seafile client during container start
This commit is contained in:
@@ -1 +1 @@
|
||||
from .client import SeafileClient, start_seaf_daemon
|
||||
from .client import SeafileClient, const
|
||||
|
||||
119
dsc/client.py
119
dsc/client.py
@@ -8,33 +8,40 @@ from urllib.parse import urlparse
|
||||
from cached_property import cached_property_with_ttl
|
||||
import requests
|
||||
|
||||
from dsc import consts
|
||||
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):
|
||||
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})
|
||||
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']
|
||||
self.__token = r.json()["token"]
|
||||
return self.__token
|
||||
|
||||
@cached_property_with_ttl(ttl=60)
|
||||
@@ -48,36 +55,77 @@ class SeafileClient:
|
||||
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 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, data_dir: str):
|
||||
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(data_dir, lib_name.replace(' ', '_'))
|
||||
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(['su', '-', consts.DEFAULT_USERNAME, '-c', ' '.join(cmd)])
|
||||
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'
|
||||
cmd = "seaf-cli status"
|
||||
_lg.debug("Fetching seafile client status: %s", cmd)
|
||||
out = subprocess.check_output(['su', '-', consts.DEFAULT_USERNAME, '-c', 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():
|
||||
if line.startswith("#") or not line.strip():
|
||||
continue
|
||||
lib, status = line.split(sep='\t', maxsplit=1)
|
||||
lib, status = line.split(sep="\t", maxsplit=1)
|
||||
lib = lib.strip()
|
||||
status = " ".join(status.split())
|
||||
statuses[lib] = status
|
||||
@@ -86,7 +134,7 @@ class SeafileClient:
|
||||
def watch_status(self):
|
||||
prev_status = dict()
|
||||
while True:
|
||||
time.sleep(consts.STATUS_POLL_PERIOD)
|
||||
time.sleep(const.STATUS_POLL_PERIOD)
|
||||
cur_status = self.get_status()
|
||||
for folder, state in cur_status.items():
|
||||
if state != prev_status.get(folder):
|
||||
@@ -94,38 +142,13 @@ class SeafileClient:
|
||||
prev_status[folder] = cur_status[folder]
|
||||
|
||||
def get_local_libraries(self) -> set:
|
||||
cmd = 'seaf-cli list'
|
||||
cmd = "seaf-cli list"
|
||||
_lg.info("Listing local libraries: %s", cmd)
|
||||
out = subprocess.check_output(['su', '-', consts.DEFAULT_USERNAME, '-c', cmd])
|
||||
out = out.decode().splitlines()[1:] # first line is a header
|
||||
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 check_seaf_daemon_is_ready() -> bool:
|
||||
cmd = 'seaf-cli status'
|
||||
_lg.info("Checking seafile daemon status: %s", cmd)
|
||||
proc = subprocess.run(
|
||||
['su', '-', consts.DEFAULT_USERNAME, '-c', cmd],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
return proc.returncode == 0
|
||||
|
||||
|
||||
def start_seaf_daemon():
|
||||
cmd = 'seaf-cli start'
|
||||
_lg.info("Starting seafile daemon: %s", cmd)
|
||||
subprocess.run(['su', '-', consts.DEFAULT_USERNAME, '-c', cmd])
|
||||
_lg.info("Waiting for seafile daemon to start")
|
||||
|
||||
while True:
|
||||
if check_seaf_daemon_is_ready():
|
||||
break
|
||||
time.sleep(5)
|
||||
|
||||
_lg.info("Seafile daemon is ready")
|
||||
|
||||
5
dsc/const.py
Normal file
5
dsc/const.py
Normal file
@@ -0,0 +1,5 @@
|
||||
DEFAULT_APP_DIR = "/dsc"
|
||||
DEFAULT_LIBS_DIR = "/dsc/seafile"
|
||||
DEPRECATED_LIBS_DIR = "/data"
|
||||
DEFAULT_USERNAME = "seafile"
|
||||
STATUS_POLL_PERIOD = 1
|
||||
@@ -1,2 +0,0 @@
|
||||
DEFAULT_USERNAME = "seafile"
|
||||
STATUS_POLL_PERIOD = 1
|
||||
21
dsc/misc.py
21
dsc/misc.py
@@ -2,15 +2,34 @@ import os
|
||||
import pwd
|
||||
import subprocess
|
||||
|
||||
from dsc.consts import DEFAULT_USERNAME
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user