1.3. Adjustments (optional)

1.3.1. Adjust Tomcat/Java memory

The default memory heap size of Tomcat is very low. You can augment this in the following way:

Windows

In the bin directory of Tomcat create (if it doesn't exist) a file called setenv.bat, edit this file and add the following line:

set JAVA_OPTS=-Xmx1g

Linux

In the bin directory of Tomcat create (if it doesn't exist) a file called setenv.sh, edit this file and add the following line:

JAVA_OPTS="-Xmx1g"

1.3.2. Enable SSL/HTTPS

Enabling SSL is crucial on live systems, as it protects sensitive information, like passwords, to be sent plain over the Internet, making it readable by eavesdroppers. Generally it is advised to use a proxy server, like Apache or Nginx that handles HTTPS and then redirect the request to Tomcat. See Section 1.3.5, “Enable SSL on apache” for more details.

Otherwise, if the Tomcat server is directly used from the Internet, to enable SSL / HTTPS you first have to enable (un-comment) the https connector in the file <tomcat_home>/conf/server.xml

<Connector port="443" maxHttpHeaderSize="8192"
    maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
    enableLookups="false" disableUploadTimeout="true"
    acceptCount="100" scheme="https" secure="true"
    clientAuth="false" sslProtocol="TLS" />

Generate a key with the keytool from Java:

$JAVA_HOME/bin/keytool -genkey -alias tomcat -keyalg RSA -keystore /path/to/my/keystore

After executing this command, you will first be prompted for the keystore password. Passwords are *case sensitive*. You will also need to specify the custom password in the server.xml configuration file, as described later. Next, you will be prompted for general information about this Certificate, such as company, contact name, and so on. This information will be displayed to users who attempt to access a secure page in your application, so make sure that the information provided here matches what they will expect. Finally, you will be prompted for the key password, which is the password specifically for this Certificate (as opposed to any other Certificates stored in the same keystore file). You MUST use the same password here as was used for the keystore password itself. (Currently, the keytool prompt will tell you that pressing the ENTER key does this for you automatically). If everything was successful, you now have a keystore file with a Certificate that can be used by your server.

1.3.3. Clustering

Clustering is useful both for scaling (serving more requests) and for high availability (if a server crashes, the application continues to run). The main reason for configuring a cluster in Tomcat is to replicate HTTP sessions. Cyclos, however, doesn't use Tomcat sessions, but handles them internally. This way, there is no special Tomcat configuration to support a Cyclos cluster.

The Cyclos application, however, needs some small configurations to enable clustering. Cyclos uses Hazelcast to synchronize aspects (such as caches) between cluster servers. To enable clustering, find in cyclos.properties the line containing cyclos.clusterHandler, and set it to hazelcast.

Some extra configuration can be performed in the WEB-INF/classes/hazelcast.xml file. Basically, if the local network runs more than a single Cyclos instance, the group needs to be configured. Configure all files belonging to the same group with the same group name and password. It is also possible to change the default multicast to TCP/IP communication. Just comment the <multicast> tag and uncomment the <tcp-ip> tag, setting up the hosts / ports which will be part of the cluster. For a TCP/IP cluster, Hazelcast needs the host name / port of at least one node already in a cluster (it is not necessary to set all other nodes on each node).

Starting with Cyclos 4.14, the Hazelcast AWS plugin is bundled with Cyclos. So it is very easy to achieve auto-discovery when Cyclos is running in Amazon services. Follow the examples on the plugin website for the configuration, which is basically disabling multicast and enabling aws.

To setup high-availability at database (PostgreSQL) level, please, refer to this document.

1.3.4. Use Apache as frontend for Tomcat

You can use apache as a front-end / load balancer for the tomcat. This is very usefull when you have several domains configured on the server. There are several documentations and examples available on the internet, in our example we will use the mod_jk library for apache.

sudo apt-get install apache2 libapache2-mod-jk

The configuration is done on the file /etc/libapache2-mod-jk/workers.properties. By default this is configured to use the AJP port 8009, this is the default ajp port for tomcat, if you are using a different port you need to configure here.

On tomcat we need to enable the ajp connector. Edit the file tomcat/conf/server.xml and uncomment the AJP connector:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" secretRequired="false" />

Now on apache we need to configure the virtualhost (which normally can be found here: /etc/apache2/sites-enabled/) to use the ajp connector. On the virtualhost of your domain add the following lines:

        <IfModule mod_jk.c>
            JkMount /* ajp13_worker
            JkMount / ajp13_worker      
        </IfModule>            
        

This example uses the cyclos as ROOT application on tomcat. If you want to use something like http://www.yourdomain.com/instance_name we need to deploy cyclos on the webapps/instance_name directory and configure apache like this:

        <IfModule mod_jk.c>
            JkMount /instance_name/* ajp13_worker
            JkMount /instance_name ajp13_worker    
        </IfModule>            
        

Now restart both apache and tomcat and check if it works.

1.3.5. Enable SSL on apache

Enabling SSL is highly recommended on live systems, as it protects sensitive information, like passwords, to be sent plain over the Internet, making it readable by eavesdroppers. If you are using apache as a front-end for the tomcat first you need to enable the ssl module.

sudo a2enmod ssl

After enable the module we need to configure the virtualhost to use the ssl. On the virtualhost of your domain add the following lines:

NameVirtualhost www.yourdomain.org:443
<VirtualHost www.yourdomain.org:443>
ServerAdmin youremail@yourdomain.org
ServerName www.yourdomain.org
DocumentRoot /var/www/

<Directory />
        Options FollowSymLinks
        AllowOverride All
</Directory>
<Directory /var/www/>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all
</Directory>

ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
        AllowOverride None
        Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
        Order allow,deny
        Allow from all
</Directory>

ErrorLog /var/log/apache2/domain_error.log
LogLevel warn
CustomLog /var/log/apache2/domain_access.log combined

<IfModule mod_ssl.c>
       SSLEngine on
       SSLProtocol ALL -SSLv2 -SSLv3
       SSLHonorCipherOrder On
       SSLCipherSuite ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH
       ServerSignature Off
       BrowserMatch ".*MSIE.*" \
        nokeepalive ssl-unclean-shutdown \
        downgrade-1.0 force-response-1.0
       SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
       SSLCertificateFile /etc/ssl/certs/yourcertificate.crt
       SSLCertificateKeyFile /etc/ssl/private/yourkey.key        
</IfModule>
</VirtualHost>            
        

Now we need to generate the certificate, in this example we will use a self-signed certificate, normally used to test your new SSL implementation

Generate a Private Key

The utility "openssl" is used to generate the key and CSR. This utility comes with the OpenSSL package and is usually installed under /usr/local/ssl/bin. If the utility was installed elsewhere, these instructions will need to be adjusted accordingly.

Type the following command at the prompt:

        openssl genrsa -des3 -out yourkey.key 2048

Generate a CSR (Certificate Signing Request)

Once the private key is generated a Certificate Signing Request can be generated. The CSR is then used in one of two ways. Ideally, the CSR will be sent to a Certificate Authority, such as Thawte or Verisign who will verify the identity of the requestor and issue a signed certificate. The second option is to self-sign the CSR, which will be demonstrated in the next section.

During the generation of the CSR, you will be prompted for several pieces of information. These are the X.509 attributes of the certificate. One of the prompts will be for "Common Name (e.g., YOUR name)". It is important that this field be filled in with the fully qualified domain name of the server to be protected by SSL. If the website to be protected will be https://public.akadia.com, then enter public.akadia.com at this prompt. The command to generate the CSR is as follows:

            openssl req -new -key yourkey.key -out yourcertificate.csr
        
            Country Name (2 letter code) [GB]:CH
            State or Province Name (full name) [Berkshire]:Bern
            Locality Name (eg, city) [Newbury]:Oberdiessbach
            Organization Name (eg, company) [My Company Ltd]:Akadia AG
            Organizational Unit Name (eg, section) []:Information Technology
            Common Name (eg, your name or your server hostname) []:public.akadia.com
            Email Address []:martin dot zahn at akadia dot ch
            Please enter the following extra attributes
            to be sent with your certificate request
            A challenge password []:
            An optional company name []:
       

Remove Passphrase from Key

One unfortunate side-effect of the pass-phrased private key is that Apache will ask for the pass-phrase each time the web server is started. Obviously this is not necessarily convenient as someone will not always be around to type in the pass-phrase, such as after a reboot or crash. mod_ssl includes the ability to use an external program in place of the built-in pass-phrase dialog, however, this is not necessarily the most secure option either. It is possible to remove the Triple-DES encryption from the key, thereby no longer needing to type in a pass-phrase. If the private key is no longer encrypted, it is critical that this file only be readable by the root user! If your system is ever compromised and a third party obtains your unencrypted private key, the corresponding certificate will need to be revoked. With that being said, use the following command to remove the pass-phrase from the key:

    cp yourkey.key yourkey.key.org
    openssl rsa -in yourkey.key.org -out yourkey.key
    

The newly created yourkey.key file has no more passphrase in it.

Generating a Self-Signed Certificate

At this point you will need to generate a self-signed certificate because you either don't plan on having your certificate signed by a CA, or you wish to test your new SSL implementation while the CA is signing your certificate. This temporary certificate will generate an error in the client browser to the effect that the signing certificate authority is unknown and not trusted.

To generate a temporary certificate which is good for 365 days, issue the following command:

    openssl x509 -req -days 365 -in yourcertificate.csr -signkey yourkey.key \
    -out yourcertificate.crt
    

Installing the Private Key and Certificate

When Apache with mod_ssl is installed, it creates several directories in the Apache config directory. The location of this directory will differ depending on how Apache was compiled.

    cp yourcertificate.crt /etc/ssl/certs/yourcertificate.crt
    cp yourkey.key /etc/ssl/private/yourkey.key
    

Now restart apache and check if it works.

1.3.6. Configuring Cyclos to work behind a proxy / load balancer

The easiest configuration for a load balancer is Apache connecting to Tomcat using the AJP protocol. In this case, the original request is forwarded to Tomcat as is, keeping the original client IP address and URL. However, in most other cases, the load balancer works as a proxy, sending a new HTTP to Tomcat and forwarding the response to the client. Examples of such proxies include Apache with mod_proxy, Nginx, haproxy and Amazon's Elastic Load Balancing. In either way, generally the proxy will have the server certificate and will terminate the SSL connection with the client. The second request, to Tomcat, will generally use a plain HTTP connection.

That means the client IP address received by Cyclos, as well as the request URL are different than the original request performed by the client. As Cyclos uses the client IP for logging and blocking in case of abuse, this would lead Cyclos to block the proxy, preventing any further request. However, the proxy will add some extra request headers, with information about the original request. Cyclos then needs to be configured to read both the IP address and which was the connection protocol used by the original request (HTTP or HTTPS) from those headers, instead of directly from the incoming HTTP request. As such, the following settings in cyclos.properties are needed:

  • cyclos.header.remoteAddress: Specifies the name of the header which contains the original client's IP address. The name of this header is usually X-Forwarded-For.
  • cyclos.header.protocol: Specifies the protocol name (http or https) used on the original request. The name of this header is usually X-Forwarded-Proto.

The following cases are handled by Cyclos to match a specific network / configuration from the request URL:

  • A request to Tomcat using the root URL specified in a parent configuration (normally the network default). For example, if the network default configuration's root URL is http://cyclos-net.com, any requests to http://cyclos-net.com/* will match that configuration;
  • A request to Tomcat using the root URL specified in a parent configuration plus a specific path of an inherited configuration. Following the previous example, if the child configuration has a custom path of config, any requests to http://cyclos-net.com/config/* will match that configuration;
  • If no custom URL is matched, requests having first subpath (after the web application context path) equals the network internal name. For example, if Cyclos is deployed in a Tomcat under the context path cyclos and it has a network called main, any requests to http://localhost:8080/instance_name/main/* will match this network;
  • Same as previous, but with a specific configuration path. Following the previous example, if that network has a configuration with path config, requests to http://localhost:8080/instance_name/main/config/* will match this configuration;
  • Also, the name global is reserved as a network internal name. For example, requests to http://localhost:8080/instance_name/global/* will be considered in global mode;
  • Still if no custom URL is matched, if no network internal name is given and there is a default network, the network internal name can be omitted. For example, requests to http://localhost:8080/instance_name/* will be considered in the default network.

When Tomcat is behind a proxy, probably it will never receive requests using the original public URL. Hence, only matching by network (and optionally, configuration paths) will be used. Still, networks should correctly set the root url in their configurations because they are used to generate full URLs in the server side, for example, when sending links in e-mails or resolving image URLs in rich texts.

Following are common cases that could be configured for a proxy, all assuming Cyclos is deployed to a Tomcat accessible by the proxy via http://tomcat:8080/instance_name:

  • The proxy handles the (sub)domain of a specific network. For example, it handles requests to https://main.my-cyclos.com and forwards them to http://tomcat:8080/instance_name/main. The network in Cyclos must have the internal name main in this case;
  • The proxy handles a specific (sub)domain for the global mode. For example, it handles requests to https://global.my-cyclos.com and forwards them to http://tomcat:8080/instance_name/global;
  • The proxy handles a multi-network entry point on a (sub)domain of a specific network. For example, it handles requests to https://www.my-cyclos.com and forwards them to http://tomcat:8080/instance_name. So, requests to https://www.my-cyclos.com/main will match the main network, while requests to https://www.my-cyclos.com/alt will match the alt network.

1.3.6.1. Reserved names that cannot be used in proxy paths

There are reserved paths in Cyclos that cannot be used as path in proxies. For example, a proxy could handle requests to https://www.my-project.com/app and redirect them to http://tomcat:8080/instance_name. In this case, the /app path part is public, used by clients, but never visible on Tomcat. To handle such cases, the list of reserved paths is used to generate the correct URIs for scripts and stylesheets, and having the any of the reserved paths in the proxy would prevent the URI generation from working correctly.

The list of reserved paths is:

cyclos.gwt
fonts
js
pay
consent
classic
ui
.well-known
robots.txt
sitemap.xml
sitemap-index.xml
sitemap.xstl
network-information-id.html
activate-access-client
external-redirect-callback
identity
run
content
web-rpc
java-rpc
api
sms
push-notifications
global
redirect
mobile-redirect

1.3.7. Enabling Google Maps

Cyclos supports displaying maps using Google Maps. This has to be enabled in the Cyclos configuration. Starting in June 2016, the Google Maps requires API Keys in order to use. For details on the free daily quota for map views and geocode requests, see this page. As of March 2018, the free quota is 25,000 map views and 2,500 geocode requests. Cyclos uses the geocode requests to map the user-informed address fields to a position (latitude/longitude).

There are 2 API keys that can be set in the Cyclos configuration: The server-side API key and the browser API-key. Each one needs to be generated in the API Manager.

  • Enabling the APIs: on the "Library" menu, search for the following APIs and enable them: "Maps JavaScript API", "Maps Static API" and "Geocoding API";
  • Creating the API keys: on the "Credentials" menu, choose "Create credentials", then choose "API key". Choose "Server key" and specify a name for it. Save and then create a new one, this time as "Browser key".

Once the API keys are ready, they can be copied / pasted into the Cyclos configuration, on the corresponding "Google maps server API" and "Google maps browser API" fields. The API Manager allows monitoring of requests performed by each API.

On the main Cyclos web application, addresses are geolocated on the client-side, before saving it. In such cases, the browser API key is used. However, sometimes addresses can be saved without being geolocated, either by thirdy-party software or by importing new users into Cyclos. In such cases, a background task will attempt to geolocate them (might take up to 24 hours for this) using the server API key.

1.3.8. reCAPTCHA

Starting with Cyclos 4.15, it has been added the support for reCAPTCHA v2, which displays the "I am not a robot" checkbox. As of 2021, it offers a free usage for up to 1 million requests per month. As CAPTCHAS are used in Cyclos only on registration and on forgot password requests, it should be enough for most systems.

To enable it, first you need to register a new site in reCAPTCHA. Select the v2 on reCAPTCHA type. Then add your domain, accept the termos of service and submit. It will display 2 keys: the site key and the secret key. Then, in Cyclos configuration form, under the CAPTCHA section, select reCAPTCHA v2 as provider and paste both keys to the corresponding fields.

1.3.9. External content storage

1.3.9.1. Storage types

You have the possibility to configure the storage type to be used for images, documents, imported files and custom fields of type image/file. Additionally, in the configuration details page you can define a specific storage directory for individual (i.e user) documents and in the custom field details page do the same for custom fields. The 'Amazon S3', 'Google Cloud Storage' and 'Filesystem' implementations has support for storage directories, please read below to know how to configure it.

Cyclos comes with four implementations out of the box:

  • Database: the content is stored in conjunction with all data in the database. This is the default implementation.
  • File system: the content is stored outside the database in specific paths.
  • Amazon S3: Amazon Simple Storage Service, the content is stored outside the database in specific buckets.
  • Google Cloude storage: the content is stored outside the database in specific bucket.

Besides the built-in implementations you can create your own custom implementation. To do that you must create a Java class implementing org.cyclos.impl.storage.StoredFileContentManager

The following are the properties you need to configure in the cyclos.properties

Storage type property

Database storage specific properties

There are no additional properties to be configured.

File system storage specific properties

  • cyclos.storedFileContentManager.rootDir: the root directory where the contents will be stored.
  • cyclos.storedFileContentManager.directories: comma separated list of folder (storage directory) names that will be created as children of the rootDir and where individual documents and image/file custom field values can be stored.
  • cyclos.storedFileContentManager.maxSubDirs: tha maximum count of directories to be created below the root directory or a specific storage directory where the content will be stored.

Amazon S3 storage specific properties:

  • cyclos.storedFileContentManager.bucketName: the name of the default bucket that will be created (if it doesn't exist) and where the content will be stored.
  • cyclos.storedFileContentManager.regionName: the name of the default region where the buckets will be created.
  • cyclos.storedFileContentManager.directories: comma separated list of bucket (storage directory) names where individual documents and image/file custom field values can be stored.
  • cyclos.storedFileContentManager.accessKeyId: the AWS access key.
  • cyclos.storedFileContentManager.secretAccessKey: the AWS secret access key.

If you need to create a bucket in a different region than the default one then you need to define a property of the form:
cyclos.storedFileContentManager.regionName.bucket_name=specific_region_name

Google Cloud storage specific properties

  • cyclos.storedFileContentManager.bucketName: the name of the bucket created on Goolge Cloud, and where the content will be stored.
  • cyclos.storedFileContentManager.credentialsFile: path to JSON key file downloaded when create the service account

1.3.9.2. Storage migrator utility class

If you already have a running Cyclos instance and want to change the storage type to use then there is an utility class that will allow to migrate the contents from the current storage to a new one. To use it you must have Java configured in your path then go to the <TOMCAT_DIR>/webapps/<cyclos_dir> directory and execute:

java -cp "WEB-INF/classes:../../lib/*:WEB-INF/lib/*" \
    org.cyclos.impl.storage.utils.StoredFileContentMigrator

and just follow the instructions shown in the usage help.

1.3.10. Using Elasticsearch

If the database performance is unacceptable when performing complex searches on users, advertisements or records, using keywords or geo-distance filters, it is advised to use Elasticsearch to handle such cases. For more details, see Section 2.1, “Using Elasticsearch”.

1.3.11. Logging

By default, Cyclos logs access to services, as well as background tasks, to files. But for production systems, starting with Cyclos 4.11, it is recommended to set logging to an external database. The log destination is configured in cyclos.properties, through the cyclos.log property. The value can be either file, db or none.

Other properties depend on the log provider:

  • For cyclos.log=file:
    • cyclos.log.dir: The directory where to write logs. Supports %t as the temporary directory, %w as the web application directory and %n as the network internal name.
    • cyclos.log.maxFiles: The log files are rotating. This setting indicates the maximum number of files per network.
    • cyclos.log.maxFileSize: The maximum size per log file. Examples are 2M or 500K.
  • For cyclos.log=db: All properties under cyclos.log.datasource are used to configure the datasource, just like the regular cyclos.datasource-prefixed properties. It can be configured to point to an external PostgreSQL database or to the same database used by Cyclos. If an external database, just like the regular database, it must be previously created, but the tables are created automatically on the server startup.

Logs are, by default, asynchronous, so the requests are not delayed until the log is written. This is controlled by the cyclos.log.threads property, which sets the number of threads used to concurrently write logs. However, if the server crashes, some log entries may be lost. If the number of threads is set to zero, logs will be synchronous, delaying each response, specially under heavy load, but guaranteeing that logs are written. If using a database log, make sure to set the maximum number of connections equal to the number of threads. When using synchronous logging, the maximum connections to the log database will also limit the number of concurrent request, so, an extra care is needed.

A final note on the log database: there are currently 2 tables - service_logs and task_logs. The service_logs store a row each time a client calls any Cyclos service, while the task_logs stores a row for each background task execution. The tables have no indexes, and will store parameters and results as the PostgreSQL's JSON type, so that INSERT operations run as fast as possible. However, for searching data, the JSONB type (binary JSON) is more efficient, and supports indexing. When searching the table with too many logs, instead of searching directly on service_logs, it is recommended to create a new table, and query it instead. This new table should have the same columns as service_logs, but with JSONB columns instead. Also, add indexes according to your query. A final note on log tables is that they tend to quickly grow in size, so you may need to periodically (according to the database data volume) move old data to another database in order to not impact the logging performance. Also, don't forget to vacuum the table after deleting old records.