deploy1 há 6 anos atrás
commit
fadda1bc29
22 ficheiros alterados com 1070 adições e 0 exclusões
  1. 3 0
      .dockerignore
  2. 2 0
      .gitignore
  3. 57 0
      CHANGELOG
  4. 41 0
      CloudronManifest.json
  5. 23 0
      DESCRIPTION.md
  6. 112 0
      Dockerfile
  7. 9 0
      LICENSE
  8. 1 0
      README.md
  9. 69 0
      apache2-app.conf
  10. 108 0
      htconfig.php
  11. BIN
      logo.png
  12. 101 0
      logo.svg
  13. 18 0
      phpmyadmin-config.inc.php
  14. 126 0
      proftpd.conf
  15. 101 0
      start.sh
  16. 12 0
      supervisor/apache2.conf
  17. 11 0
      supervisor/cron.conf
  18. 11 0
      supervisor/proftpd.conf
  19. 7 0
      test/.jshintrc
  20. 25 0
      test/package.json
  21. 227 0
      test/test.js
  22. 6 0
      test/test.php

+ 3 - 0
.dockerignore

@@ -0,0 +1,3 @@
+test/*
+.git/*
+medialinks/

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+test/node_modules/
+

+ 57 - 0
CHANGELOG

@@ -0,0 +1,57 @@
+[0.1.0]
+- initial version
+
+[0.1.1]
+- Update title and tagline
+
+[0.2.0]
+- Block executing php on /webdav
+- Update to new base image
+
+[0.3.0]
+- Replace webdav with sftp
+- Make php.ini configurable
+- Preinstall all common php modules
+- Update to base image 0.10.0
+
+[0.3.1]
+* Fix index.php display
+
+[0.4.0]
+* Remove simple auth
+
+[0.5.0]
+* Add phpMyAdmin
+
+[0.6.0]
+* Add cron support
+
+[0.6.1]
+* Increase default file upload limit to 64m
+
+[0.6.2]
+* Cleanup PHP sessions
+* Update PHP to 7.0.18
+
+[1.0.0]
+* Update PHP to 7.0.22
+* Fix issue where cron will endlessly try to send emails
+
+[1.1.0]
+* Remove unused oauth addon
+* Add documentationUrl
+
+[1.1.1]
+* Remove unused oauth addon
+* Add documentationUrl
+
+[1.1.2]
+* Fix addon urls
+
+[1.2.0]
+* Update PHP 7.0.28-0ubuntu0.16.04.1
+* Fix bug where env vars were not set in cron jobs
+
+[1.2.1]
+* Enable mod_headers
+

+ 41 - 0
CloudronManifest.json

@@ -0,0 +1,41 @@
+{
+  "id": "friendica.cloudronapp",
+  "title": "Friendica",
+  "author": "M-arcus",
+  "description": "file://DESCRIPTION.md",
+  "tagline": "Friendica",
+  "version": "3.6.0",
+  "healthCheckPath": "/",
+  "httpPort": 80,
+  "manifestVersion": 1,
+  "website": "https://friendi.ca/",
+  "contactEmail": "apps@cloudron.io",
+  "icon": "logo.png",
+  "addons": {
+    "mysql": {},
+    "localstorage": {},
+    "sendmail": {},
+    "redis": {},
+    "ldap": {}
+  },
+  "tcpPorts": {
+    "SFTP_PORT": {
+      "title": "SFTP Port",
+      "description": "SFTP Port. Disabling SFTP also disables phpMyAdmin",
+      "defaultValue": 2345
+    }
+  },
+  "tags": [
+    "apache",
+    "php",
+    "mysql",
+    "hosting",
+    "lamp",
+    "stacks",
+    "development"
+  ],
+  "mediaLinks": [],
+  "changelog": "file://CHANGELOG",
+  "minBoxVersion": "1.8.5",
+  "documentationUrl": "https://cloudron.io/documentation/apps/lamp/"
+}

+ 23 - 0
DESCRIPTION.md

@@ -0,0 +1,23 @@
+## Cloudron Friendica Stack
+
+A personal social network
+Keep in contact with people you care about.
+
+### Setup
+
+Use 'admin@example.com' for the email admin account.
+
+### SFTP
+
+This app also bundles [ProFTPD](http://www.proftpd.org/) which provides `sftp://` access. Use your preferred ftp client to manage all files on the server. The `public` folder contains your PHP files. You will find `php.ini` at the root directory.
+
+### Cron
+
+This app supports running one or more cronjobs. The jobs are specified using the standard crontab syntax.
+
+### Remote Terminal
+
+Use the [web terminal](https://cloudron.io/documentation/apps/#web-terminal) for a remote shell connection into the
+app to adjust configuration files like `php.ini`.
+
+PHP Version: <upstream>PHP 7</upstream>

+ 112 - 0
Dockerfile

@@ -0,0 +1,112 @@
+FROM cloudron/base:0.10.0
+MAINTAINER Johannes Zellner <johannes@cloudron.io>
+
+RUN mkdir -p /app/code /run/app/sessions
+WORKDIR /app/code
+
+RUN apt-get update && apt-get install -y php libapache2-mod-php crudini \
+    php-redis \
+    php-bcmath \
+    php-bz2 \
+    php-curl \
+    php-date \
+    php-dba \
+    php-enchant \
+    php-gd \
+    php-geoip \
+    php-gettext \
+    php-imap \
+    php-json \
+    php-log \
+    php-mbstring \
+    php-mcrypt \
+    php-mime-type \
+    php-mysql \
+    php-pdfparser \
+    php-readline \
+    php-soap \
+    php-sql-formatter \
+    php-sqlite3 \
+    php-tcpdf \
+    php-timer \
+    php-twig \
+    php-uuid \
+    php-validate \
+    php-xml \
+    php-xml-parser \
+    php-xml-svg \
+    php-yac \
+    php-zip \
+    php-imagick \
+    proftpd proftpd-mod-ldap \
+    cron \
+    git \
+    && rm -rf /var/cache/apt /var/lib/apt/lists /etc/ssh_host_*
+
+# configure apache
+RUN rm /etc/apache2/sites-enabled/*
+RUN sed -e 's,^ErrorLog.*,ErrorLog "|/bin/cat",' -i /etc/apache2/apache2.conf
+RUN sed -e "s,MaxSpareServers[^:].*,MaxSpareServers 5," -i /etc/apache2/mods-available/mpm_prefork.conf
+
+RUN a2disconf other-vhosts-access-log
+RUN echo "Listen 80" > /etc/apache2/ports.conf
+RUN a2enmod rewrite authnz_ldap headers
+
+# configure mod_php
+RUN crudini --set /etc/php/7.0/apache2/php.ini PHP upload_max_filesize 64M && \
+    crudini --set /etc/php/7.0/apache2/php.ini PHP post_max_size 64M && \
+    crudini --set /etc/php/7.0/apache2/php.ini PHP memory_limit 128M && \
+    crudini --set /etc/php/7.0/apache2/php.ini Session session.save_path /run/app/sessions && \
+    crudini --set /etc/php/7.0/apache2/php.ini Session session.gc_probability 1 && \
+    crudini --set /etc/php/7.0/apache2/php.ini Session session.gc_divisor 100
+
+RUN mv /etc/php/7.0/apache2/php.ini /etc/php/7.0/apache2/php.ini.orig && ln -sf /app/data/php.ini /etc/php/7.0/apache2/php.ini
+
+# configure site
+COPY apache2-app.conf /app/code/apache2-app.conf
+RUN ln -s /run/apache2/app.conf /etc/apache2/sites-enabled/app.conf
+
+# phpMyAdmin
+RUN mkdir -p /app/code/phpmyadmin && \
+    curl -L https://files.phpmyadmin.net/phpMyAdmin/4.7.0/phpMyAdmin-4.7.0-english.tar.gz | tar zxvf - -C /app/code/phpmyadmin --strip-components=1
+COPY phpmyadmin-config.inc.php /app/code/phpmyadmin/config.inc.php
+
+# configure proftpd
+ADD proftpd.conf /app/code/proftpd.conf.template
+
+RUN rm -rf /var/log/proftpd && ln -s /run/proftpd /var/log/proftpd
+
+# configure cron
+RUN rm -rf /var/spool/cron && ln -s /run/cron /var/spool/cron
+# clear out the crontab
+RUN rm -f /etc/cron.d/* /etc/cron.daily/* /etc/cron.hourly/* /etc/cron.monthly/* /etc/cron.weekly/* && truncate -s0 /etc/crontab
+
+# configure supervisor
+ADD supervisor/ /etc/supervisor/conf.d/
+RUN sed -e 's,^logfile=.*$,logfile=/run/supervisord.log,' -i /etc/supervisor/supervisord.conf
+
+# add code
+COPY start.sh /app/code/
+
+# forgotten in the base image
+RUN chmod +x /usr/local/bin/composer
+
+# copy code from github
+RUN git clone https://github.com/friendica/friendica.git /app/code/friendica
+
+# navigate to friendica dir
+WORKDIR /app/code/friendica
+
+# checkout specific version
+RUN git checkout 7997df877d709f5c7ca211449ad941b1be216a2d
+
+#install composer packages
+RUN /usr/local/bin/composer install --optimize-autoloader
+
+# copy config file
+COPY htconfig.php /app/code/friendica/
+
+# make cloudron exec sane
+WORKDIR /app/data
+
+CMD [ "/app/code/start.sh" ]

+ 9 - 0
LICENSE

@@ -0,0 +1,9 @@
+MIT License (MIT)
+Copyright (c) 2016 Cloudron UG
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+

+ 1 - 0
README.md

@@ -0,0 +1 @@
+##friendica-app

+ 69 - 0
apache2-app.conf

@@ -0,0 +1,69 @@
+<VirtualHost *:80>
+    DocumentRoot /app/data/public
+
+    ErrorLog "|/bin/cat"
+    CustomLog "|/bin/cat" combined
+
+    <Directory /app/data/public>
+        Options +FollowSymLinks
+        AllowOverride All
+        Require all granted
+        Options -Indexes
+        AddType application/x-java-archive .jar
+        AddType audio/ogg .oga
+        #AddHandler php53-cgi .php
+
+        <FilesMatch "\.(out|log)$">
+          <IfModule authz_host_module>
+            #Apache 2.4
+            Require all denied
+          </IfModule>
+          <IfModule !authz_host_module>
+            #Apache 2.2
+            Deny from all
+          </IfModule>
+        </FilesMatch>
+
+        <IfModule mod_rewrite.c>
+          RewriteEngine on
+          # Protect repository directory from browsing
+          RewriteRule "(^|/)\.git" - [F]
+
+          # Rewrite current-style URLs of the form 'index.php?pagename=x'.
+          # Also place auth information into REMOTE_USER for sites running
+          # in CGI mode.
+
+          # If you have troubles or use VirtualDocumentRoot
+          # uncomment this and set it to the path where your friendica installation is
+          # i.e.:
+          # Friendica url: http://some.example.com
+          # RewriteBase /
+          # Friendica url: http://some.example.com/friendica
+          # RewriteBase /friendica/
+          #
+          #RewriteBase /
+
+          RewriteCond %{REQUEST_FILENAME} !-f
+          RewriteCond %{REQUEST_FILENAME} !-d
+          RewriteRule ^(.*)$ index.php?pagename=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA]
+
+        </IfModule>
+    </Directory>
+
+    ## PMA BEGIN
+
+    Alias /phpmyadmin /app/code/phpmyadmin
+ 
+    <Location /phpmyadmin>
+        AuthType Basic
+        AuthBasicProvider ldap
+        AuthName "Cloudron Authorization"
+        AuthLDAPURL ldap://url/basedn?username??(objectclass=user)
+        AuthLDAPBindDN abouttochange
+        AuthLDAPBindPassword abouttochange
+        Require valid-user
+    </Location>
+
+    ## PMA END
+
+</VirtualHost>

+ 108 - 0
htconfig.php

@@ -0,0 +1,108 @@
+<?php
+
+// If automatic system installation fails:
+
+
+//die('The configuration you did manually contains some mistakes. Please have a look at your .htconfig.php file.');
+// If you are doing the configuration manually, please remove the line above
+
+
+// Copy or rename this file to .htconfig.php
+
+// Why .htconfig.php? Because it contains sensitive information which could
+// give somebody complete control of your database. Apache's default
+// configuration denies access to and refuses to serve any file beginning
+// with .ht
+
+// Then set the following for your MySQL installation
+
+$db_host = 'your.mysqlhost.com';
+$db_user = 'mysqlusername';
+$db_pass = 'mysqlpassword';
+$db_data = 'mysqldatabasename';
+
+// Use environment variables for mysql if they are set beforehand
+if (!empty(getenv('MYSQL_HOST'))
+    && !empty(getenv('MYSQL_PORT'))
+    && !empty(getenv('MYSQL_USERNAME'))
+    && !empty(getenv('MYSQL_PASSWORD'))
+    && !empty(getenv('MYSQL_DATABASE'))) {
+    $db_host = getenv('MYSQL_HOST') . ':' . getenv('MYSQL_PORT');
+    $db_user = getenv('MYSQL_USERNAME');
+    $db_pass = getenv('MYSQL_PASSWORD');
+    $db_data = getenv('MYSQL_DATABASE');
+}
+
+// Set the database connection charset to full Unicode (utf8mb4).
+// Changing this value will likely corrupt the special characters.
+// You have been warned.
+$a->config['system']['db_charset'] = "utf8mb4";
+
+// Choose a legal default timezone. If you are unsure, use "America/Los_Angeles".
+// It can be changed later and only applies to timestamps for anonymous viewers.
+
+$default_timezone = 'America/Los_Angeles';
+
+// Default system language
+
+$a->config['system']['language'] = 'en';
+
+// What is your site name?
+
+$a->config['sitename'] = "Friendica Social Network";
+
+// Your choices are REGISTER_OPEN, REGISTER_APPROVE, or REGISTER_CLOSED.
+// Be certain to create your own personal account before setting
+// REGISTER_CLOSED. 'register_text' (if set) will be displayed prominently on
+// the registration page. REGISTER_APPROVE requires you set 'admin_email'
+// to the email address of an already registered person who can authorise
+// and/or approve/deny the request.
+
+// In order to perform system administration via the admin panel, admin_email
+// must precisely match the email address of the person logged in.
+
+$a->config['register_policy'] = REGISTER_OPEN;
+$a->config['register_text'] = '';
+$a->config['admin_email'] = 'admin@example.com';
+
+// Maximum size of an imported message, 0 is unlimited
+
+$a->config['max_import_size'] = 200000;
+
+// maximum size of uploaded photos
+
+$a->config['system']['maximagesize'] = 800000;
+
+// Location of PHP command line processor
+
+$a->config['php_path'] = 'php';
+
+// Server-to-server private message encryption (RINO) is allowed by default.
+// set to 0 to disable, 1 to enable
+
+$a->config['system']['rino_encrypt'] = 1;
+
+// allowed themes (change this from admin panel after installation)
+
+$a->config['system']['allowed_themes'] = 'quattro,vier,duepuntozero,smoothly,frio';
+
+// default system theme
+
+$a->config['system']['theme'] = 'vier';
+
+
+// By default allow pseudonyms
+
+$a->config['system']['no_regfullname'] = true;
+
+//Deny public access to the local directory
+//$a->config['system']['block_local_dir'] = false;
+
+// Location of the global directory
+$a->config['system']['directory'] = getenv('APP_ORIGIN');
+
+// Allowed protocols in link URLs; HTTP protocols always are accepted
+$a->config['system']['allowed_link_protocols'] = ['ftp', 'ftps', 'mailto', 'cid', 'gopher'];
+
+// Authentication cookie lifetime, in days
+$a->config['system']['auth_cookie_lifetime'] = 7;

BIN
logo.png


+ 101 - 0
logo.svg

@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="256pc"
+   height="256pc"
+   viewBox="0 0 3839.9999 3840.0001"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="logo.svg"
+   inkscape:export-filename="/home/nebulon/projects/yellowtent/apps/lamp-app/logo.png"
+   inkscape:export-xdpi="6"
+   inkscape:export-ydpi="6">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.04375"
+     inkscape:cx="408.20832"
+     inkscape:cy="3651.575"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="pc"
+     inkscape:window-width="1920"
+     inkscape:window-height="1016"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,2787.6379)">
+    <g
+       id="g4324"
+       transform="translate(0,-429.74243)">
+      <path
+         inkscape:connector-curvature="0"
+         id="path4233"
+         d="m 201.77272,-171.81666 c -63.17621,-27.6751 -68.72541,-53.92715 -65.47745,-309.76071 l 2.90464,-228.79371 91.46952,0 91.46951,0 5.38056,225.98351 5.38056,225.9835 191.61636,2.94097 191.61637,2.94114 -3.29678,45.4839 -3.29678,45.48406 -236.74462,2.37649 c -183.16923,1.8386 -244.50158,-1.02119 -271.02189,-12.63915 z"
+         style="fill:#094080;fill-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path4229"
+         d="m 821.00118,-716.57938 0,272.60936 c 0,149.93838 3.41033,276.03649 7.58315,280.20948 4.17282,4.1729 44.12352,6.19616 88.77923,4.48936 l 81.1961,-3.11056 3.09384,-99.54035 3.0938,-99.54036 117.5653,0 117.5652,0 3.0938,99.54036 3.0938,99.54035 91.4695,0 91.4696,0 2.7575,-220.60295 c 2.5683,-204.43294 1.1977,-223.17623 -18.7311,-255.67748 -11.8278,-19.2896 -37.2904,-44.68438 -56.58,-56.44542 -32.3354,-19.71523 -54.5792,-21.40378 -285.2537,-21.43822 l -250.19602,-0.0344 z m 348.54252,66.39946 c 23.0464,0.557 40.7814,1.75105 47.6348,3.4637 4.5022,1.12519 8.7704,3.03757 12.7956,5.68325 4.0253,2.6456 7.8095,6.02141 11.3161,10.07172 7.013,8.10063 12.9332,18.906 17.6213,31.93022 4.688,13.02431 8.1431,28.27626 10.1726,45.2976 2.0295,17.02133 2.6439,35.79871 1.6815,55.89058 l -3.1779,66.43313 -142.9717,3.00967 -142.97154,2.99297 3.077,-110.60417 3.0771,-110.62086 102.23064,-3.1611 c 28.1134,-0.8676 56.4682,-0.94379 79.5145,-0.38654 z"
+         clip-path="none"
+         style="fill:#094080;fill-opacity:1" />
+      <path
+         sodipodi:nodetypes="ccccsscccssaccccsssccc"
+         inkscape:connector-curvature="0"
+         id="path4221"
+         d="m 2954.9864,-717.12596 -256.4509,2.8752 -256.451,2.89207 -2.892,263.44563 c -1.588,144.89425 -0.452,269.78522 2.5221,277.53607 3.9941,10.4091 28.6646,13.27328 94.3615,10.96282 l 88.9474,-3.12744 3.0434,-115.68203 3.0602,-115.68203 156.2044,0 c 220.4493,0 241.234,-14.50108 241.234,-168.37786 0,-78.17118 2.2412,-93.80261 -23.7451,-128.94007 -11.1319,-15.05205 -49.834,-25.90236 -49.834,-25.90236 z m -330.7363,89.85534 102.2306,0 102.2306,0 0,63.42337 c 0,47.89955 -4.1589,65.01352 -17.016,69.94728 -9.3619,3.59258 -55.3657,6.5239 -102.2306,6.5239 l -85.2146,0 0,-69.94727 z"
+         style="fill:#094080;fill-opacity:1" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path4209"
+         d="m 1540.4513,-433.07356 0,-274.40855 307.2263,0 c 331.2552,0 338.0167,1.01715 392.2465,59.00615 26.7224,28.57473 26.9248,30.3145 30.1861,259.28934 l 3.2834,230.5216 -91.423,0 -91.4232,0 -3.3297,-210.81334 c -3.2854,-208.00073 -3.6763,-211.09348 -29.2926,-231.81603 -14.2796,-11.55151 -33.6496,-19.52541 -43.0445,-17.71978 -14.8516,2.85428 -17.4608,33.11773 -19.986,231.81603 l -2.9044,228.53312 -91.2554,0 -91.2555,0 0,-231.36407 0,-231.36406 -43.0444,0 -43.0445,0 0,231.36406 0,231.36407 -91.4695,0 -91.4696,0 0,-274.40854 z"
+         style="fill:#ff7f00;fill-opacity:1" />
+    </g>
+    <g
+       id="g4304"
+       transform="matrix(8.6088954,0,0,8.6088954,-1179.0369,-6278.0919)">
+      <path
+         id="path4213"
+         d="m 490.5957,490.16602 c -1.27377,-0.0707 -1.8457,0.51344 -1.8457,1.71679 0,1.01335 1.64736,5.98773 3.66016,11.05469 8.2963,20.8847 7.62529,35.08777 -1.86133,39.41016 -1.74145,0.79346 -4.12415,1.44807 -5.29492,1.45507 -3.94774,0.0226 -14.85232,5.91871 -19.45118,10.51758 -4.88302,4.88302 -12.05273,17.84526 -12.05273,21.78907 0,2.42705 3.11377,3.88146 8.37891,3.91406 2.05718,0.0127 3.36595,-1.36467 5.17187,-5.44727 4.37328,-9.88651 10.58936,-15.42568 20.64453,-18.39453 9.93132,-2.93228 16.12898,-6.97636 18.89649,-12.32812 3.19989,-6.1879 4.50424,-14.66779 3.36914,-21.90625 -1.03887,-6.62485 -1.00256,-6.74986 1.55078,-5.40039 4.8961,2.58763 26.34945,25.46042 30.55664,32.57812 14.37337,24.31675 16.49195,48.12003 6.1875,69.51758 -6.23508,12.94737 -22.17739,24.09052 -41.00586,28.66406 -9.13531,2.21901 -9.38505,2.38258 -9.77539,6.375 -0.22031,2.25344 -0.0792,4.93691 0.31445,5.96289 0.90649,2.36226 7.48614,1.42721 20.71094,-2.94141 33.03654,-10.91312 50.31271,-36.35428 48.35742,-71.21289 -1.86717,-33.28782 -25.71889,-66.73517 -63.70312,-89.33203 -6.61478,-3.93513 -10.68564,-5.87431 -12.8086,-5.99218 z"
+         style="fill:#094080;fill-opacity:1"
+         inkscape:connector-curvature="0" />
+      <path
+         id="path4205"
+         d="m 521.11719,537.54102 c -0.59479,0 -2.75272,3.30987 -4.79492,7.35546 -5.00824,9.92127 -12.17901,15.83638 -23.16797,19.10938 -9.99139,2.97587 -13.32633,5.51985 -16.89258,12.88672 -3.46902,7.16606 -3.22995,8.00438 2.55664,8.92969 7.0981,1.13504 12.57964,5.17015 15.86914,11.68164 2.39569,4.74219 2.8125,7.55339 2.8125,18.9707 0,7.37207 -0.58523,14.9441 -1.30078,16.82617 -0.71564,1.88206 -1.01429,4.1674 -0.66406,5.08008 0.81094,2.11325 1.83795,2.0857 11.07031,-0.30664 15.72893,-4.07578 28.34176,-13.19567 34.37891,-24.85742 2.77149,-5.35363 3.33929,-8.31242 3.77539,-19.68946 0.59899,-15.62626 -1.78832,-25.60863 -8.97071,-37.51367 -4.98543,-8.26353 -13.09373,-18.47265 -14.67187,-18.47265 z"
+         style="fill:#ff7f00;fill-opacity:1"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>

+ 18 - 0
phpmyadmin-config.inc.php

@@ -0,0 +1,18 @@
+<?php
+$i = 0;
+$i++;
+$cfg['Servers'][$i]['auth_type'] = 'config';
+
+/* Server parameters */
+$cfg['Servers'][$i]['host'] = getenv("MYSQL_HOST");
+$cfg['Servers'][$i]['port'] = getenv("MYSQL_PORT");
+$cfg['Servers'][$i]['user'] = getenv("MYSQL_USERNAME");
+$cfg['Servers'][$i]['password'] = getenv("MYSQL_PASSWORD");
+$cfg['Servers'][$i]['only_db'] = array(getenv("MYSQL_DATABASE"));
+
+$cfg['Servers'][$i]['compress'] = false;
+$cfg['Servers'][$i]['AllowNoPassword'] = false;
+
+$cfg['UploadDir'] = '';
+$cfg['SaveDir'] = '';
+

+ 126 - 0
proftpd.conf

@@ -0,0 +1,126 @@
+# Includes DSO modules
+Include /etc/proftpd/modules.conf
+
+# Set off to disable IPv6 support which is annoying on IPv4 only boxes.
+UseIPv6				off
+# If set on you can experience a longer connection delay in many cases.
+IdentLookups			off
+
+ServerName			"##SERVER_NAME"
+ServerType			standalone
+DeferWelcome			off
+
+MultilineRFC2228		on
+DefaultServer			on
+ShowSymlinks			on
+
+TimeoutNoTransfer		600
+TimeoutStalled			600
+TimeoutIdle			1200
+
+DisplayLogin                    welcome.msg
+DisplayChdir               	.message true
+ListOptions                	"-l"
+
+DenyFilter			\*.*/
+
+# Use this to jail all users in their homes
+# DefaultRoot			~
+
+# Users require a valid shell listed in /etc/shells to login.
+# Use this directive to release that constrain.
+# RequireValidShell		off
+
+# Port 21 is the standard FTP port.
+Port				0
+
+# To prevent DoS attacks, set the maximum number of child processes
+# to 30.  If you need to allow more than 30 concurrent connections
+# at once, simply increase this value.  Note that this ONLY works
+# in standalone mode, in inetd mode you should use an inetd server
+# that allows you to limit maximum number of processes per service
+# (such as xinetd)
+MaxInstances			10
+
+# Set the user and group that the server normally runs at.
+User				www-data
+Group				www-data
+
+# Umask 022 is a good standard umask to prevent new files and dirs
+# (second parm) from being group and world writable.
+Umask				022  022
+# Normally, we want files to be overwriteable.
+AllowOverwrite			on
+
+TransferLog /run/proftpd/xferlog
+SystemLog   /run/proftpd/proftpd.log
+
+<IfModule mod_quotatab.c>
+QuotaEngine off
+</IfModule>
+
+<IfModule mod_ratio.c>
+Ratios off
+</IfModule>
+
+# Delay engine reduces impact of the so-called Timing Attack described in
+# http://www.securityfocus.com/bid/11430/discuss
+# It is on by default.
+<IfModule mod_delay.c>
+DelayEngine on
+</IfModule>
+
+<IfModule mod_ctrls.c>
+ControlsEngine        off
+ControlsMaxClients    2
+ControlsLog           /var/log/proftpd/controls.log
+ControlsInterval      5
+ControlsSocket        /var/run/proftpd/proftpd.sock
+</IfModule>
+
+<IfModule mod_ctrls_admin.c>
+    AdminControlsEngine off
+</IfModule>
+
+LoadModule mod_ldap.c
+<IfModule mod_ldap.c>
+# https://forums.proftpd.org/smf/index.php?topic=6368.0
+LDAPServer "##LDAP_URL/??sub"
+LDAPBindDN "##LDAP_BIND_DN" "##LDAP_BIND_PASSWORD"
+LDAPUsers "##LDAP_USERS_BASE_DN" (username=%u)
+
+LDAPForceDefaultUID on
+LDAPDefaultUID ##LDAP_UID
+LDAPForceDefaultGID on
+LDAPDefaultGID ##LDAP_GID
+
+LDAPForceGeneratedHomedir on
+LDAPGenerateHomedir on
+LDAPGenerateHomedirPrefix /app/data
+LDAPGenerateHomedirPrefixNoUsername on
+
+#LDAPUseTLS off
+#LDAPLog /run/proftpd/ldap.log
+</IfModule>
+
+<IfModule mod_sftp.c>
+SFTPEngine on
+Port ##SFTP_PORT
+SFTPLog /run/proftpd/sftp.log
+
+# Configure both the RSA and DSA host keys, using the same host key
+# files that OpenSSH uses.
+SFTPHostKey /app/data/sftpd/ssh_host_rsa_key
+SFTPHostKey /app/data/sftpd/ssh_host_dsa_key
+
+SFTPAuthMethods password
+
+# Enable compression
+SFTPCompression delayed
+
+RequireValidShell off
+</IfModule>
+
+<Directory />
+  HideNoAccess yes
+</Directory>

+ 101 - 0
start.sh

@@ -0,0 +1,101 @@
+#!/bin/bash
+
+set -eu
+
+mkdir -p /app/data/public /run/apache2 /run/proftpd /run/app /run/cron
+
+if ! [ -f /app/data/.initialized ]; then
+  echo "Fresh installation, setting up data directory..."
+
+  # Setup commands here
+  echo "Copying Friendica code"
+  cp -rf /app/code/friendica/* /app/data/public/
+  echo "Copying finished"
+
+  echo "Starting automatic installation"
+  cd /app/data/public/
+  bin/console autoinstall
+  cd /app/data
+  echo "Installation done"
+
+  touch /app/data/.initialized
+  echo "Done."
+fi
+
+# cleanup for old apache2-app.conf
+rm -f /app/data/apache2-app.conf
+
+if [ ! -f "/app/data/php.ini" ]; then
+    cp /etc/php/7.0/apache2/php.ini.orig /app/data/php.ini
+else
+    crudini --set /app/data/php.ini Session session.gc_probability 1
+    crudini --set /app/data/php.ini Session session.gc_divisor 100
+fi
+
+# SFTP_PORT can be unset to disable SFTP
+disable_sftp="false"
+if [[ -z "${SFTP_PORT:-}" ]]; then
+    echo "SSH disabled"
+    SFTP_PORT=29418 # arbitrary port to keep sshd happy
+    disable_sftp="true"
+else
+    sed -e "s,##SERVER_NAME,${APP_DOMAIN}," \
+        -e "s/##SFTP_PORT/${SFTP_PORT}/" \
+        -e "s,##LDAP_URL,${LDAP_URL},g" \
+        -e "s/##LDAP_BIND_DN/${LDAP_BIND_DN}/g" \
+        -e "s/##LDAP_BIND_PASSWORD/${LDAP_BIND_PASSWORD}/g" \
+        -e "s/##LDAP_USERS_BASE_DN/${LDAP_USERS_BASE_DN}/g" \
+        -e "s/##LDAP_UID/$(id -u www-data)/g" \
+        -e "s/##LDAP_GID/$(id -g www-data)/g" \
+        /app/code/proftpd.conf.template > /run/proftpd/proftpd.conf
+
+    if [[ -f /app/data/public/index.php ]]; then
+        sed -e "s,^sftp -P.*public/$,sftp -P ${SFTP_PORT} ${APP_DOMAIN}:public/," \
+            -i /app/data/public/index.php
+    fi
+fi
+
+if [[ ! -f "/app/data/sftpd/ssh_host_ed25519_key" ]]; then
+    echo "Generating ssh host keys"
+    mkdir -p /app/data/sftpd
+    ssh-keygen -qt rsa -N '' -f /app/data/sftpd/ssh_host_rsa_key
+    ssh-keygen -qt dsa -N '' -f /app/data/sftpd/ssh_host_dsa_key
+    ssh-keygen -qt ecdsa -N '' -f /app/data/sftpd/ssh_host_ecdsa_key
+    ssh-keygen -qt ed25519 -N '' -f /app/data/sftpd/ssh_host_ed25519_key
+else
+    echo "Reusing existing host keys"
+fi
+
+chmod 0600 /app/data/sftpd/*_key
+chmod 0644 /app/data/sftpd/*.pub
+
+## Generate apache config. PMA is disabled based on SFTP config
+if [[ "${disable_sftp}" == "true" ]]; then
+    echo "PMA disabled"
+    sed '/.*PMA BEGIN/,/.*PMA END/d' /app/code/apache2-app.conf > /run/apache2/app.conf
+else
+    sed -e "s@AuthLDAPURL .*@AuthLDAPURL ${LDAP_URL}/${LDAP_USERS_BASE_DN}?username??(objectclass=user)@" \
+        -e "s@AuthLDAPBindDN .*@AuthLDAPBindDN ${LDAP_BIND_DN}@" \
+        -e "s@AuthLDAPBindPassword .*@AuthLDAPBindPassword ${LDAP_BIND_PASSWORD}@" \
+        /app/code/apache2-app.conf > /run/apache2/app.conf
+fi
+
+## hook for custom start script in /app/data/run.sh
+if [ -f "/app/data/run.sh" ]; then
+    /bin/bash /app/data/run.sh
+fi
+
+## configure in-container Crontab
+if [ -f "/app/data/crontab" ]; then
+    # http://www.gsp.com/cgi-bin/man.cgi?section=5&topic=crontab
+    if ! (env; cat /app/data/crontab; echo -e '\nMAILTO=""') | crontab -u www-data -; then
+        echo "Error importing crontab. Continuing anyway"
+    else
+        echo "Imported crontab"
+    fi
+fi
+
+chown -R www-data:www-data /app/data /run/apache2 /run/proftpd /run/app
+
+echo "Starting supervisord"
+exec /usr/bin/supervisord --configuration /etc/supervisor/supervisord.conf --nodaemon -i Lamp

+ 12 - 0
supervisor/apache2.conf

@@ -0,0 +1,12 @@
+[program:apache2]
+autorestart=true
+autostart=true
+command=/usr/bin/pidproxy /run/apache2/apache2.pid /bin/bash -c "source /etc/apache2/envvars && /usr/sbin/apache2 -DFOREGROUND"
+environment=APACHE_CONFDIR=""
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+stopasgroup=true
+killasgroup=true
+

+ 11 - 0
supervisor/cron.conf

@@ -0,0 +1,11 @@
+[program:cron]
+directory=/
+command=/usr/sbin/cron -f -L 15
+user=root
+autostart=true
+autorestart=true
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+

+ 11 - 0
supervisor/proftpd.conf

@@ -0,0 +1,11 @@
+[program:proftpd]
+directory=/
+command=/usr/sbin/proftpd --nodaemon -c /run/proftpd/proftpd.conf
+user=root
+autostart=true
+autorestart=true
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+

+ 7 - 0
test/.jshintrc

@@ -0,0 +1,7 @@
+{
+	"node": true,
+	"browser": true,
+	"unused": true,
+	"globalstrict": true,
+	"predef": [ "angular", "$", "describe", "it", "before", "after" ]
+}

+ 25 - 0
test/package.json

@@ -0,0 +1,25 @@
+{
+  "name": "test",
+  "version": "1.0.0",
+  "description": "",
+  "main": "test.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "ejs": "^2.3.4",
+    "expect.js": "^0.3.1",
+    "mkdirp": "^0.5.1",
+    "mocha": "^2.3.4",
+    "rimraf": "^2.4.4",
+    "superagent": "^1.4.0"
+  },
+  "dependencies": {
+    "chromedriver": "^2.37.0",
+    "selenium-server-standalone-jar": "^3.3.1",
+    "selenium-webdriver": "^3.3.0",
+    "superagent": "^1.8.5"
+  }
+}

+ 227 - 0
test/test.js

@@ -0,0 +1,227 @@
+#!/usr/bin/env node
+
+'use strict';
+
+require('chromedriver');
+
+var execSync = require('child_process').execSync,
+    expect = require('expect.js'),
+    fs = require('fs'),
+    net = require('net'),
+    path = require('path'),
+    superagent = require('superagent'),
+    util = require('util'),
+    webdriver = require('selenium-webdriver');
+
+var by = webdriver.By,
+    until = webdriver.until,
+    Builder = require('selenium-webdriver').Builder;
+
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
+
+if (!process.env.USERNAME || !process.env.PASSWORD) {
+    console.log('USERNAME and PASSWORD env vars need to be set');
+    process.exit(1);
+}
+
+describe('Application life cycle test', function () {
+    this.timeout(0);
+
+    var server, browser = new Builder().forBrowser('chrome').build();
+
+    before(function (done) {
+        var seleniumJar= require('selenium-server-standalone-jar');
+        var SeleniumServer = require('selenium-webdriver/remote').SeleniumServer;
+        server = new SeleniumServer(seleniumJar.path, { port: 4444 });
+        server.start();
+
+        done();
+    });
+
+    after(function (done) {
+        browser.quit();
+        server.stop();
+        done();
+    });
+
+    var LOCATION = 'test';
+    var TEST_TIMEOUT = 50000;
+    var app;
+
+    function waitForElement(elem, callback) {
+         browser.wait(until.elementLocated(elem), TEST_TIMEOUT).then(function () {
+            browser.wait(until.elementIsVisible(browser.findElement(elem)), TEST_TIMEOUT).then(function () {
+                callback();
+            });
+        });
+    }
+
+    function welcomePage(callback) {
+        browser.get('https://' + app.fqdn);
+
+        waitForElement(by.xpath('//*[text()="Cloudron LAMP App"]'), function () {
+            waitForElement(by.xpath('//*[text()="PHP Version 7.0.28-0ubuntu0.16.04.1"]'), callback);
+        });
+    }
+
+    function uploadedFileExists(callback) {
+        browser.get('https://' + app.fqdn + '/test.php');
+
+        waitForElement(by.xpath('//*[text()="this works"]'), function () {
+            waitForElement(by.xpath('//*[text()="' + app.fqdn + '"]'), callback);
+        });
+    }
+
+    function checkPhpMyAdmin(callback) {
+        superagent.get('https://' + app.fqdn + '/phpmyadmin').end(function (error, result) {
+            if (error && !error.response) return callback(error); // network error
+
+            if (result.statusCode !== 401) return callback('Expecting 401 error');
+
+            superagent.get('https://' + app.fqdn + '/phpmyadmin')
+                .auth(process.env.USERNAME, process.env.PASSWORD)
+                .end(function (error, result) {
+                if (error) return callback(error);
+
+                if (result.text.indexOf(`${app.fqdn} / mysql | phpMyAdmin`) === -1) { // in the <title>
+                    console.log(result.text);
+                    return callback(new Error('could not detect phpmyadmin'));
+                }
+
+                callback();
+            });
+        });
+    }
+
+    function checkCron(callback) {
+        this.timeout(60000 * 2);
+
+        fs.writeFileSync('/tmp/crontab', '* * * * * echo -n "$MYSQL_HOST" > /app/data/public/cron\n', 'utf8');
+        execSync('cloudron push /tmp/crontab /app/data/crontab');
+        fs.unlinkSync('/tmp/crontab');
+
+        execSync('cloudron restart --wait');
+
+        console.log('Waiting for crontab to trigger');
+
+        setTimeout(function () {
+            superagent.get('https://' + app.fqdn + '/cron').end(function (error, result) {
+                if (error && !error.response) return callback(error); // network error
+
+                if (result.statusCode !== 200) return callback('Expecting 200, got ' + result.statusCode);
+
+                if (result.text !== 'mysql') return callback('Unexpected text: ' + result.text);
+
+                callback();
+            });
+        }, 60 * 1000); // give it a minute to run the crontab
+    }
+
+    xit('build app', function () {
+        execSync('cloudron build', { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+    });
+
+    it('install app', function () {
+        execSync('cloudron install --new --wait --location ' + LOCATION, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+    });
+
+    it('can get app information', function () {
+        var inspect = JSON.parse(execSync('cloudron inspect'));
+
+        app = inspect.apps.filter(function (a) { return a.location === LOCATION; })[0];
+
+        expect(app).to.be.an('object');
+    });
+
+    it('can view welcome page', welcomePage);
+    it('can upload file with sftp', function () {
+        // remove from known hosts in case this test was run on other apps with the same domain already
+        // if the tests fail here you want to set "HashKnownHosts no" in ~/.ssh/config
+        execSync(util.format('sed -i \'/%s/d\' -i ~/.ssh/known_hosts', app.fqdn));
+        execSync(util.format('lftp sftp://%s:%s@%s:%s  -e "set sftp:auto-confirm yes; cd public/; put test.php; bye"', process.env.USERNAME, process.env.PASSWORD, app.fqdn, app.portBindings.SFTP_PORT));
+    });
+    it('can get uploaded file', uploadedFileExists);
+    it('can access phpmyadmin', checkPhpMyAdmin);
+    it('executes cron tasks', checkCron);
+
+    it('backup app', function () {
+        execSync('cloudron backup create --app ' + app.id, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+    });
+
+    it('restore app', function () {
+        execSync('cloudron restore --app ' + app.id, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+    });
+
+    it('can get uploaded file', uploadedFileExists);
+
+    it('move to different location', function () {
+        browser.manage().deleteAllCookies();
+        execSync('cloudron configure --wait --location ' + LOCATION + '2 --app ' + app.id, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+        var inspect = JSON.parse(execSync('cloudron inspect'));
+        app = inspect.apps.filter(function (a) { return a.location === LOCATION + '2'; })[0];
+        expect(app).to.be.an('object');
+    });
+
+    it('can get uploaded file', uploadedFileExists);
+    it('can access phpmyadmin', checkPhpMyAdmin);
+
+    // disable SFTP
+    it('can disable sftp', function () {
+        execSync('cloudron configure --wait -p SFTP_PORT=', { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+    });
+    it('(nosftp) can view welcome page', welcomePage);
+    it('(nosftp cannot upload file with sftp', function (done) {
+        var client = new net.Socket();
+        client.setTimeout(10000);
+
+        client.connect(2222, app.fqdn, function() {
+            client.destroy();
+            done(new Error('Connected'));
+        });
+
+        client.on('timeout', function () { client.destroy(); done(); }); // the packet just got dropped (good)
+
+        client.on('error', function (error) {
+            client.destroy();
+            done(new Error('Should have got timeout but got error:' + error.message));
+        });
+    });
+
+    it('(nosftp) cannot access phpmyadmin', function (done) {
+        superagent.get('https://' + app.fqdn + '/phpmyadmin').end(function (error, result) {
+            if (error && !error.response) return done(error); // network error
+
+            if (result.statusCode !== 404) return done('Expecting 404 error');
+
+            done();
+        });
+    });
+
+    it('uninstall app', function () {
+        execSync('cloudron uninstall --app ' + app.id, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+    });
+
+    // test update
+    it('can install app', function () {
+        execSync('cloudron install --new --wait --appstore-id lamp.cloudronapp --location ' + LOCATION, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+        var inspect = JSON.parse(execSync('cloudron inspect'));
+        app = inspect.apps.filter(function (a) { return a.location === LOCATION; })[0];
+        expect(app).to.be.an('object');
+    });
+    it('can upload file with sftp', function () {
+        // remove from known hosts in case this test was run on other apps with the same domain already
+        // if the tests fail here you want to set "HashKnownHosts no" in ~/.ssh/config
+        execSync(util.format('sed -i \'/%s/d\' -i ~/.ssh/known_hosts', app.fqdn));
+        execSync(util.format('lftp sftp://%s:%s@%s:%s  -e "set sftp:auto-confirm yes; cd public/; put test.php; bye"', process.env.USERNAME, process.env.PASSWORD, app.fqdn, app.portBindings.SFTP_PORT));
+    });
+
+    it('can update', function () {
+        execSync('cloudron install --wait --app ' + LOCATION, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+    });
+    it('can get uploaded file', uploadedFileExists);
+    it('can access phpmyadmin', checkPhpMyAdmin);
+
+    it('uninstall app', function () {
+        execSync('cloudron uninstall --app ' + app.id, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' });
+    });
+});

+ 6 - 0
test/test.php

@@ -0,0 +1,6 @@
+<?php
+
+echo "<p>this works</p>";
+echo "<p>" . getenv("APP_DOMAIN") . "</p>";
+
+?>