Newer
Older
#!/bin/bash
# _____ _ _
# | __|___ ___ ___ _| |___ _____| |_ ___ ___ ___
# | __| _| -_| -_| . | . | | . | . | | -_|
# |__| |_| |___|___|___|___|_|_|_|___|___|_|_|___|
#
# Freedom in the Cloud
#
# Web based administration user interface
# A background daemon running the freedombone-installer
# command is used to add and remove apps and handle
# initial setup of the box
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
WEBADMIN_LOGIN_TEXT=$"Welcome to ${PROJECT_NAME}"
function web_admin_get_hostname {
# get the local name (usually freedombone.local) from avahi config
local_hostname=$(grep 'host-name' /etc/avahi/avahi-daemon.conf | awk -F '=' '{print $2}').local
# where the web admin files are
webadmin_install_dir="/var/www/${local_hostname}/htdocs/admin"
}
function web_admin_configure_installer_daemon {
if [ -f /etc/systemd/system/webadmin.service ]; then
return
fi
{ echo '[Unit]';
echo 'Description=Installer daemon for web admin';
echo 'After=network.target';
echo '';
echo '[Service]';
echo 'User=root';
echo "ExecStart=/usr/local/bin/${PROJECT_NAME}-installer";
echo "ExecReload=/bin/kill \$MAINPID";
echo 'KillMode=process';
echo 'Restart=always';
echo '';
echo '[Install]';
echo 'WantedBy=multi-user.target';
echo 'Alias=pleroma.service'; } > /etc/systemd/system/webadmin.service
systemctl enable webadmin
systemctl daemon-reload
systemctl start webadmin
}
if [ -f /etc/avahi/services/webadmin.service ]; then
return
fi
{ echo '<?xml version="1.0" standalone="no"?><!--*-nxml-*-->';
echo '<!DOCTYPE service-group SYSTEM "avahi-service.dtd">';
echo '<service-group>';
echo ' <name replace-wildcards="yes">%h http</name>';
echo ' <service>';
echo ' <type>_http._tcp</type>';
echo ' <port>80</port>';
echo ' </service>';
echo ' <service>';
echo ' <type>_https._tcp</type>';
echo ' <port>443</port>';
echo ' </service>';
echo '</service-group>'; } > /etc/avahi/services/webadmin.service
systemctl restart avahi-daemon
}
if [ -f "$webadmin_install_dir/.setupscreenactive" ]; then
if [ ! -f "$webadmin_install_dir/.running_install_command" ]; then
if [ -f "$users_file" ]; then
rm "$users_file"
fi
if [ ! -d "$INSTALL_DIR/tempwebadmin" ]; then
mkdir -p "$INSTALL_DIR/tempwebadmin"
fi
mv "$webadmin_install_dir/userprofile_"*.html "$INSTALL_DIR/tempwebadmin"
return
fi
ADMIN_USER=$(get_completion_param "Admin user")
cp "$webadmin_install_dir"/users_template.html "$users_file"
sed -i '/users list/,/end of users/d' "$users_file"
sed -i '/<\/body>/d' "$users_file"
sed -i '/<\/html>/d' "$users_file"
echo " <div class=\"row\">" >> "$users_file"
echo " <div class=\"column\">" >> "$users_file"
for d in /home/*/ ; do
USERNAME=$(echo "$d" | awk -F '/' '{print $3}')
if [[ $(is_valid_user "$USERNAME") == "1" ]]; then
GPG_ID=$(su -c "gpg --list-keys '$useremail'" - "$USERNAME" | sed -n '2p' | sed 's/^[ \t]*//')
if [ -f "/home/$USERNAME/.gnupg/gpg.conf" ]; then
if grep -q "default-key" "/home/$USERNAME/.gnupg/gpg.conf"; then
default_gpg_key=$(grep "default-key" "/home/$USERNAME/.gnupg/gpg.conf")
if [[ "$default_gpg_key" != *'#'* ]]; then
default_gpg_key=$(grep "default-key" "/home/$USERNAME/.gnupg/gpg.conf" | awk -F ' ' '{print $2}')
if [ ${#default_gpg_key} -gt 3 ]; then
GPG_ID=$(su -c "gpg --list-keys '$default_gpg_key'" - "$USERNAME" | sed -n '2p' | sed 's/^[ \t]*//')
fi
fi
fi
fi
pubkey_qrcode="$webadmin_install_dir/images/userprofile_${USERNAME}.png"
su -c "gpg --armor --export \"$GPG_ID\"" - "$USERNAME" | qrencode -t PNG -o "$pubkey_qrcode"
{ echo ' <div class="chip">';
echo " <a href=\"userprofile_${USERNAME}.html\">";
echo ' <img src="images/admin_users.png" alt="Person" width="96" height="96">';
echo " $USERNAME";
echo ' </a>';
echo ' </div>'; } >> "$users_file"
sed -i "s|USERNAME|${USERNAME}|g" "$userfile"
if [[ "$USERNAME" == "$ADMIN_USER" ]]; then
sed -i "s|USERTYPE|Admin|g" "$userfile"
sed -i '/remove user button/,/end of remove/d' "$userfile"
else
sed -i "s|USERTYPE|User|g" "$userfile"
fi
sed -i "s|USEREMAIL|${useremail}|g" "$userfile"
sed -i "s|USERGPG|${GPG_ID}|g" "$userfile"
chown www-data:www-data "$userfile"
chown www-data:www-data "$pubkey_qrcode"
fi
done
{ echo ' <div class="chip">';
echo " <a href=\"newuser.html\">";
echo ' <img src="images/admin_users.png" alt="Person" width="96" height="96">';
echo ' +';
echo ' </a>';
echo ' </div>';
echo ' </div>';
echo ' </div>';
echo ' </body>';
echo '</html>'; } >> "$users_file"
chown www-data:www-data "$users_file"
}
if [ -f "$webadmin_install_dir/.setupscreenactive" ]; then
if [ ! -f "$webadmin_install_dir/.running_install_command" ]; then
if [ -f "$appslist_add_filename" ]; then
rm "$appslist_add_filename"
fi
if [ ! -d "$INSTALL_DIR/tempwebadmin" ]; then
mkdir -p "$INSTALL_DIR/tempwebadmin"
fi
mv "$webadmin_install_dir/app_add_"*.html "$INSTALL_DIR/tempwebadmin"
return
fi
read_config_param DDNS_PROVIDER
apps_add_template_filename="$webadmin_install_dir/apps_add_template.html"
pending_installs="$webadmin_install_dir/pending_installs.txt"
icons_dir="$webadmin_install_dir/icons"
app_add_template_filename="$webadmin_install_dir/app_add_template.html"
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
FILES="/usr/share/${PROJECT_NAME}/apps/${PROJECT_NAME}-app-*"
if [ ! -d "$icons_dir" ]; then
mkdir -p "$icons_dir"
fi
if [ ! -f "$apps_add_template_filename" ]; then
return
fi
cp "$apps_add_template_filename" "$appslist_add_filename"
sed -i '/<\/body>/d' "$appslist_add_filename"
sed -i '/<\/html>/d' "$appslist_add_filename"
available_apps_ctr=0
app_index=0
for filename in $FILES
do
app_name=$(echo "${filename}" | awk -F '-app-' '{print $2}')
app_index=0
app_is_installed=
# shellcheck disable=SC2068
for a in ${APPS_INSTALLED[@]}
do
installed_app_name=${APPS_INSTALLED_NAMES[$app_index]}
if [[ "$installed_app_name" == "$app_name" ]]; then
app_is_installed=1
break
fi
app_index=$((app_index+1))
done
# check if the app is pending installation
app_pending_install=
if [ -f "$pending_installs" ]; then
if grep -q "install_${app_name}" "$pending_installs"; then
app_is_installed=1
app_pending_install=1
if [ ! $app_is_installed ]; then
app_filename="/usr/share/${PROJECT_NAME}/apps/${PROJECT_NAME}-app-${app_name}"
if [ -f "$app_filename" ]; then
# get the icon for the app
icon_filename="/usr/share/${PROJECT_NAME}/android-app/${app_name}.png"
if [ -f "$icon_filename" ]; then
fi
app_name_upper=$(echo "$app_name" | awk '{print toupper($0)}')
SHORT_DESCRIPTION=
DESCRIPTION=
if ! grep -q "${app_name_upper}_SHORT_DESCRIPTION=" "$app_filename"; then
continue
fi
if grep -q "#${app_name_upper}_SHORT_DESCRIPTION=" "$app_filename"; then
continue
fi
SHORT_DESCRIPTION="$(grep "${app_name_upper}_SHORT_DESCRIPTION=" "$app_filename" | head -n 1 | sed 's|\$||g' | sed "s|'||g" | sed 's|\"||g' | awk -F '=' '{print $2}')"
if grep -q "${app_name_upper}_DESCRIPTION=" "$app_filename"; then
DESCRIPTION="$(grep "${app_name_upper}_DESCRIPTION=" "$app_filename" | head -n 1 | sed 's|\$||g' | sed "s|'||g" | sed 's|\"||g' | awk -F '=' '{print $2}')"
fi
if [ $available_apps_ctr -eq 0 ]; then
echo ' <div class="row">' >> "$appslist_add_filename"
fi
if [ -f "$filename" ]; then
rm "$filename"
fi
if [ ! $app_pending_install ]; then
{ echo ' <div class="column">';
echo ' <div>';
echo " <a href=\"app_add_${app_name}.html\">";
echo " <img src=\"icons/${app_name}.png\" style=\"width:100%\">";
echo " <center>${app_name}</center>";
echo ' </a>';
echo ' </div>';
echo ' </div>'; } >> "$appslist_add_filename"
else
{ echo ' <div class="column">';
echo ' <div>';
echo " <img src=\"icons/${app_name}.png\" style=\"width:100%\">";
echo " <center class=\"installing\">${app_name}</center>";
echo ' </div>';
echo ' </div>'; } >> "$appslist_add_filename"
fi
cp "$app_add_template_filename" "$filename"
# Replace app variables
sed -i "s|APPNAME|${app_name}|g" "$filename"
sed -i "s|APPDESCRIPTION|${DESCRIPTION}|g" "$filename"
# remove freedns if necessary
if [[ "$DDNS_PROVIDER" != *"freedns"* ]]; then
if grep -q 'freedns_code' "$filename"; then
sed -i '/freedns_code/d' "$filename"
fi
available_apps_ctr=$((available_apps_ctr+1))
# four columns per row
if [ $available_apps_ctr -eq 4 ]; then
echo ' </div>' >> "$appslist_add_filename"
available_apps_ctr=0
fi
fi
fi
done
# Complete the rest of the four column row
# shellcheck disable=SC2034
for i in $(seq ${available_apps_ctr} 3)
do
{ echo ' <div class="column">';
echo ' <div>';
echo ' </div>';
echo ' </div>'; } >> "$appslist_add_filename"
done
echo ' </body>' >> "$appslist_add_filename"
echo '</html>' >> "$appslist_add_filename"
function web_admin_create_installed_apps {
appslist_filename="$webadmin_install_dir/apps.html"
if [ -f "$webadmin_install_dir/.setupscreenactive" ]; then
if [ ! -f "$webadmin_install_dir/.running_install_command" ]; then
if [ -f "$appslist_filename" ]; then
rm "$appslist_filename"
fi
if [ ! -d "$INSTALL_DIR/tempwebadmin" ]; then
mkdir -p "$INSTALL_DIR/tempwebadmin"
fi
mv "$webadmin_install_dir/app_"*.html "$INSTALL_DIR/tempwebadmin"
return
appslist_template_filename="$webadmin_install_dir/apps_template.html"
app_template_filename="$webadmin_install_dir/app_template.html"
icons_dir="$webadmin_install_dir/icons"
pending_removes="$webadmin_install_dir/pending_removes.txt"
if [ ! -d "$icons_dir" ]; then
mkdir -p "$icons_dir"
fi
return
fi
cp "$appslist_template_filename" "$appslist_filename"
sed -i '/<\/body>/d' "$appslist_filename"
sed -i '/<\/html>/d' "$appslist_filename"
installed_apps_ctr=0
app_index=0
# shellcheck disable=SC2068,SC2034
for a in ${APPS_INSTALLED[@]}
do
app_name=${APPS_INSTALLED_NAMES[$app_index]}
if [ "$app_name" ]; then
app_filename="/usr/share/${PROJECT_NAME}/apps/${PROJECT_NAME}-app-${app_name}"
if [ -f "$app_filename" ]; then
# check if the app is being removed
app_being_removed=
if [ -f "$pending_removes" ]; then
if grep -q "remove_${app_name}" "$pending_removes"; then
app_being_removed=1
fi
fi
if [ ! $app_being_removed ]; then
# get the icon for the app
icon_filename="/usr/share/${PROJECT_NAME}/android-app/${app_name}.png"
if [ -f "$icon_filename" ]; then
app_name_upper=$(echo "$app_name" | awk '{print toupper($0)}')
SHORT_DESCRIPTION=
DESCRIPTION=
if ! grep -q "${app_name_upper}_SHORT_DESCRIPTION=" "$app_filename"; then
app_index=$((app_index+1))
continue
fi
if grep -q "#${app_name_upper}_SHORT_DESCRIPTION=" "$app_filename"; then
app_index=$((app_index+1))
continue
fi
SHORT_DESCRIPTION="$(grep "${app_name_upper}_SHORT_DESCRIPTION=" "$app_filename" | head -n 1 | sed 's|\$||g' | sed "s|'||g" | sed 's|\"||g' | awk -F '=' '{print $2}')"
if grep -q "${app_name_upper}_DESCRIPTION=" "$app_filename"; then
DESCRIPTION="$(grep "${app_name_upper}_DESCRIPTION=" "$app_filename" | head -n 1 | sed 's|\$||g' | sed "s|'||g" | sed 's|\"||g' | awk -F '=' '{print $2}')"
fi
read_config_param "${app_name_upper}_DOMAIN_NAME"
test_domain_name="${app_name_upper}_DOMAIN_NAME"
domain_name=${!test_domain_name}
if [ "$domain_name" ]; then
if [[ "$domain_name" != *'.onion' ]]; then
domain_name="https://${!test_domain_name}"
else
domain_name="http://${!test_domain_name}"
fi
fi
if [ $installed_apps_ctr -eq 0 ]; then
echo ' <div class="row">' >> "$appslist_filename"
fi
if [ -f "$filename" ]; then
rm "$filename"
fi
{ echo ' <div class="column">';
echo ' <div>';
echo " <a href=\"app_${app_name}.html\">";
echo " <img src=\"icons/${app_name}.png\" style=\"width:100%\">";
echo " <center>${app_name}</center>";
echo ' </a>';
echo ' </div>';
echo ' </div>'; } >> "$appslist_filename"
# Replace app variables
sed -i "s|HOSTNAME|$(hostname)|g" "$filename"
sed -i "s|APPNAME|${app_name}|g" "$filename"
sed -i "s|APPURL|${domain_name}|g" "$filename"
sed -i "s|APPDESCRIPTION|${DESCRIPTION}|g" "$filename"
installed_apps_ctr=$((installed_apps_ctr+1))
# four columns per row
if [ $installed_apps_ctr -eq 4 ]; then
echo ' </div>' >> "$appslist_filename"
installed_apps_ctr=0
fi
fi
fi
fi
app_index=$((app_index+1))
done
# Complete the rest of the four column row
# shellcheck disable=SC2034
for i in $(seq ${installed_apps_ctr} 3)
do
{ echo ' <div class="column">';
echo ' <div>';
echo ' </div>';
echo ' </body>' >> "$appslist_filename"
echo '</html>' >> "$appslist_filename"
}
function web_admin_get_language_subdir {
# Gets the subdirectory for the current language
# This is an upper case version of the first two characters of
# the config variable DEFAULT_LANGUAGE
read_config_param DEFAULT_LANGUAGE
if [ ! "$DEFAULT_LANGUAGE" ]; then
echo 'EN'
return
fi
lang_lower="${DEFAULT_LANGUAGE:0:2}"
echo "$lang_lower" | awk '{print toupper($0)}'
}
function web_admin_onion_only {
# In onion only mode domain names or ddns codes
# don't need to be provided
web_admin_get_hostname
sed -i '/freedns_code/d' "$webadmin_install_dir/app_add_template.html"
sed -i '/install_domain/d' "$webadmin_install_dir/app_add_template.html"
sed -i 's|onion_only=false;|onion_only=true;|g' "$webadmin_install_dir/installapp.php"
sed -i 's|setup_domain.html|setup_installing.html|g' "$webadmin_install_dir/setupconfirm.php"
}
function web_admin_reset {
web_admin_get_hostname
# reset the box back to the setup screen
if [ -f /etc/nginx/.webadminpasswd ]; then
rm /etc/nginx/.webadminpasswd
fi
if [ -d "$webadmin_install_dir" ]; then
touch "$webadmin_install_dir/.setupscreenactive"
fi
if [ ! -f /usr/bin/htpasswd ]; then
$INSTALL_PACKAGES apache2-utils
fi
# if an nginx password file has not been created for web admin
if [ -f /etc/nginx/.webadminpasswd ]; then
return
fi
web_admin_get_hostname
# this file indicates that the setup screen is active
# and gets removed by freedombone-installer
if [ ! -f "$webadmin_install_dir/.setupscreenactive" ]; then
touch "$webadmin_install_dir/.setupscreenactive"
fi
webadmin_password="$(create_password "${MINIMUM_PASSWORD_LENGTH}")"
# create a password for users
touch /etc/nginx/.webadminpasswd
# create a password file used by nginx
echo -n "$webadmin_password" | htpasswd -i -s -c /etc/nginx/.webadminpasswd "admin"
if ! grep -q 'admin:' /etc/nginx/.webadminpasswd; then
echo $'/etc/nginx/.webadminpasswd password not created for admin'
exit 2428956
fi
# create a setup page with the initial password inserted
# and copy it to the index
cp "$webadmin_install_dir"/setup.html "$webadmin_install_dir"/setup.prev
cp "$webadmin_install_dir"/setup_confirm_template.html "$webadmin_install_dir"/setup_confirm.html
sed -i "s|WEBADMINPASSWORD|${webadmin_password}|g" "$webadmin_install_dir/setup.prev"
sed -i "s|WEBADMINPASSWORD|${webadmin_password}|g" "$webadmin_install_dir/setup_confirm.html"
cp "$webadmin_install_dir"/setup.prev "$webadmin_install_dir"/index.html
# if initial setup has not yet happened then create
# a password file
if ! grep -q 'install_final' "$COMPLETION_FILE"; then
echo -n "$webadmin_password" > /root/login.txt
# This is intended as an admin web user interface
# similar to Plinth or the yunohost
# get the language subdirectory name
# This is usually EN
language_subdir=$(web_admin_get_language_subdir)
# whether or not to add authentication to freedombone.local
basic_auth_str='#auth_basic'
if grep -q 'install_final' "$COMPLETION_FILE"; then
# initial installation has completed
if [ -f /etc/nginx/.webadminpasswd ]; then
if [ ! -f "$webadmin_install_dir/.setupscreenactive" ]; then
# a password was created
basic_auth_str='auth_basic'
fi
if [ ! -d "$webadmin_install_dir" ]; then
mkdir -p "$webadmin_install_dir"
web_admin_installed=
if [ -d "/usr/share/${PROJECT_NAME}/webadmin/${language_subdir}" ]; then
cp -r "/usr/share/${PROJECT_NAME}/webadmin"/* "$webadmin_install_dir"
cp "/usr/share/${PROJECT_NAME}/webadmin/${language_subdir}"/*.html "$webadmin_install_dir"
cp "$webadmin_install_dir"/index.html "$webadmin_install_dir"/index.prev
if [ -f "$webadmin_install_dir/.setupscreenactive" ]; then
if [ -f "$webadmin_install_dir/setup.prev" ]; then
# We are still waiting for initial setup to happen
cp "$webadmin_install_dir"/setup.prev "$webadmin_install_dir"/index.html
fi
# if this is an onion only install then modify some php scripts
read_config_param ONION_ONLY
if [[ "$ONION_ONLY" != 'no' ]]; then
web_admin_onion_only
fi
web_admin_installed=1
else
echo $"No web admin language subdirectory for ${language_subdir}"
fi
echo $"Directory not found /usr/share/${PROJECT_NAME}/webadmin"
fi
if [ ! $web_admin_installed ]; then
placeholderstr=$"This is a placeholder for the web admin panel on ${local_hostname}"
echo " ${placeholderstr}";
echo '</html>'; } > "$webadmin_install_dir/index.html"
fi
nginx_file=/etc/nginx/sites-available/$local_hostname
{ echo 'server {';
echo ' listen 80 default_server;';
echo ' #listen [::]:80;';
echo " server_name ${local_hostname};";
echo " root /var/www/${local_hostname}/htdocs;";
echo ' index index.html;';
echo '';
echo ' access_log /dev/null;';
echo ' error_log /dev/null;';
echo '';
echo ' location ^~ /admin {';
echo " ${basic_auth_str} \"${WEBADMIN_LOGIN_TEXT}\";";
echo " ${basic_auth_str}_user_file /etc/nginx/.webadminpasswd;";
echo ' index index.html;';
echo " error_page 405 = \$uri;";
echo ' location ~ \.php {';
echo ' include snippets/fastcgi-php.conf;';
echo ' fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;';
echo ' fastcgi_read_timeout 30;';
echo ' }';
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
echo ' }';
echo '';
echo ' location /icons {';
echo ' autoindex on;';
echo ' break;';
echo ' }';
echo '';
echo ' rewrite ^/plinth/(.*)$ /api.json last;';
echo '';
echo ' location / {';
echo " root /var/www/${local_hostname}/htdocs/plinth;";
echo ' index api.json /api.json;';
echo " error_page 405 = \$uri;";
echo ' }';
echo '}';
echo '';
echo 'server {';
echo ' listen 443 default_server ssl;';
echo ' #listen [::]:443 ssl;';
echo " server_name ${local_hostname};";
echo " root /var/www/${local_hostname}/htdocs;";
echo ' index index.html;';
echo '';
echo ' access_log /dev/null;';
echo ' error_log /dev/null;';
echo ''; } > "$nginx_file"
nginx_ssl "${local_hostname}"
nginx_security_options "${local_hostname}"
{ echo ' add_header Strict-Transport-Security max-age=0;';
echo '';
echo ' location ^~ /admin {';
echo " ${basic_auth_str} \"${WEBADMIN_LOGIN_TEXT}\";";
echo " ${basic_auth_str}_user_file /etc/nginx/.webadminpasswd;";
echo ' index index.html;';
echo " error_page 405 = \$uri;";
echo ' location ~ \.php {';
echo ' include snippets/fastcgi-php.conf;';
echo ' fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;';
echo ' fastcgi_read_timeout 30;';
echo ' }';
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
echo ' }';
echo '';
echo ' location /icons {';
echo ' autoindex on;';
echo ' break;';
echo ' }';
echo '';
echo ' rewrite ^/plinth/(.*)$ /api.json last;';
echo '';
echo ' location / {';
echo " root /var/www/${local_hostname}/htdocs/plinth;";
echo ' index api.json /api.json;';
echo " error_page 405 = \$uri;";
echo ' }';
echo '}'; } >> "$nginx_file"
if [ ! -f "/etc/ssl/certs/${local_hostname}.crt" ]; then
"${PROJECT_NAME}-addcert" -h "${local_hostname}" --dhkey "${DH_KEYLENGTH}"
fi
sed -i "s|ssl_certificate .*|ssl_certificate /etc/ssl/certs/${local_hostname}.crt;|g" "$nginx_file"
sed -i "s|ssl_certificate_key .*|ssl_certificate_key /etc/ssl/private/${local_hostname}.key;|g" "$nginx_file"
nginx_ensite "${local_hostname}"
# Compatibility with FreedomBox android app
# The installed apps get published to a json file called api.json
# in this directory
if [ ! -d "/var/www/${local_hostname}/htdocs/plinth" ]; then
mkdir -p "/var/www/${local_hostname}/htdocs/plinth"
fi
# make list of apps which can be added
web_admin_create_add_apps
# make the list of apps
web_admin_create_installed_apps
chown -R www-data:www-data "/var/www/${local_hostname}/htdocs"