mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #1214 from YunoHost/more-uniform-tmp-workdir-for-apps
More uniform tmp dir for apps, remove some weird 'admin' ownership
This commit is contained in:
commit
931ed64e1b
2 changed files with 72 additions and 120 deletions
|
@ -33,6 +33,7 @@ import re
|
|||
import subprocess
|
||||
import glob
|
||||
import urllib.parse
|
||||
import tempfile
|
||||
from collections import OrderedDict
|
||||
|
||||
from moulinette import msignals, m18n, msettings
|
||||
|
@ -57,10 +58,8 @@ from yunohost.log import is_unit_operation, OperationLogger
|
|||
|
||||
logger = getActionLogger("yunohost.app")
|
||||
|
||||
APPS_PATH = "/usr/share/yunohost/apps"
|
||||
APPS_SETTING_PATH = "/etc/yunohost/apps/"
|
||||
INSTALL_TMP = "/var/cache/yunohost"
|
||||
APP_TMP_FOLDER = INSTALL_TMP + "/from_file"
|
||||
APP_TMP_WORKDIRS = "/var/cache/yunohost/app_tmp_work_dirs"
|
||||
|
||||
APPS_CATALOG_CACHE = "/var/cache/yunohost/repo"
|
||||
APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml"
|
||||
|
@ -459,34 +458,12 @@ def app_change_url(operation_logger, app, domain, path):
|
|||
operation_logger.extra.update({"env": env_dict})
|
||||
operation_logger.start()
|
||||
|
||||
if os.path.exists(os.path.join(APP_TMP_FOLDER, "scripts")):
|
||||
shutil.rmtree(os.path.join(APP_TMP_FOLDER, "scripts"))
|
||||
|
||||
shutil.copytree(
|
||||
os.path.join(APPS_SETTING_PATH, app, "scripts"),
|
||||
os.path.join(APP_TMP_FOLDER, "scripts"),
|
||||
)
|
||||
|
||||
if os.path.exists(os.path.join(APP_TMP_FOLDER, "conf")):
|
||||
shutil.rmtree(os.path.join(APP_TMP_FOLDER, "conf"))
|
||||
|
||||
shutil.copytree(
|
||||
os.path.join(APPS_SETTING_PATH, app, "conf"),
|
||||
os.path.join(APP_TMP_FOLDER, "conf"),
|
||||
)
|
||||
tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app)
|
||||
change_url_script = os.path.join(tmp_workdir_for_app, "scripts/change_url")
|
||||
|
||||
# Execute App change_url script
|
||||
os.system("chown -R admin: %s" % INSTALL_TMP)
|
||||
os.system("chmod +x %s" % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts")))
|
||||
os.system(
|
||||
"chmod +x %s"
|
||||
% os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url"))
|
||||
)
|
||||
|
||||
if (
|
||||
hook_exec(os.path.join(APP_TMP_FOLDER, "scripts/change_url"), env=env_dict)[0]
|
||||
!= 0
|
||||
):
|
||||
ret = hook_exec(change_url_script, env=env_dict)[0]
|
||||
if ret != 0:
|
||||
msg = "Failed to change '%s' url." % app
|
||||
logger.error(msg)
|
||||
operation_logger.error(msg)
|
||||
|
@ -496,6 +473,7 @@ def app_change_url(operation_logger, app, domain, path):
|
|||
app_setting(app, "domain", value=old_domain)
|
||||
app_setting(app, "path", value=old_path)
|
||||
return
|
||||
shutil.rmtree(tmp_workdir_for_app)
|
||||
|
||||
# this should idealy be done in the change_url script but let's avoid common mistakes
|
||||
app_setting(app, "domain", value=domain)
|
||||
|
@ -620,7 +598,7 @@ def app_upgrade(app=[], url=None, file=None, force=False):
|
|||
_check_manifest_requirements(manifest, app_instance_name=app_instance_name)
|
||||
_assert_system_is_sane_for_app(manifest, "pre")
|
||||
|
||||
app_setting_path = APPS_SETTING_PATH + "/" + app_instance_name
|
||||
app_setting_path = os.path.join(APPS_SETTING_PATH, app_instance_name)
|
||||
|
||||
# Retrieve arguments list for upgrade script
|
||||
# TODO: Allow to specify arguments
|
||||
|
@ -646,9 +624,6 @@ def app_upgrade(app=[], url=None, file=None, force=False):
|
|||
operation_logger = OperationLogger("app_upgrade", related_to, env=env_dict)
|
||||
operation_logger.start()
|
||||
|
||||
# Execute App upgrade script
|
||||
os.system("chown -hR admin: %s" % INSTALL_TMP)
|
||||
|
||||
# Execute the app upgrade script
|
||||
upgrade_failed = True
|
||||
try:
|
||||
|
@ -775,6 +750,12 @@ def app_upgrade(app=[], url=None, file=None, force=False):
|
|||
% (extracted_app_folder, file_to_copy, app_setting_path)
|
||||
)
|
||||
|
||||
# Clean and set permissions
|
||||
shutil.rmtree(extracted_app_folder)
|
||||
os.system("chmod 600 %s" % app_setting_path)
|
||||
os.system("chmod 400 %s/settings.yml" % app_setting_path)
|
||||
os.system("chown -R root: %s" % app_setting_path)
|
||||
|
||||
# So much win
|
||||
logger.success(m18n.n("app_upgraded", app=app_instance_name))
|
||||
|
||||
|
@ -816,10 +797,6 @@ def app_install(
|
|||
)
|
||||
from yunohost.regenconf import manually_modified_files
|
||||
|
||||
# Fetch or extract sources
|
||||
if not os.path.exists(INSTALL_TMP):
|
||||
os.makedirs(INSTALL_TMP)
|
||||
|
||||
def confirm_install(confirm):
|
||||
# Ignore if there's nothing for confirm (good quality app), if --force is used
|
||||
# or if request on the API (confirm already implemented on the API side)
|
||||
|
@ -953,10 +930,6 @@ def app_install(
|
|||
}
|
||||
_set_app_settings(app_instance_name, app_settings)
|
||||
|
||||
os.system("chown -R admin: " + extracted_app_folder)
|
||||
|
||||
# Execute App install script
|
||||
os.system("chown -hR admin: %s" % INSTALL_TMP)
|
||||
# Move scripts and manifest to the right place
|
||||
if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")):
|
||||
os.system("cp %s/manifest.json %s" % (extracted_app_folder, app_setting_path))
|
||||
|
@ -1131,9 +1104,9 @@ def app_install(
|
|||
|
||||
# Clean and set permissions
|
||||
shutil.rmtree(extracted_app_folder)
|
||||
os.system("chmod -R 400 %s" % app_setting_path)
|
||||
os.system("chmod 600 %s" % app_setting_path)
|
||||
os.system("chmod 400 %s/settings.yml" % app_setting_path)
|
||||
os.system("chown -R root: %s" % app_setting_path)
|
||||
os.system("chown -R admin: %s/scripts" % app_setting_path)
|
||||
|
||||
logger.success(m18n.n("installation_complete"))
|
||||
|
||||
|
@ -1212,13 +1185,7 @@ def app_remove(operation_logger, app):
|
|||
|
||||
logger.info(m18n.n("app_start_remove", app=app))
|
||||
|
||||
app_setting_path = APPS_SETTING_PATH + app
|
||||
|
||||
# TODO: display fail messages from script
|
||||
try:
|
||||
shutil.rmtree("/tmp/yunohost_remove")
|
||||
except Exception:
|
||||
pass
|
||||
app_setting_path = os.path.join(APPS_SETTING_PATH, app)
|
||||
|
||||
# Attempt to patch legacy helpers ...
|
||||
_patch_legacy_helpers(app_setting_path)
|
||||
|
@ -1228,13 +1195,8 @@ def app_remove(operation_logger, app):
|
|||
_patch_legacy_php_versions(app_setting_path)
|
||||
|
||||
manifest = _get_manifest_of_app(app_setting_path)
|
||||
|
||||
os.system(
|
||||
"cp -a %s /tmp/yunohost_remove && chown -hR admin: /tmp/yunohost_remove"
|
||||
% app_setting_path
|
||||
)
|
||||
os.system("chown -R admin: /tmp/yunohost_remove")
|
||||
os.system("chmod -R u+rX /tmp/yunohost_remove")
|
||||
tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app)
|
||||
remove_script = f"{tmp_workdir_for_app}/scripts/remove"
|
||||
|
||||
env_dict = {}
|
||||
app_id, app_instance_nb = _parse_app_instance_name(app)
|
||||
|
@ -1246,7 +1208,7 @@ def app_remove(operation_logger, app):
|
|||
operation_logger.flush()
|
||||
|
||||
try:
|
||||
ret = hook_exec("/tmp/yunohost_remove/scripts/remove", env=env_dict)[0]
|
||||
ret = hook_exec(remove_script, env=env_dict)[0]
|
||||
# Here again, calling hook_exec could fail miserably, or get
|
||||
# manually interrupted (by mistake or because script was stuck)
|
||||
# In that case we still want to proceed with the rest of the
|
||||
|
@ -1256,6 +1218,8 @@ def app_remove(operation_logger, app):
|
|||
import traceback
|
||||
|
||||
logger.error(m18n.n("unexpected_error", error="\n" + traceback.format_exc()))
|
||||
finally:
|
||||
shutil.rmtree(tmp_workdir_for_app)
|
||||
|
||||
if ret == 0:
|
||||
logger.success(m18n.n("app_removed", app=app))
|
||||
|
@ -1269,7 +1233,7 @@ def app_remove(operation_logger, app):
|
|||
|
||||
if os.path.exists(app_setting_path):
|
||||
shutil.rmtree(app_setting_path)
|
||||
shutil.rmtree("/tmp/yunohost_remove")
|
||||
|
||||
hook_remove(app)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
@ -1717,7 +1681,6 @@ def app_action_run(operation_logger, app, action, args=None):
|
|||
logger.warning(m18n.n("experimental_feature"))
|
||||
|
||||
from yunohost.hook import hook_exec
|
||||
import tempfile
|
||||
|
||||
# will raise if action doesn't exist
|
||||
actions = app_action_list(app)["actions"]
|
||||
|
@ -1755,8 +1718,9 @@ def app_action_run(operation_logger, app, action, args=None):
|
|||
if action_declaration.get("cwd"):
|
||||
cwd = action_declaration["cwd"].replace("$app", app)
|
||||
else:
|
||||
cwd = "/etc/yunohost/apps/" + app
|
||||
cwd = os.path.join(APPS_SETTING_PATH, app)
|
||||
|
||||
# FIXME: this should probably be ran in a tmp workdir...
|
||||
retcode = hook_exec(
|
||||
path,
|
||||
env=env_dict,
|
||||
|
@ -1811,6 +1775,7 @@ def app_config_show_panel(operation_logger, app):
|
|||
"YNH_APP_INSTANCE_NUMBER": str(app_instance_nb),
|
||||
}
|
||||
|
||||
# FIXME: this should probably be ran in a tmp workdir...
|
||||
return_code, parsed_values = hook_exec(
|
||||
config_script, args=["show"], env=env, return_format="plain_dict"
|
||||
)
|
||||
|
@ -1923,6 +1888,7 @@ def app_config_apply(operation_logger, app, args):
|
|||
"Ignore key '%s' from arguments because it is not in the config", key
|
||||
)
|
||||
|
||||
# FIXME: this should probably be ran in a tmp workdir...
|
||||
return_code = hook_exec(
|
||||
config_script,
|
||||
args=["apply"],
|
||||
|
@ -2237,43 +2203,28 @@ def _set_app_settings(app_id, settings):
|
|||
yaml.safe_dump(settings, f, default_flow_style=False)
|
||||
|
||||
|
||||
def _extract_app_from_file(path, remove=False):
|
||||
def _extract_app_from_file(path):
|
||||
"""
|
||||
Unzip or untar application tarball in APP_TMP_FOLDER, or copy it from a directory
|
||||
Unzip / untar / copy application tarball or directory to a tmp work directory
|
||||
|
||||
Keyword arguments:
|
||||
path -- Path of the tarball or directory
|
||||
remove -- Remove the tarball after extraction
|
||||
|
||||
Returns:
|
||||
Dict manifest
|
||||
|
||||
"""
|
||||
logger.debug(m18n.n("extracting"))
|
||||
|
||||
if os.path.exists(APP_TMP_FOLDER):
|
||||
shutil.rmtree(APP_TMP_FOLDER)
|
||||
os.makedirs(APP_TMP_FOLDER)
|
||||
|
||||
path = os.path.abspath(path)
|
||||
|
||||
extracted_app_folder = _make_tmp_workdir_for_app()
|
||||
|
||||
if ".zip" in path:
|
||||
extract_result = os.system(
|
||||
"unzip %s -d %s > /dev/null 2>&1" % (path, APP_TMP_FOLDER)
|
||||
)
|
||||
if remove:
|
||||
os.remove(path)
|
||||
extract_result = os.system(f"unzip '{path}' -d {extracted_app_folder} > /dev/null 2>&1")
|
||||
elif ".tar" in path:
|
||||
extract_result = os.system(
|
||||
"tar -xf %s -C %s > /dev/null 2>&1" % (path, APP_TMP_FOLDER)
|
||||
)
|
||||
if remove:
|
||||
os.remove(path)
|
||||
extract_result = os.system(f"tar -xf '{path}' -C {extracted_app_folder} > /dev/null 2>&1")
|
||||
elif os.path.isdir(path):
|
||||
shutil.rmtree(APP_TMP_FOLDER)
|
||||
shutil.rmtree(extracted_app_folder)
|
||||
if path[-1] != "/":
|
||||
path = path + "/"
|
||||
extract_result = os.system('cp -a "%s" %s' % (path, APP_TMP_FOLDER))
|
||||
extract_result = os.system(f"cp -a '{path}' {extracted_app_folder}")
|
||||
else:
|
||||
extract_result = 1
|
||||
|
||||
|
@ -2281,7 +2232,6 @@ def _extract_app_from_file(path, remove=False):
|
|||
raise YunohostError("app_extraction_failed")
|
||||
|
||||
try:
|
||||
extracted_app_folder = APP_TMP_FOLDER
|
||||
if len(os.listdir(extracted_app_folder)) == 1:
|
||||
for folder in os.listdir(extracted_app_folder):
|
||||
extracted_app_folder = extracted_app_folder + "/" + folder
|
||||
|
@ -2508,24 +2458,11 @@ def _get_git_last_commit_hash(repository, reference="HEAD"):
|
|||
|
||||
def _fetch_app_from_git(app):
|
||||
"""
|
||||
Unzip or untar application tarball in APP_TMP_FOLDER
|
||||
Unzip or untar application tarball to a tmp directory
|
||||
|
||||
Keyword arguments:
|
||||
app -- App_id or git repo URL
|
||||
|
||||
Returns:
|
||||
Dict manifest
|
||||
|
||||
"""
|
||||
extracted_app_folder = APP_TMP_FOLDER
|
||||
|
||||
app_tmp_archive = "{0}.zip".format(extracted_app_folder)
|
||||
if os.path.exists(extracted_app_folder):
|
||||
shutil.rmtree(extracted_app_folder)
|
||||
if os.path.exists(app_tmp_archive):
|
||||
os.remove(app_tmp_archive)
|
||||
|
||||
logger.debug(m18n.n("downloading"))
|
||||
|
||||
# Extract URL, branch and revision to download
|
||||
if ("@" in app) or ("http://" in app) or ("https://" in app):
|
||||
|
@ -2549,6 +2486,10 @@ def _fetch_app_from_git(app):
|
|||
branch = app_info["git"]["branch"]
|
||||
revision = str(app_info["git"]["revision"])
|
||||
|
||||
extracted_app_folder = _make_tmp_workdir_for_app()
|
||||
|
||||
logger.debug(m18n.n("downloading"))
|
||||
|
||||
# Download only this commit
|
||||
try:
|
||||
# We don't use git clone because, git clone can't download
|
||||
|
@ -3384,6 +3325,23 @@ def _load_apps_catalog():
|
|||
# ############################### #
|
||||
#
|
||||
|
||||
def _make_tmp_workdir_for_app(app=None):
|
||||
|
||||
# Create parent dir if it doesn't exists yet
|
||||
if not os.path.exists(APP_TMP_WORKDIRS):
|
||||
os.makedirs(APP_TMP_WORKDIRS)
|
||||
|
||||
# Cleanup old dirs
|
||||
for dir_ in os.listdir(APP_TMP_WORKDIRS):
|
||||
shutil.rmtree(os.path.join(APP_TMP_WORKDIRS, dir_))
|
||||
tmpdir = tempfile.mkdtemp(prefix="app_", dir=APP_TMP_WORKDIRS)
|
||||
|
||||
# Copy existing app scripts, conf, ... if an app arg was provided
|
||||
if app:
|
||||
os.system(f"cp -a {APPS_SETTING_PATH}/{app}/* {tmpdir}")
|
||||
|
||||
return tmpdir
|
||||
|
||||
|
||||
def is_true(arg):
|
||||
"""
|
||||
|
|
|
@ -53,6 +53,7 @@ from yunohost.app import (
|
|||
_patch_legacy_php_versions,
|
||||
_patch_legacy_php_versions_in_settings,
|
||||
LEGACY_PHP_VERSION_REPLACEMENTS,
|
||||
_make_tmp_workdir_for_app
|
||||
)
|
||||
from yunohost.hook import (
|
||||
hook_list,
|
||||
|
@ -644,7 +645,7 @@ class BackupManager:
|
|||
|
||||
restore_hooks_dir = os.path.join(self.work_dir, "hooks", "restore")
|
||||
if not os.path.exists(restore_hooks_dir):
|
||||
filesystem.mkdir(restore_hooks_dir, mode=0o750, parents=True, uid="admin")
|
||||
filesystem.mkdir(restore_hooks_dir, mode=0o700, parents=True, uid="root")
|
||||
|
||||
restore_hooks = hook_list("restore")["hooks"]
|
||||
|
||||
|
@ -705,21 +706,17 @@ class BackupManager:
|
|||
settings_dir = os.path.join(self.work_dir, "apps", app, "settings")
|
||||
|
||||
logger.info(m18n.n("app_start_backup", app=app))
|
||||
tmp_folder = tempfile.mkdtemp()
|
||||
tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app)
|
||||
try:
|
||||
# Prepare backup directory for the app
|
||||
filesystem.mkdir(tmp_app_bkp_dir, 0o750, True, uid="admin")
|
||||
filesystem.mkdir(tmp_app_bkp_dir, 0o700, True, uid="root")
|
||||
|
||||
# Copy the app settings to be able to call _common.sh
|
||||
shutil.copytree(app_setting_path, settings_dir)
|
||||
|
||||
# Copy app backup script in a temporary folder and execute it
|
||||
app_script = os.path.join(app_setting_path, "scripts/backup")
|
||||
tmp_script = os.path.join(tmp_folder, "backup")
|
||||
subprocess.call(["install", "-Dm555", app_script, tmp_script])
|
||||
|
||||
hook_exec(
|
||||
tmp_script, raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict
|
||||
f"{tmp_workdir_for_app}/scripts/backup",
|
||||
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict
|
||||
)[0]
|
||||
|
||||
self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"])
|
||||
|
@ -750,8 +747,7 @@ class BackupManager:
|
|||
|
||||
# Remove tmp files in all situations
|
||||
finally:
|
||||
if tmp_folder and os.path.exists(tmp_folder):
|
||||
shutil.rmtree(tmp_folder)
|
||||
shutil.rmtree(tmp_workdir_for_app)
|
||||
filesystem.rm(env_dict["YNH_BACKUP_CSV"], force=True)
|
||||
|
||||
#
|
||||
|
@ -1405,13 +1401,11 @@ class RestoreManager:
|
|||
filesystem.chown(app_scripts_new_path, "root", None, True)
|
||||
|
||||
# Copy the app scripts to a writable temporary folder
|
||||
# FIXME : use 'install -Dm555' or something similar to what's done
|
||||
# in the backup method ?
|
||||
tmp_folder_for_app_restore = tempfile.mkdtemp(prefix="restore")
|
||||
copytree(app_scripts_in_archive, tmp_folder_for_app_restore)
|
||||
filesystem.chmod(tmp_folder_for_app_restore, 0o550, 0o550, True)
|
||||
filesystem.chown(tmp_folder_for_app_restore, "root", None, True)
|
||||
restore_script = os.path.join(tmp_folder_for_app_restore, "restore")
|
||||
tmp_workdir_for_app = _make_tmp_workdir_for_app()
|
||||
copytree(app_scripts_in_archive, tmp_workdir_for_app)
|
||||
filesystem.chmod(tmp_workdir_for_app, 0o700, 0o700, True)
|
||||
filesystem.chown(tmp_workdir_for_app, "root", None, True)
|
||||
restore_script = os.path.join(tmp_workdir_for_app, "restore")
|
||||
|
||||
# Restore permissions
|
||||
if not os.path.isfile("%s/permissions.yml" % app_settings_new_path):
|
||||
|
@ -1466,7 +1460,7 @@ class RestoreManager:
|
|||
|
||||
# Cleanup
|
||||
shutil.rmtree(app_settings_new_path, ignore_errors=True)
|
||||
shutil.rmtree(tmp_folder_for_app_restore, ignore_errors=True)
|
||||
shutil.rmtree(tmp_workdir_for_app, ignore_errors=True)
|
||||
|
||||
return
|
||||
|
||||
|
@ -1516,7 +1510,7 @@ class RestoreManager:
|
|||
failure_message_with_debug_instructions = operation_logger.error(error)
|
||||
finally:
|
||||
# Cleaning temporary scripts directory
|
||||
shutil.rmtree(tmp_folder_for_app_restore, ignore_errors=True)
|
||||
shutil.rmtree(tmp_workdir_for_app, ignore_errors=True)
|
||||
|
||||
if not restore_failed:
|
||||
self.targets.set_result("apps", app_instance_name, "Success")
|
||||
|
@ -1872,7 +1866,7 @@ class CopyBackupMethod(BackupMethod):
|
|||
|
||||
dest_parent = os.path.dirname(dest)
|
||||
if not os.path.exists(dest_parent):
|
||||
filesystem.mkdir(dest_parent, 0o750, True, uid="admin")
|
||||
filesystem.mkdir(dest_parent, 0o700, True, uid="admin")
|
||||
|
||||
if os.path.isdir(source):
|
||||
shutil.copytree(source, dest)
|
||||
|
|
Loading…
Add table
Reference in a new issue