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-26 18:31:24 +01:00
install_tmp = ' /tmp/yunohost/install '
app_tmp_folder = install_tmp + ' /from_file '
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-02-25 14:04:41 +01:00
def app_install ( app , domain , path = ' / ' , label = None , public = False , protected = True ) :
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
public - - Allow app public access
protected - - App is protected by the SSO
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-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 ' ] )
if is_webapp :
2013-02-26 18:31:24 +01:00
# Handle domain if ain't already created
try :
domain_list ( filter = " virtualdomain= " + domain )
except YunoHostError :
2013-02-27 22:11:10 +01:00
domain_add ( [ domain ] , web = True )
2013-02-26 19:00:15 +01:00
2013-02-26 18:31:24 +01:00
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
app_final_path = apps_path + ' / ' + unique_app_id
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 + ' " ' )
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
# 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 ' ] ,
' public ' : public ,
' protected ' : protected ,
' 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 )
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
installed_apps = os . listdir ( apps_setting_path )
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