2012-11-17 16:29:06 +01:00
# -*- coding: utf-8 -*-
import os
import sys
import json
2013-02-25 19:54:23 +01:00
import shutil
2013-02-26 14:52:13 +01:00
import stat
2013-02-26 20:36:37 +01:00
import yaml
2013-02-27 17:55:29 +01:00
import time
2013-02-25 19:54:23 +01:00
from yunohost import YunoHostError , YunoHostLDAP , win_msg , random_password
2013-02-12 13:52:11 +01:00
from yunohost_domain import domain_list , domain_add
2012-11-17 16:29:06 +01:00
2013-02-26 18:31:24 +01:00
repo_path = ' /var/cache/yunohost/repo '
apps_path = ' /usr/share/yunohost/apps '
2013-02-26 23:13:49 +01:00
apps_setting_path = ' /etc/yunohost/apps/ '
2013-02-28 17:16:18 +01:00
a2_settings_path = ' /etc/yunohost/apache/domains/ '
2013-02-26 18:31:24 +01:00
install_tmp = ' /tmp/yunohost/install '
app_tmp_folder = install_tmp + ' /from_file '
2013-03-01 18:24:37 +01:00
lemon_tmp_conf = ' /tmp/tmplemonconf '
2013-02-26 18:31:24 +01:00
def app_listlists ( ) :
"""
List fetched lists
Returns :
Dict of lists
"""
list_list = [ ]
try :
for filename in os . listdir ( repo_path ) :
if ' .json ' in filename :
list_list . append ( filename [ : len ( filename ) - 5 ] )
except OSError :
raise YunoHostError ( 1 , _ ( " No list found " ) )
return { ' Lists ' : list_list }
2013-02-25 22:23:32 +01:00
2013-02-24 17:36:58 +01:00
def app_fetchlist ( url = None , name = None ) :
2012-11-17 16:29:06 +01:00
"""
Fetch application list
Keyword arguments :
2012-11-29 14:48:58 +01:00
url - - Custom list URL
2013-02-24 17:36:58 +01:00
name - - Name of the app list
2012-11-17 16:29:06 +01:00
Returns :
True | YunoHostError
"""
# Create app path if not exists
2013-02-24 17:36:58 +01:00
try : os . listdir ( repo_path )
except OSError : os . makedirs ( repo_path )
2012-11-17 16:29:06 +01:00
2013-02-24 17:36:58 +01:00
if not url :
url = ' http://fapp.yunohost.org/app/list/raw '
name = " fapp "
else :
if not name : raise YunoHostError ( 22 , _ ( " You must indicate a name for your custom list " ) )
2012-11-17 16:29:06 +01:00
2013-02-25 22:23:32 +01:00
if os . system ( ' wget " ' + url + ' " -O " ' + repo_path + ' / ' + name + ' .json " ' ) != 0 :
2013-02-24 17:36:58 +01:00
raise YunoHostError ( 1 , _ ( " List server connection failed " ) )
2013-02-12 13:52:11 +01:00
2013-02-24 17:36:58 +01:00
win_msg ( _ ( " List successfully fetched " ) )
2012-11-17 16:29:06 +01:00
2013-02-25 22:54:28 +01:00
def app_removelist ( name ) :
"""
Remove specified application list
Keyword arguments :
name - - Name of the list to remove
"""
try :
os . remove ( repo_path + ' / ' + name + ' .json ' )
except OSError :
raise YunoHostError ( 22 , _ ( " Unknown list " ) )
win_msg ( _ ( " List successfully removed " ) )
2013-02-25 14:04:41 +01:00
def app_list ( offset = None , limit = None , filter = None , raw = False ) :
2013-02-10 22:34:14 +01:00
"""
List available applications
Keyword arguments :
offset - - App to begin with
2013-02-24 17:36:58 +01:00
limit - - Number of apps to list
2013-02-25 14:04:41 +01:00
filter - - Name filter
raw - - Return the full app_dict
2013-02-10 22:34:14 +01:00
Returns :
Dict of apps
"""
# TODO: List installed applications
if offset : offset = int ( offset )
else : offset = 0
if limit : limit = int ( limit )
else : limit = 1000
2013-02-10 21:04:15 +01:00
2013-02-24 17:36:58 +01:00
applists = os . listdir ( repo_path )
app_dict = { }
2013-02-10 21:04:15 +01:00
list_dict = { }
2013-02-24 17:36:58 +01:00
if not applists : app_fetchlist ( )
for applist in applists :
if ' .json ' in applist :
2013-02-25 22:23:32 +01:00
with open ( repo_path + ' / ' + applist ) as json_list :
2013-02-24 17:36:58 +01:00
app_dict . update ( json . loads ( str ( json_list . read ( ) ) ) )
2013-02-10 22:34:14 +01:00
if len ( app_dict ) > ( 0 + offset ) and limit > 0 :
i = 0 + offset
2013-02-24 17:36:58 +01:00
sorted_app_dict = { }
2013-02-10 22:34:14 +01:00
for sorted_keys in sorted ( app_dict . keys ( ) ) [ i : ] :
if i < = limit :
sorted_app_dict [ sorted_keys ] = app_dict [ sorted_keys ]
i + = 1
for app_id , app_info in sorted_app_dict . items ( ) :
2013-02-25 14:04:41 +01:00
if ( filter and ( ( filter in app_id ) or ( filter in app_info [ ' manifest ' ] [ ' name ' ] ) ) ) or not filter :
2013-02-27 19:20:45 +01:00
instance_number = _installed_instance_number ( app_id )
if instance_number > 1 :
installed_txt = ' Yes ( ' + str ( instance_number ) + ' times) '
elif instance_number == 1 :
installed_txt = ' Yes '
else :
installed_txt = ' No '
2013-02-25 14:04:41 +01:00
if raw :
list_dict [ app_id ] = app_info
else :
2013-02-27 20:06:17 +01:00
list_dict [ app_id ] = [
( ' Name ' , app_info [ ' manifest ' ] [ ' name ' ] ) ,
( ' Version ' , app_info [ ' manifest ' ] [ ' version ' ] ) ,
( ' Description ' , app_info [ ' manifest ' ] [ ' description ' ] ) ,
( ' Installed ' , installed_txt )
]
2013-02-10 21:04:15 +01:00
return list_dict
2013-02-11 11:14:39 +01:00
2013-03-01 17:46:42 +01:00
def app_install ( app , domain , path = ' / ' , label = None , mode = ' private ' ) :
2013-02-11 11:14:39 +01:00
"""
Install selected app
Keyword arguments :
app - - AppID to install ( or filename )
domain - - Web domain for the app
path - - Subpath of the domain
label - - User defined name for the app
2013-03-01 17:46:42 +01:00
mode - - public | private | protected
2013-02-11 11:14:39 +01:00
Returns :
Win | Fail
"""
2013-02-26 23:13:49 +01:00
is_webapp = False
2013-02-11 11:14:39 +01:00
2013-02-25 19:54:23 +01:00
with YunoHostLDAP ( ) as yldap :
try : os . listdir ( install_tmp )
except OSError : os . makedirs ( install_tmp )
2013-02-11 13:45:58 +01:00
2013-02-25 22:23:32 +01:00
# Check if install from file or git
2013-02-25 19:54:23 +01:00
if " . " in app :
2013-02-26 14:52:13 +01:00
manifest = _extract_app_tarball ( app )
2013-02-25 19:54:23 +01:00
else :
2013-02-26 14:52:13 +01:00
manifest = _fetch_app_from_git ( app )
2013-02-11 13:45:58 +01:00
2013-02-26 23:13:49 +01:00
if ' __ ' in manifest [ ' yunohost ' ] [ ' uid ' ] :
raise YunoHostError ( 22 , _ ( " App uid is invalid " ) )
2013-02-11 11:14:39 +01:00
2013-02-26 23:13:49 +01:00
instance_number = _installed_instance_number ( manifest [ ' yunohost ' ] [ ' uid ' ] ) + 1
if instance_number > 1 :
if not ( ' multi_instance ' in manifest [ ' yunohost ' ] and ( manifest [ ' yunohost ' ] [ ' multi_instance ' ] == ' yes ' or manifest [ ' yunohost ' ] [ ' multi_instance ' ] == ' true ' ) ) :
raise YunoHostError ( 1 , _ ( " App is already installed " ) )
unique_app_id = manifest [ ' yunohost ' ] [ ' uid ' ] + ' __ ' + str ( instance_number )
2013-02-28 21:44:13 +01:00
app_final_path = apps_path + ' / ' + unique_app_id
2013-02-26 14:52:13 +01:00
script_var_dict = { ' APP_DIR ' : app_tmp_folder }
if ' dependencies ' in manifest : _install_app_dependencies ( manifest [ ' dependencies ' ] )
2013-02-11 11:14:39 +01:00
2013-02-25 19:54:23 +01:00
if ' webapp ' in manifest [ ' yunohost ' ] :
2013-02-26 23:13:49 +01:00
is_webapp = True
2013-02-25 19:54:23 +01:00
if ' db ' in manifest [ ' yunohost ' ] [ ' webapp ' ] :
2013-02-26 23:13:49 +01:00
db_user = unique_app_id
2013-02-25 22:23:32 +01:00
db_pwd = random_password ( )
2013-02-26 14:52:13 +01:00
script_var_dict [ ' DB_USER ' ] = db_user
script_var_dict [ ' DB_PWD ' ] = db_pwd
script_var_dict [ ' DB_NAME ' ] = db_user
2013-02-11 11:14:39 +01:00
2013-02-26 14:52:13 +01:00
_init_app_db ( db_user , db_pwd , manifest [ ' yunohost ' ] [ ' webapp ' ] [ ' db ' ] )
2013-02-11 11:14:39 +01:00
2013-02-26 23:13:49 +01:00
if ' script_path ' in manifest [ ' yunohost ' ] :
_exec_app_script ( step = ' install ' , path = app_tmp_folder + ' / ' + manifest [ ' yunohost ' ] [ ' script_path ' ] , var_dict = script_var_dict , app_type = manifest [ ' type ' ] )
2013-02-28 21:44:13 +01:00
if is_webapp :
domain_add ( [ domain ] , web = True )
# Customize apache conf
a2_conf_lines = [
' Alias ' + path + ' ' + app_final_path + manifest [ ' launch_path ' ]
]
if manifest [ ' yunohost ' ] [ ' webapp ' ] [ ' language ' ] == ' php ' :
a2_conf_lines . extend ( [
' <IfModule php5_module> ' ,
' AddType application/x-httpd-php .php ' ,
' <FilesMatch \ .php$> ' ,
' SetHandler application/x-httpd-php ' ,
' </FilesMatch> ' ,
' AddType application/x-httpd-php-source .phps ' ,
' <IfModule dir_module> ' ,
' DirectoryIndex index.php index.html ' ,
' </IfModule> ' ,
' </IfModule> '
] )
a2_conf_file = a2_settings_path + ' / ' + domain + ' .d/ ' + unique_app_id + ' .app.conf '
if os . path . exists ( a2_conf_file ) : os . remove ( a2_conf_file )
with open ( a2_conf_file , ' a ' ) as file :
for line in a2_conf_lines :
file . write ( line + ' \n ' )
2013-03-01 18:24:37 +01:00
lemon_mode = ' accept '
if ' access_control ' in manifest [ ' yunohost ' ] [ ' webapp ' ] :
if mode == ' public ' and ' can_be_public ' in manifest [ ' yunohost ' ] [ ' webapp ' ] [ ' access_control ' ] and ( manifest [ ' yunohost ' ] [ ' webapp ' ] [ ' access_control ' ] [ ' can_be_public ' ] == ' yes ' or manifest [ ' yunohost ' ] [ ' webapp ' ] [ ' access_control ' ] [ ' can_be_public ' ] == ' true ' ) :
lemon_mode = ' skip '
elif mode == ' protected ' and ' can_be_protected ' in manifest [ ' yunohost ' ] [ ' webapp ' ] [ ' access_control ' ] and ( manifest [ ' yunohost ' ] [ ' webapp ' ] [ ' access_control ' ] [ ' can_be_protected ' ] == ' yes ' or manifest [ ' yunohost ' ] [ ' webapp ' ] [ ' access_control ' ] [ ' can_be_protected ' ] == ' true ' ) :
lemon_mode = ' unprotect '
else :
raise YunoHostError ( 22 , _ ( " Invalid privacy mode " ) )
if os . path . exists ( lemon_tmp_conf ) : os . remove ( lemon_tmp_conf )
lemon_conf_lines = [
" $tmp-> { ' locationRules ' }-> { ' " + domain + " ' }-> { ' (?# " + unique_app_id + " Z)^ " + path + " ' } = ' " + lemon_mode + " ' ; "
]
with open ( lemon_tmp_conf , ' w ' ) as lemon_conf :
for line in lemon_conf_lines :
lemon_conf . write ( line + ' \n ' )
if os . system ( ' /usr/share/lemonldap-ng/bin/lmYnhMoulinette ' ) == 0 :
win_msg ( _ ( " LemonLDAP configured " ) )
else :
raise YunoHostError ( 1 , _ ( " An error occured during LemonLDAP configuration " ) )
2013-02-11 11:14:39 +01:00
2013-02-26 18:31:24 +01:00
# Copy files to the right place
try : os . listdir ( apps_path )
except OSError : os . makedirs ( apps_path )
2013-02-11 11:14:39 +01:00
2013-02-26 23:13:49 +01:00
2013-02-26 18:31:24 +01:00
# TMP: Remove old application
if os . path . exists ( app_final_path ) : shutil . rmtree ( app_final_path )
2013-02-26 23:13:49 +01:00
2013-02-26 18:31:24 +01:00
os . system ( ' cp -a " ' + app_tmp_folder + ' " " ' + app_final_path + ' " ' )
os . system ( ' chown -R www-data: " ' + app_final_path + ' " ' )
2013-03-01 17:46:42 +01:00
if is_webapp : os . system ( ' service apache2 reload ' )
2013-02-26 18:31:24 +01:00
shutil . rmtree ( app_final_path + manifest [ ' yunohost ' ] [ ' script_path ' ] )
2013-02-11 11:14:39 +01:00
2013-02-26 23:13:49 +01:00
app_setting_path = apps_setting_path + ' / ' + unique_app_id
2013-02-28 17:16:18 +01:00
2013-02-26 23:13:49 +01:00
# TMP: Remove old settings
if os . path . exists ( app_setting_path ) : shutil . rmtree ( app_setting_path )
os . makedirs ( app_setting_path )
if is_webapp :
yaml_dict = {
' uid ' : manifest [ ' yunohost ' ] [ ' uid ' ] ,
' instance ' : instance_number ,
2013-02-27 17:55:29 +01:00
' last_update ' : manifest [ ' lastUpdate ' ] ,
' install_time ' : int ( time . time ( ) ) ,
2013-02-26 23:13:49 +01:00
' name ' : manifest [ ' name ' ] ,
2013-03-01 17:46:42 +01:00
' mode ' : mode ,
2013-02-26 23:13:49 +01:00
' domain ' : domain ,
' path ' : path ,
}
if ' db ' in manifest [ ' yunohost ' ] [ ' webapp ' ] :
yaml_dict [ ' db_pwd ' ] = db_pwd
yaml_dict [ ' db_user ' ] = db_user
if label : yaml_dict [ ' label ' ] = label
else : yaml_dict [ ' label ' ] = manifest [ ' name ' ]
2013-02-11 11:14:39 +01:00
2013-02-26 23:13:49 +01:00
with open ( app_setting_path + ' /app_setting.yml ' , ' w ' ) as f :
yaml . safe_dump ( yaml_dict , f , default_flow_style = False )
win_msg ( _ ( " App setting file created " ) )
2013-02-26 20:36:37 +01:00
2013-02-26 23:13:49 +01:00
if ' script_path ' in manifest [ ' yunohost ' ] :
os . system ( ' cp -a " ' + app_tmp_folder + ' / ' + manifest [ ' yunohost ' ] [ ' script_path ' ] + ' " ' + app_setting_path )
2013-02-28 18:22:34 +01:00
2013-02-28 17:58:18 +01:00
shutil . rmtree ( app_tmp_folder )
2013-02-26 20:36:37 +01:00
2013-02-26 23:13:49 +01:00
if os . system ( ' chmod 400 -R ' + app_setting_path ) == 0 :
win_msg ( _ ( " Installation complete " ) )
else :
raise YunoHostError ( 22 , _ ( " Error during permission setting " ) )
2013-02-24 17:36:58 +01:00
2013-02-11 11:14:39 +01:00
2013-02-26 14:52:13 +01:00
def _extract_app_tarball ( path ) :
2013-02-25 22:23:32 +01:00
"""
Unzip or untar application tarball in app_tmp_folder
Keyword arguments :
2013-02-26 14:52:13 +01:00
path - - Path of the tarball
2013-02-25 22:23:32 +01:00
Returns :
Dict manifest
"""
if os . path . exists ( app_tmp_folder ) : shutil . rmtree ( app_tmp_folder )
os . makedirs ( app_tmp_folder )
2013-02-26 14:52:13 +01:00
if " .zip " in path :
extract_result = os . system ( ' cd ' + os . getcwd ( ) + ' && unzip ' + path + ' -d ' + app_tmp_folder )
elif " .tar " in path :
extract_result = os . system ( ' cd ' + os . getcwd ( ) + ' && tar -C ' + app_tmp_folder + ' -xf ' + path )
2013-02-25 22:23:32 +01:00
else :
extract_result = 1
if extract_result != 0 :
raise YunoHostError ( 22 , _ ( " Invalid install file " ) )
with open ( app_tmp_folder + ' /manifest.webapp ' ) as json_manifest :
manifest = json . loads ( str ( json_manifest . read ( ) ) )
2013-02-27 17:55:29 +01:00
manifest [ ' lastUpdate ' ] = int ( time . time ( ) )
2013-02-25 22:23:32 +01:00
2013-02-26 14:52:13 +01:00
win_msg ( _ ( " Tarball extracted " ) )
2013-02-25 22:23:32 +01:00
return manifest
2013-02-26 14:52:13 +01:00
def _fetch_app_from_git ( app ) :
2013-02-25 22:23:32 +01:00
"""
Unzip or untar application tarball in app_tmp_folder
2013-02-11 11:14:39 +01:00
2013-02-25 22:23:32 +01:00
Keyword arguments :
app - - Path of the tarball
Returns :
Dict manifest
"""
global app_tmp_folder
app_tmp_folder = install_tmp + ' / ' + app
if os . path . exists ( app_tmp_folder ) : shutil . rmtree ( app_tmp_folder )
app_dict = app_list ( raw = True )
if app in app_dict :
app_info = app_dict [ app ]
2013-02-27 17:55:29 +01:00
app_info [ ' manifest ' ] [ ' lastUpdate ' ] = app_info [ ' lastUpdate ' ]
2013-02-25 22:23:32 +01:00
else :
raise YunoHostError ( 22 , _ ( " App doesn ' t exists " ) )
git_result = os . system ( ' git clone ' + app_info [ ' git ' ] [ ' url ' ] + ' -b ' + app_info [ ' git ' ] [ ' branch ' ] + ' ' + app_tmp_folder )
git_result_2 = os . system ( ' cd ' + app_tmp_folder + ' && git reset --hard ' + str ( app_info [ ' git ' ] [ ' revision ' ] ) )
if not git_result == git_result_2 == 0 :
raise YunoHostError ( 22 , _ ( " Sources fetching failed " ) )
2013-02-26 14:52:13 +01:00
win_msg ( _ ( " Repository fetched " ) )
2013-02-25 22:23:32 +01:00
return app_info [ ' manifest ' ]
2013-02-26 14:52:13 +01:00
def _install_app_dependencies ( dep_dict ) :
2013-02-25 22:23:32 +01:00
"""
Install debian , npm , gem , pip and pear dependencies of the app
Keyword arguments :
dep_dict - - Dict of dependencies from the manifest
"""
if ( ' debian ' in dep_dict ) and ( len ( dep_dict [ ' debian ' ] ) > 0 ) :
#os.system('apt-get update')
if os . system ( ' apt-get install " ' + ' " " ' . join ( dep_dict [ ' debian ' ] ) + ' " ' ) != 0 :
raise YunoHostError ( 1 , _ ( " Dependency installation failed: " ) + dependency )
# TODO: Install npm, pip, gem and pear dependencies
2013-02-26 14:52:13 +01:00
win_msg ( _ ( " Dependencies installed " ) )
2013-02-25 22:23:32 +01:00
2013-02-26 14:52:13 +01:00
def _init_app_db ( db_user , db_pwd , db_dict ) :
2013-02-25 22:23:32 +01:00
"""
Create database and initialize it with optionnal attached script
Keyword arguments :
db_user - - Name of the DB user ( also used as database name )
db_pwd - - Password for the user
db_dict - - Dict of DB parameters from the manifest
"""
# Need MySQL DB ?
if ' has_mysql_db ' in db_dict and ( ( db_dict [ ' has_mysql_db ' ] == ' true ' ) or ( db_dict [ ' has_mysql_db ' ] == ' yes ' ) ) :
mysql_root_pwd = open ( ' /etc/yunohost/mysql ' , ' rb ' ) . read ( ) . rstrip ( )
mysql_command = ' mysql -u root -p ' + mysql_root_pwd + ' -e " CREATE DATABASE ' + db_user + ' ; GRANT ALL PRIVILEGES ON ' + db_user + ' .* TO \' ' + db_user + ' \' @localhost IDENTIFIED BY \' ' + db_pwd + ' \' ; " '
if os . system ( mysql_command ) != 0 :
raise YunoHostError ( 1 , _ ( " MySQL DB creation failed " ) )
if ' mysql_init_script ' in db_dict :
if os . system ( ' mysql -u ' + db_user + ' -p ' + db_pwd + ' ' + db_user + ' < ' + app_tmp_folder + db_dict [ ' mysql_init_script ' ] + ' ; ' ) != 0 :
raise YunoHostError ( 1 , _ ( " MySQL DB init failed " ) )
# TODO: PgSQL/MongoDB ?
2013-02-26 14:52:13 +01:00
win_msg ( _ ( " Database initiliazed " ) )
def _exec_app_script ( step , path , var_dict , app_type ) :
"""
Execute step user script
Keyword arguments :
step - - Name of the script to call regarding the current step ( e . g . install | upgrade | remove | etc . )
path - - Absolute path of the script ' s directory
var_dict - - Dictionnary of environnement variable to pass to the script
app_type - - Decides whether to execute as root or as yunohost - app user ( e . g . web | privileged | certified )
"""
scripts = [ step , step + ' .sh ' , step + ' .py ' ]
for script in scripts :
script_path = path + ' / ' + script
if os . path . exists ( script_path ) :
st = os . stat ( script_path )
os . chmod ( script_path , st . st_mode | stat . S_IEXEC )
if app_type == ' privileged ' or app_type == ' certified ' :
user = ' root '
else :
user = ' yunohost-app '
os . system ( ' chown -R ' + user + ' : ' + app_tmp_folder )
env_vars = ' '
for key , value in var_dict . items ( ) :
env_vars = env_vars + key + " = ' " + value + " ' "
command = ' su - ' + user + ' -c " ' + env_vars + ' sh ' + path + ' / ' + script + ' " '
if os . system ( command ) == 0 :
win_msg ( _ ( " Script executed: " ) + script )
else :
raise YunoHostError ( 1 , _ ( " Script execution failed: " ) + script )
break
2013-02-26 18:31:24 +01:00
2013-02-26 23:13:49 +01:00
def _installed_instance_number ( app ) :
"""
Check if application is installed and return instance number
Keyword arguments :
app - - uid of App to check
Returns :
Number of installed instance
"""
number = 0
2013-02-28 17:58:18 +01:00
try :
installed_apps = os . listdir ( apps_setting_path )
except OSError :
os . makedirs ( apps_setting_path )
return 0
2013-02-26 23:13:49 +01:00
for installed_app in installed_apps :
if ' __ ' in installed_app :
if app == installed_app [ : installed_app . index ( ' __ ' ) ] :
if int ( installed_app [ installed_app . index ( ' __ ' ) + 2 : ] ) > number :
number = int ( installed_app [ installed_app . index ( ' __ ' ) + 2 : ] )
return number