<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://wiki.socialstack.dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=80.229.4.125</id>
	<title>SocialStack - User contributions [en-gb]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.socialstack.dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=80.229.4.125"/>
	<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Special:Contributions/80.229.4.125"/>
	<updated>2026-10-16T02:38:52Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.36.2</generator>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=682</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=682"/>
		<updated>2024-11-22T09:57:38Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates (Manual Steps for Cloudfare Hosted) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps with http access)==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates, adding your domain names as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /var/www/prod/nginx.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssl_certificate /etc/ssl/cert/fullchain.pem;&lt;br /&gt;
ssl_certificate_key /etc/ssl/cert/privkey.pem;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next you need to use our new files to replace the existing files&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
rm *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem /etc/ssl/cert/fullchain.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem /etc/ssl/cert/privkey.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps for Cloudfare Hosted) ==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-dns-cloudflare&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Obtain an api token with the permissions from cloudflare&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
Zone: DNS:Edit&lt;br /&gt;
Zone Resources: Include the specific zone(s) for which you want to manage DNS records.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create a config file containing the following &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nano /etc/letsencrypt/cloudflare.ini&lt;br /&gt;
chown root:root /etc/letsencrypt/cloudflare.ini&lt;br /&gt;
chmod 600 /etc/letsencrypt/cloudflare.ini&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Request the certificate adding your domains as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly \&lt;br /&gt;
  --dns-cloudflare \&lt;br /&gt;
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \&lt;br /&gt;
  -d www.test.com \&lt;br /&gt;
  -d test.com&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/www.test.com/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/www.test.com/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/www.test.com/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/www.test.com/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /var/www/prod/nginx.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssl_certificate /etc/ssl/cert/fullchain.pem;&lt;br /&gt;
ssl_certificate_key /etc/ssl/cert/privkey.pem;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next you need to use our new files to replace the existing files&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
rm *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/www.test.com/fullchain.pem /etc/ssl/cert/fullchain.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/www.test.com/privkey.pem /etc/ssl/cert/privkey.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain.pem -&amp;gt; /etc/letsencrypt/live/www.test.com/fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey.pem -&amp;gt; /etc/letsencrypt/live/www.test.com/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=681</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=681"/>
		<updated>2024-11-22T09:56:16Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps with http access)==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates, adding your domain names as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /var/www/prod/nginx.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssl_certificate /etc/ssl/cert/fullchain.pem;&lt;br /&gt;
ssl_certificate_key /etc/ssl/cert/privkey.pem;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next you need to use our new files to replace the existing files&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
rm *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem /etc/ssl/cert/fullchain.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem /etc/ssl/cert/privkey.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps for Cloudfare Hosted) ==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-dns-cloudflare&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Obtain an api token with the permissions from cloudflare&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
Zone: DNS:Edit&lt;br /&gt;
Zone Resources: Include the specific zone(s) for which you want to manage DNS records.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates, adding your domain names as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create a config file containing the following &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nano /etc/letsencrypt/cloudflare.ini&lt;br /&gt;
chown root:root /etc/letsencrypt/cloudflare.ini&lt;br /&gt;
chmod 600 /etc/letsencrypt/cloudflare.ini&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Request the certificate &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly \&lt;br /&gt;
  --dns-cloudflare \&lt;br /&gt;
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \&lt;br /&gt;
  -d www.test.com \&lt;br /&gt;
  -d test.com&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/www.test.com/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/www.test.com/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/www.test.com/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/www.test.com/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /var/www/prod/nginx.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssl_certificate /etc/ssl/cert/fullchain.pem;&lt;br /&gt;
ssl_certificate_key /etc/ssl/cert/privkey.pem;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next you need to use our new files to replace the existing files&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
rm *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/www.test.com/fullchain.pem /etc/ssl/cert/fullchain.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/www.test.com/privkey.pem /etc/ssl/cert/privkey.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain.pem -&amp;gt; /etc/letsencrypt/live/www.test.com/fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey.pem -&amp;gt; /etc/letsencrypt/live/www.test.com/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=680</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=680"/>
		<updated>2024-11-22T09:55:22Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates (Manual Steps) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps with http access)==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates, adding your domain names as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /var/www/prod/nginx.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssl_certificate /etc/ssl/cert/fullchain.pem;&lt;br /&gt;
ssl_certificate_key /etc/ssl/cert/privkey.pem;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next you need to use our new files to replace the existing files&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
rm *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem /etc/ssl/cert/fullchain.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem /etc/ssl/cert/privkey.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=679</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=679"/>
		<updated>2024-11-22T09:52:50Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates (Manual Steps) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps)==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates, adding your domain names as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /var/www/prod/nginx.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssl_certificate /etc/ssl/cert/fullchain.pem;&lt;br /&gt;
ssl_certificate_key /etc/ssl/cert/privkey.pem;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next you need to use our new files to replace the existing files&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
rm *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem /etc/ssl/cert/fullchain.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem /etc/ssl/cert/privkey.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=678</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=678"/>
		<updated>2024-07-29T15:40:11Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Get openssl */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
So something like OpenSSL-Win64-1-1-1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
c:\Program Files\OpenSSL-Win64-1-1-1\bin&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\distribution.key -out d:\\distribution.csr -subj &amp;quot;/emailAddress=you@domain.com, CN=Your Company, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the distribution.csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\distribution.key -in d:\\distribution.pem -out d:\\distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example yourapp.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier&lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 password $(p12passwd)&lt;br /&gt;
&lt;br /&gt;
You should now have 2 files in the relevant secure files area of the pipeline&lt;br /&gt;
&lt;br /&gt;
*distribution.p12 &lt;br /&gt;
*yourapp.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Ensure they are correctly referenced in the build pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;yourapp.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=677</id>
		<title>Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=677"/>
		<updated>2024-07-15T12:40:02Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Community Backend Testing */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setting up firebase ==&lt;br /&gt;
=== Get the package details from your application ===&lt;br /&gt;
&lt;br /&gt;
Get the package name from the cordova config.xml&lt;br /&gt;
&lt;br /&gt;
for example &amp;lt;widget android-packageName=&amp;quot;com.thermomix.comapp&amp;quot; id=&amp;quot;com.thermomix.comapp&amp;quot; version=&amp;quot;1.0.0&amp;quot; xmlns=&amp;quot;http://www.w3.org/ns/widgets&amp;quot; xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; xmlns:cdv=&amp;quot;http://cordova.apache.org/ns/1.0&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So in this case &amp;quot;com.thermomix.comapp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Setup a new project in firebase ===&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/web/setup&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/android/setup&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/2060582-how-to-set-up-your-firebase-certificates-for-ios-and-android&lt;br /&gt;
&lt;br /&gt;
Create apps for ios, android and web as required&lt;br /&gt;
&lt;br /&gt;
=== Update your application ===&lt;br /&gt;
&lt;br /&gt;
Add the following plugin into the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;platform name=&amp;quot;android&amp;quot;&amp;gt;section you will need to reference the icon colours for notifications&lt;br /&gt;
&lt;br /&gt;
&amp;lt;config-file parent=&amp;quot;/manifest/application/&amp;quot; target=&amp;quot;app/src/main/AndroidManifest.xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_icon&amp;quot; android:resource=&amp;quot;@drawable/notification_icon&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_color&amp;quot; android:resource=&amp;quot;@color/defaultNotificationColor&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/config-file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;plugin name=&amp;quot;cordova-plugin-firebase-messaging&amp;quot; spec=&amp;quot;~4.3.1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;FIREBASE_MESSAGING_VERSION&amp;quot; value=&amp;quot;20.1.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;ANDROIDX_CORE_VERSION&amp;quot; value=&amp;quot;1.0.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/plugin&amp;gt;&lt;br /&gt;
&lt;br /&gt;
see the official plugin page for more info &lt;br /&gt;
&lt;br /&gt;
https://www.npmjs.com/package/cordova-plugin-firebase-messaging&lt;br /&gt;
&lt;br /&gt;
=== Get the firebase config files ===&lt;br /&gt;
&lt;br /&gt;
See https://support.google.com/firebase/answer/7015592?hl=en#zippy=%2Cin-this-article for more info but basically&lt;br /&gt;
&lt;br /&gt;
Edit the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
within platform name = andriod&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;google-services.json&amp;quot; target=&amp;quot;app/google-services.json&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
within platform name = ios&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;GoogleService-Info.plist&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Download the 2 config files from the firebase application and copy then into the App folder containing the cordova config&lt;br /&gt;
&lt;br /&gt;
Get a copy of firebase-messaging-sw.js and copy it into the UI/public folder&lt;br /&gt;
&lt;br /&gt;
=== Pull in the required javascript files ===&lt;br /&gt;
&lt;br /&gt;
Update the htmlservice config to include &lt;br /&gt;
&lt;br /&gt;
  &amp;quot;EndHeadScripts&amp;quot;: [&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot; : &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot;: &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-messaging.js&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ],&lt;br /&gt;
&lt;br /&gt;
=== Add a UI handler for the messaging ===&lt;br /&gt;
&lt;br /&gt;
Install the push notifications UI module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install UI/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module has the main job of registering for push notifications when the device is either in a native app or via the web. A device on the web will see the permission request for push notifications.&lt;br /&gt;
&lt;br /&gt;
You&amp;#039;ll need to edit the PushNotifications.js file to include the correct firebase config from the web application. This config is primarily used when your site is running from the web.&lt;br /&gt;
&lt;br /&gt;
=== Add a settings page to your local wep app ===&lt;br /&gt;
&lt;br /&gt;
This will allow you to test the token refresh process in a webapp, and quickly confirm that all looks ok&lt;br /&gt;
&lt;br /&gt;
You need to add a page with the following component&lt;br /&gt;
&lt;br /&gt;
UI/PushSettings&lt;br /&gt;
{\n\t\&amp;quot;c\&amp;quot;: {\n\t\t\&amp;quot;t\&amp;quot;: \&amp;quot;UI/PushSettings\&amp;quot;,\n\t\t\&amp;quot;d\&amp;quot;: {},\n\t\t\&amp;quot;i\&amp;quot;: 2\n\t},\n\t\&amp;quot;i\&amp;quot;: 3\n}&lt;br /&gt;
&lt;br /&gt;
Once added, when you visit the page it should try and get a token from firebase and then send an update to the community via /api.ashx/v2/mwa/user/device/add.json&lt;br /&gt;
&lt;br /&gt;
This will add a device entry to the community and allow notifications to be passed back to the web app&lt;br /&gt;
&lt;br /&gt;
There are some console log entries to confirm it has been added&lt;br /&gt;
&lt;br /&gt;
=== First Test Time To Get The Client Key ===&lt;br /&gt;
&lt;br /&gt;
If all goes well, you should now be able to get a key in PushNotifications.js&lt;br /&gt;
&lt;br /&gt;
see section&lt;br /&gt;
&lt;br /&gt;
.then((currentToken)&lt;br /&gt;
&lt;br /&gt;
You should get some data looking like this, the key can then be used in your backend to send notifications to the web app&lt;br /&gt;
&lt;br /&gt;
notificationKey&lt;br /&gt;
&lt;br /&gt;
xxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&lt;br /&gt;
&lt;br /&gt;
notificationKeyType&lt;br /&gt;
&lt;br /&gt;
web&lt;br /&gt;
&lt;br /&gt;
=== Community Backend Authentication ===&lt;br /&gt;
&lt;br /&gt;
Create a service account via https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project&lt;br /&gt;
&lt;br /&gt;
Choose the application&lt;br /&gt;
&lt;br /&gt;
Click on the service account (will initial say no keys under key id)&lt;br /&gt;
&lt;br /&gt;
Click on keys&lt;br /&gt;
&lt;br /&gt;
Create a new key (export as json) and copy the contents into the mwa core config -&amp;gt; messaging&lt;br /&gt;
&lt;br /&gt;
The key should look something like &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;service_account&amp;quot;,&lt;br /&gt;
  &amp;quot;project_id&amp;quot;: &amp;quot;com-theintelligence-app&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key_id&amp;quot;: &amp;quot;31dc7cc70581a64dd2fdcb8df2d6179b018a9698&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key&amp;quot;: &amp;quot;-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n&amp;quot;,&lt;br /&gt;
  &amp;quot;client_email&amp;quot;: &amp;quot;firebase-adminsdk-jgnr7@com-theintelligence-app.iam.gserviceaccount.com&amp;quot;,&lt;br /&gt;
  &amp;quot;client_id&amp;quot;: &amp;quot;108818059171631247824&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_uri&amp;quot;: &amp;quot;https://accounts.google.com/o/oauth2/auth&amp;quot;,&lt;br /&gt;
  &amp;quot;token_uri&amp;quot;: &amp;quot;https://oauth2.googleapis.com/token&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_provider_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/oauth2/v1/certs&amp;quot;,&lt;br /&gt;
  &amp;quot;client_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-jgnr7%40com-theintelligence-app.iam.gserviceaccount.com&amp;quot;&lt;br /&gt;
?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Community Backend Testing ===&lt;br /&gt;
&lt;br /&gt;
Once all is setup and the app is running, it should register itself with the community for push notifications&lt;br /&gt;
&lt;br /&gt;
You can check this by running the following in the editor sandbox on the community, passing in the relevant userid &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
$mwa_v1_push.List({userid})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should get a list of the devices/browsers linked to the user (y4es browser as running as ss in browser will also register)&lt;br /&gt;
&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[List-of-UserDevice]&lt;br /&gt;
&lt;br /&gt;
[0] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;30320502008403&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;eRLAz76oKlkaclmsOIHjqj:APA91bEhrSbBLADxMLagqucznK5W06dx0JoMxGGwH-XVEZD6kdKdJll7JjVKjHJgpJbc8lSiq_on1j1_t0Puthn7wNKPJpxUnhSNxEVMkBMcq2T_CaZ0YyAGIljgkvp86OC5cLTz1r-j&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;web&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
[1] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;09965002900340&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;dhC63MgrSyqsi1ilkkwnax:APA91bHuPEAeiFiSHnuTPUs7cQ1J6EP_Fl0x3zxX8jaxqBhQPQKTxOPl72KbH1ZR3-h1_WWI4RzFX2k3RwXcwoX8fNm_rHFm5OOeUGoBV3BkwXB3keVbCXDRWx0AWApxZZoK2x1-78kT&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;firebase&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
[2] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;00000019000&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;cldvQ2LES5C36bo9QQ52MQ:APA91bElxPKDdbt_rBRJZsMf02JqrK7RdgBfZm1pYCDKcmVSmwYok1exNWpaU6If5B0yTRlB6o7RnHFWybnLF2zNqXvmE6S6_Lb7nfshK0yTB_qCzQFlOsAdwdoLB8L-tNXMgLnp1cj1&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;firebase&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
Count: 3 [Int32]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the app itself you can see the device id via the about screen&lt;br /&gt;
&lt;br /&gt;
To send a test message to your device you can use the sandbox again &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
$mwa_v1_push.Test({notificationKey})&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Backend Integration ===&lt;br /&gt;
&lt;br /&gt;
Start by installing the push notifications module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates the UserDevice type and also provides the PushNotificationService which can be used to send push notifications to target devices.&lt;br /&gt;
&lt;br /&gt;
Internally the PushNotificationService batches notifications together so can be used for large amounts of push notification traffic too.&lt;br /&gt;
&lt;br /&gt;
=== IOS APNS Key Setup ===&lt;br /&gt;
&lt;br /&gt;
To access iOS clients you will need to get a key from the Apple developer site and upload it into firebase:&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/5760994-how-to-set-up-your-apple-push-notification-key-for-your-ios-firebase-certificate#:~:text=How%20do%20I%20upload%20my%20APNs%20key%20to,Step%203.%20Click%20on%20Certificates%2C%20IDs%20%26%20Profiles&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
see the devops projects THE-APP-MCP AND THE-APP-MAP&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=676</id>
		<title>Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=676"/>
		<updated>2024-07-15T12:10:36Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Community Backend Testing */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setting up firebase ==&lt;br /&gt;
=== Get the package details from your application ===&lt;br /&gt;
&lt;br /&gt;
Get the package name from the cordova config.xml&lt;br /&gt;
&lt;br /&gt;
for example &amp;lt;widget android-packageName=&amp;quot;com.thermomix.comapp&amp;quot; id=&amp;quot;com.thermomix.comapp&amp;quot; version=&amp;quot;1.0.0&amp;quot; xmlns=&amp;quot;http://www.w3.org/ns/widgets&amp;quot; xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; xmlns:cdv=&amp;quot;http://cordova.apache.org/ns/1.0&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So in this case &amp;quot;com.thermomix.comapp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Setup a new project in firebase ===&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/web/setup&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/android/setup&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/2060582-how-to-set-up-your-firebase-certificates-for-ios-and-android&lt;br /&gt;
&lt;br /&gt;
Create apps for ios, android and web as required&lt;br /&gt;
&lt;br /&gt;
=== Update your application ===&lt;br /&gt;
&lt;br /&gt;
Add the following plugin into the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;platform name=&amp;quot;android&amp;quot;&amp;gt;section you will need to reference the icon colours for notifications&lt;br /&gt;
&lt;br /&gt;
&amp;lt;config-file parent=&amp;quot;/manifest/application/&amp;quot; target=&amp;quot;app/src/main/AndroidManifest.xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_icon&amp;quot; android:resource=&amp;quot;@drawable/notification_icon&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_color&amp;quot; android:resource=&amp;quot;@color/defaultNotificationColor&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/config-file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;plugin name=&amp;quot;cordova-plugin-firebase-messaging&amp;quot; spec=&amp;quot;~4.3.1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;FIREBASE_MESSAGING_VERSION&amp;quot; value=&amp;quot;20.1.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;ANDROIDX_CORE_VERSION&amp;quot; value=&amp;quot;1.0.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/plugin&amp;gt;&lt;br /&gt;
&lt;br /&gt;
see the official plugin page for more info &lt;br /&gt;
&lt;br /&gt;
https://www.npmjs.com/package/cordova-plugin-firebase-messaging&lt;br /&gt;
&lt;br /&gt;
=== Get the firebase config files ===&lt;br /&gt;
&lt;br /&gt;
See https://support.google.com/firebase/answer/7015592?hl=en#zippy=%2Cin-this-article for more info but basically&lt;br /&gt;
&lt;br /&gt;
Edit the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
within platform name = andriod&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;google-services.json&amp;quot; target=&amp;quot;app/google-services.json&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
within platform name = ios&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;GoogleService-Info.plist&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Download the 2 config files from the firebase application and copy then into the App folder containing the cordova config&lt;br /&gt;
&lt;br /&gt;
Get a copy of firebase-messaging-sw.js and copy it into the UI/public folder&lt;br /&gt;
&lt;br /&gt;
=== Pull in the required javascript files ===&lt;br /&gt;
&lt;br /&gt;
Update the htmlservice config to include &lt;br /&gt;
&lt;br /&gt;
  &amp;quot;EndHeadScripts&amp;quot;: [&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot; : &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot;: &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-messaging.js&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ],&lt;br /&gt;
&lt;br /&gt;
=== Add a UI handler for the messaging ===&lt;br /&gt;
&lt;br /&gt;
Install the push notifications UI module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install UI/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module has the main job of registering for push notifications when the device is either in a native app or via the web. A device on the web will see the permission request for push notifications.&lt;br /&gt;
&lt;br /&gt;
You&amp;#039;ll need to edit the PushNotifications.js file to include the correct firebase config from the web application. This config is primarily used when your site is running from the web.&lt;br /&gt;
&lt;br /&gt;
=== Add a settings page to your local wep app ===&lt;br /&gt;
&lt;br /&gt;
This will allow you to test the token refresh process in a webapp, and quickly confirm that all looks ok&lt;br /&gt;
&lt;br /&gt;
You need to add a page with the following component&lt;br /&gt;
&lt;br /&gt;
UI/PushSettings&lt;br /&gt;
{\n\t\&amp;quot;c\&amp;quot;: {\n\t\t\&amp;quot;t\&amp;quot;: \&amp;quot;UI/PushSettings\&amp;quot;,\n\t\t\&amp;quot;d\&amp;quot;: {},\n\t\t\&amp;quot;i\&amp;quot;: 2\n\t},\n\t\&amp;quot;i\&amp;quot;: 3\n}&lt;br /&gt;
&lt;br /&gt;
Once added, when you visit the page it should try and get a token from firebase and then send an update to the community via /api.ashx/v2/mwa/user/device/add.json&lt;br /&gt;
&lt;br /&gt;
This will add a device entry to the community and allow notifications to be passed back to the web app&lt;br /&gt;
&lt;br /&gt;
There are some console log entries to confirm it has been added&lt;br /&gt;
&lt;br /&gt;
=== First Test Time To Get The Client Key ===&lt;br /&gt;
&lt;br /&gt;
If all goes well, you should now be able to get a key in PushNotifications.js&lt;br /&gt;
&lt;br /&gt;
see section&lt;br /&gt;
&lt;br /&gt;
.then((currentToken)&lt;br /&gt;
&lt;br /&gt;
You should get some data looking like this, the key can then be used in your backend to send notifications to the web app&lt;br /&gt;
&lt;br /&gt;
notificationKey&lt;br /&gt;
&lt;br /&gt;
xxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&lt;br /&gt;
&lt;br /&gt;
notificationKeyType&lt;br /&gt;
&lt;br /&gt;
web&lt;br /&gt;
&lt;br /&gt;
=== Community Backend Authentication ===&lt;br /&gt;
&lt;br /&gt;
Create a service account via https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project&lt;br /&gt;
&lt;br /&gt;
Choose the application&lt;br /&gt;
&lt;br /&gt;
Click on the service account (will initial say no keys under key id)&lt;br /&gt;
&lt;br /&gt;
Click on keys&lt;br /&gt;
&lt;br /&gt;
Create a new key (export as json) and copy the contents into the mwa core config -&amp;gt; messaging&lt;br /&gt;
&lt;br /&gt;
The key should look something like &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;service_account&amp;quot;,&lt;br /&gt;
  &amp;quot;project_id&amp;quot;: &amp;quot;com-theintelligence-app&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key_id&amp;quot;: &amp;quot;31dc7cc70581a64dd2fdcb8df2d6179b018a9698&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key&amp;quot;: &amp;quot;-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n&amp;quot;,&lt;br /&gt;
  &amp;quot;client_email&amp;quot;: &amp;quot;firebase-adminsdk-jgnr7@com-theintelligence-app.iam.gserviceaccount.com&amp;quot;,&lt;br /&gt;
  &amp;quot;client_id&amp;quot;: &amp;quot;108818059171631247824&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_uri&amp;quot;: &amp;quot;https://accounts.google.com/o/oauth2/auth&amp;quot;,&lt;br /&gt;
  &amp;quot;token_uri&amp;quot;: &amp;quot;https://oauth2.googleapis.com/token&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_provider_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/oauth2/v1/certs&amp;quot;,&lt;br /&gt;
  &amp;quot;client_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-jgnr7%40com-theintelligence-app.iam.gserviceaccount.com&amp;quot;&lt;br /&gt;
?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Community Backend Testing ===&lt;br /&gt;
&lt;br /&gt;
Once all is setup and the app is running, it should register itself with the community for push notifications&lt;br /&gt;
&lt;br /&gt;
You can check this by running the following in the editor sandbox on the community, where xxxx is the userid &lt;br /&gt;
&lt;br /&gt;
$mwa_v1_push.List(xxxx)&lt;br /&gt;
You should get a list of the devices/browsers linked to the user (y4es browser as running as ss in browser will also register)&lt;br /&gt;
&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[List-of-UserDevice]&lt;br /&gt;
&lt;br /&gt;
[0] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;30320502008403&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;eRLAz76oKlkaclmsOIHjqj:APA91bEhrSbBLADxMLagqucznK5W06dx0JoMxGGwH-XVEZD6kdKdJll7JjVKjHJgpJbc8lSiq_on1j1_t0Puthn7wNKPJpxUnhSNxEVMkBMcq2T_CaZ0YyAGIljgkvp86OC5cLTz1r-j&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;web&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
[1] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;09965002900340&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;dhC63MgrSyqsi1ilkkwnax:APA91bHuPEAeiFiSHnuTPUs7cQ1J6EP_Fl0x3zxX8jaxqBhQPQKTxOPl72KbH1ZR3-h1_WWI4RzFX2k3RwXcwoX8fNm_rHFm5OOeUGoBV3BkwXB3keVbCXDRWx0AWApxZZoK2x1-78kT&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;firebase&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
[2] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;00000019000&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;cldvQ2LES5C36bo9QQ52MQ:APA91bElxPKDdbt_rBRJZsMf02JqrK7RdgBfZm1pYCDKcmVSmwYok1exNWpaU6If5B0yTRlB6o7RnHFWybnLF2zNqXvmE6S6_Lb7nfshK0yTB_qCzQFlOsAdwdoLB8L-tNXMgLnp1cj1&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;firebase&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
Count: 3 [Int32]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Backend Integration ===&lt;br /&gt;
&lt;br /&gt;
Start by installing the push notifications module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates the UserDevice type and also provides the PushNotificationService which can be used to send push notifications to target devices.&lt;br /&gt;
&lt;br /&gt;
Internally the PushNotificationService batches notifications together so can be used for large amounts of push notification traffic too.&lt;br /&gt;
&lt;br /&gt;
=== IOS APNS Key Setup ===&lt;br /&gt;
&lt;br /&gt;
To access iOS clients you will need to get a key from the Apple developer site and upload it into firebase:&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/5760994-how-to-set-up-your-apple-push-notification-key-for-your-ios-firebase-certificate#:~:text=How%20do%20I%20upload%20my%20APNs%20key%20to,Step%203.%20Click%20on%20Certificates%2C%20IDs%20%26%20Profiles&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
see the devops projects THE-APP-MCP AND THE-APP-MAP&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=675</id>
		<title>Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=675"/>
		<updated>2024-07-15T11:59:29Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setting up firebase ==&lt;br /&gt;
=== Get the package details from your application ===&lt;br /&gt;
&lt;br /&gt;
Get the package name from the cordova config.xml&lt;br /&gt;
&lt;br /&gt;
for example &amp;lt;widget android-packageName=&amp;quot;com.thermomix.comapp&amp;quot; id=&amp;quot;com.thermomix.comapp&amp;quot; version=&amp;quot;1.0.0&amp;quot; xmlns=&amp;quot;http://www.w3.org/ns/widgets&amp;quot; xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; xmlns:cdv=&amp;quot;http://cordova.apache.org/ns/1.0&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So in this case &amp;quot;com.thermomix.comapp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Setup a new project in firebase ===&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/web/setup&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/android/setup&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/2060582-how-to-set-up-your-firebase-certificates-for-ios-and-android&lt;br /&gt;
&lt;br /&gt;
Create apps for ios, android and web as required&lt;br /&gt;
&lt;br /&gt;
=== Update your application ===&lt;br /&gt;
&lt;br /&gt;
Add the following plugin into the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;platform name=&amp;quot;android&amp;quot;&amp;gt;section you will need to reference the icon colours for notifications&lt;br /&gt;
&lt;br /&gt;
&amp;lt;config-file parent=&amp;quot;/manifest/application/&amp;quot; target=&amp;quot;app/src/main/AndroidManifest.xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_icon&amp;quot; android:resource=&amp;quot;@drawable/notification_icon&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_color&amp;quot; android:resource=&amp;quot;@color/defaultNotificationColor&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/config-file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;plugin name=&amp;quot;cordova-plugin-firebase-messaging&amp;quot; spec=&amp;quot;~4.3.1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;FIREBASE_MESSAGING_VERSION&amp;quot; value=&amp;quot;20.1.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;ANDROIDX_CORE_VERSION&amp;quot; value=&amp;quot;1.0.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/plugin&amp;gt;&lt;br /&gt;
&lt;br /&gt;
see the official plugin page for more info &lt;br /&gt;
&lt;br /&gt;
https://www.npmjs.com/package/cordova-plugin-firebase-messaging&lt;br /&gt;
&lt;br /&gt;
=== Get the firebase config files ===&lt;br /&gt;
&lt;br /&gt;
See https://support.google.com/firebase/answer/7015592?hl=en#zippy=%2Cin-this-article for more info but basically&lt;br /&gt;
&lt;br /&gt;
Edit the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
within platform name = andriod&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;google-services.json&amp;quot; target=&amp;quot;app/google-services.json&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
within platform name = ios&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;GoogleService-Info.plist&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Download the 2 config files from the firebase application and copy then into the App folder containing the cordova config&lt;br /&gt;
&lt;br /&gt;
Get a copy of firebase-messaging-sw.js and copy it into the UI/public folder&lt;br /&gt;
&lt;br /&gt;
=== Pull in the required javascript files ===&lt;br /&gt;
&lt;br /&gt;
Update the htmlservice config to include &lt;br /&gt;
&lt;br /&gt;
  &amp;quot;EndHeadScripts&amp;quot;: [&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot; : &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot;: &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-messaging.js&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ],&lt;br /&gt;
&lt;br /&gt;
=== Add a UI handler for the messaging ===&lt;br /&gt;
&lt;br /&gt;
Install the push notifications UI module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install UI/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module has the main job of registering for push notifications when the device is either in a native app or via the web. A device on the web will see the permission request for push notifications.&lt;br /&gt;
&lt;br /&gt;
You&amp;#039;ll need to edit the PushNotifications.js file to include the correct firebase config from the web application. This config is primarily used when your site is running from the web.&lt;br /&gt;
&lt;br /&gt;
=== Add a settings page to your local wep app ===&lt;br /&gt;
&lt;br /&gt;
This will allow you to test the token refresh process in a webapp, and quickly confirm that all looks ok&lt;br /&gt;
&lt;br /&gt;
You need to add a page with the following component&lt;br /&gt;
&lt;br /&gt;
UI/PushSettings&lt;br /&gt;
{\n\t\&amp;quot;c\&amp;quot;: {\n\t\t\&amp;quot;t\&amp;quot;: \&amp;quot;UI/PushSettings\&amp;quot;,\n\t\t\&amp;quot;d\&amp;quot;: {},\n\t\t\&amp;quot;i\&amp;quot;: 2\n\t},\n\t\&amp;quot;i\&amp;quot;: 3\n}&lt;br /&gt;
&lt;br /&gt;
Once added, when you visit the page it should try and get a token from firebase and then send an update to the community via /api.ashx/v2/mwa/user/device/add.json&lt;br /&gt;
&lt;br /&gt;
This will add a device entry to the community and allow notifications to be passed back to the web app&lt;br /&gt;
&lt;br /&gt;
There are some console log entries to confirm it has been added&lt;br /&gt;
&lt;br /&gt;
=== First Test Time To Get The Client Key ===&lt;br /&gt;
&lt;br /&gt;
If all goes well, you should now be able to get a key in PushNotifications.js&lt;br /&gt;
&lt;br /&gt;
see section&lt;br /&gt;
&lt;br /&gt;
.then((currentToken)&lt;br /&gt;
&lt;br /&gt;
You should get some data looking like this, the key can then be used in your backend to send notifications to the web app&lt;br /&gt;
&lt;br /&gt;
notificationKey&lt;br /&gt;
&lt;br /&gt;
xxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&lt;br /&gt;
&lt;br /&gt;
notificationKeyType&lt;br /&gt;
&lt;br /&gt;
web&lt;br /&gt;
&lt;br /&gt;
=== Community Backend Authentication ===&lt;br /&gt;
&lt;br /&gt;
Create a service account via https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project&lt;br /&gt;
&lt;br /&gt;
Choose the application&lt;br /&gt;
&lt;br /&gt;
Click on the service account (will initial say no keys under key id)&lt;br /&gt;
&lt;br /&gt;
Click on keys&lt;br /&gt;
&lt;br /&gt;
Create a new key (export as json) and copy the contents into the mwa core config -&amp;gt; messaging&lt;br /&gt;
&lt;br /&gt;
The key should look something like &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;service_account&amp;quot;,&lt;br /&gt;
  &amp;quot;project_id&amp;quot;: &amp;quot;com-theintelligence-app&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key_id&amp;quot;: &amp;quot;31dc7cc70581a64dd2fdcb8df2d6179b018a9698&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key&amp;quot;: &amp;quot;-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n&amp;quot;,&lt;br /&gt;
  &amp;quot;client_email&amp;quot;: &amp;quot;firebase-adminsdk-jgnr7@com-theintelligence-app.iam.gserviceaccount.com&amp;quot;,&lt;br /&gt;
  &amp;quot;client_id&amp;quot;: &amp;quot;108818059171631247824&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_uri&amp;quot;: &amp;quot;https://accounts.google.com/o/oauth2/auth&amp;quot;,&lt;br /&gt;
  &amp;quot;token_uri&amp;quot;: &amp;quot;https://oauth2.googleapis.com/token&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_provider_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/oauth2/v1/certs&amp;quot;,&lt;br /&gt;
  &amp;quot;client_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-jgnr7%40com-theintelligence-app.iam.gserviceaccount.com&amp;quot;&lt;br /&gt;
?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Community Backend Testing ===&lt;br /&gt;
&lt;br /&gt;
Once all is setup and the app is running, it should register itseldf with the community for push notifications&lt;br /&gt;
&lt;br /&gt;
You can check this by running the following in the editor sandbox on the community, where xxxx is the userid &lt;br /&gt;
&lt;br /&gt;
$mwa_v1_push.List(xxxx)&lt;br /&gt;
You should get a list of the devices/browsers linked to the user (y4es browser as running as ss in browser will also register)&lt;br /&gt;
&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[List-of-UserDevice]&lt;br /&gt;
&lt;br /&gt;
[0] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;30320502008403&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;eRLAz76oKlkaclmsOIHjqj:APA91bEhrSbBLADxMLagqucznK5W06dx0JoMxGGwH-XVEZD6kdKdJll7JjVKjHJgpJbc8lSiq_on1j1_t0Puthn7wNKPJpxUnhSNxEVMkBMcq2T_CaZ0YyAGIljgkvp86OC5cLTz1r-j&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;web&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
[1] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;09965002900340&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;dhC63MgrSyqsi1ilkkwnax:APA91bHuPEAeiFiSHnuTPUs7cQ1J6EP_Fl0x3zxX8jaxqBhQPQKTxOPl72KbH1ZR3-h1_WWI4RzFX2k3RwXcwoX8fNm_rHFm5OOeUGoBV3BkwXB3keVbCXDRWx0AWApxZZoK2x1-78kT&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;firebase&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
[2] [UserDevice]&lt;br /&gt;
DeviceId: &amp;quot;00000019000&amp;quot; [String]&lt;br /&gt;
NotificationKey: &amp;quot;cldvQ2LES5C36bo9QQ52MQ:APA91bElxPKDdbt_rBRJZsMf02JqrK7RdgBfZm1pYCDKcmVSmwYok1exNWpaU6If5B0yTRlB6o7RnHFWybnLF2zNqXvmE6S6_Lb7nfshK0yTB_qCzQFlOsAdwdoLB8L-tNXMgLnp1cj1&amp;quot; [String]&lt;br /&gt;
NotificationKeyType: &amp;quot;firebase&amp;quot; [String]&lt;br /&gt;
&lt;br /&gt;
Count: 3 [Int32]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Backend Integration ===&lt;br /&gt;
&lt;br /&gt;
Start by installing the push notifications module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates the UserDevice type and also provides the PushNotificationService which can be used to send push notifications to target devices.&lt;br /&gt;
&lt;br /&gt;
Internally the PushNotificationService batches notifications together so can be used for large amounts of push notification traffic too.&lt;br /&gt;
&lt;br /&gt;
=== IOS APNS Key Setup ===&lt;br /&gt;
&lt;br /&gt;
To access iOS clients you will need to get a key from the Apple developer site and upload it into firebase:&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/5760994-how-to-set-up-your-apple-push-notification-key-for-your-ios-firebase-certificate#:~:text=How%20do%20I%20upload%20my%20APNs%20key%20to,Step%203.%20Click%20on%20Certificates%2C%20IDs%20%26%20Profiles&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
see the devops projects THE-APP-MCP AND THE-APP-MAP&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=674</id>
		<title>Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=674"/>
		<updated>2024-07-15T10:43:07Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Setup a new project in firebase */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setting up firebase ==&lt;br /&gt;
=== Get the package details from your application ===&lt;br /&gt;
&lt;br /&gt;
Get the package name from the cordova config.xml&lt;br /&gt;
&lt;br /&gt;
for example &amp;lt;widget android-packageName=&amp;quot;com.thermomix.comapp&amp;quot; id=&amp;quot;com.thermomix.comapp&amp;quot; version=&amp;quot;1.0.0&amp;quot; xmlns=&amp;quot;http://www.w3.org/ns/widgets&amp;quot; xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; xmlns:cdv=&amp;quot;http://cordova.apache.org/ns/1.0&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So in this case &amp;quot;com.thermomix.comapp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Setup a new project in firebase ===&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/web/setup&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/android/setup&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/2060582-how-to-set-up-your-firebase-certificates-for-ios-and-android&lt;br /&gt;
&lt;br /&gt;
Create apps for ios, android and web as required&lt;br /&gt;
&lt;br /&gt;
=== Update your application ===&lt;br /&gt;
&lt;br /&gt;
Add the following plugin into the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;platform name=&amp;quot;android&amp;quot;&amp;gt;section you will need to reference the icon colours for notifications&lt;br /&gt;
&lt;br /&gt;
&amp;lt;config-file parent=&amp;quot;/manifest/application/&amp;quot; target=&amp;quot;app/src/main/AndroidManifest.xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_icon&amp;quot; android:resource=&amp;quot;@drawable/notification_icon&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_color&amp;quot; android:resource=&amp;quot;@color/defaultNotificationColor&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/config-file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;plugin name=&amp;quot;cordova-plugin-firebase-messaging&amp;quot; spec=&amp;quot;~4.3.1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;FIREBASE_MESSAGING_VERSION&amp;quot; value=&amp;quot;20.1.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;ANDROIDX_CORE_VERSION&amp;quot; value=&amp;quot;1.0.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/plugin&amp;gt;&lt;br /&gt;
&lt;br /&gt;
see the official plugin page for more info &lt;br /&gt;
&lt;br /&gt;
https://www.npmjs.com/package/cordova-plugin-firebase-messaging&lt;br /&gt;
&lt;br /&gt;
=== Get the firebase config files ===&lt;br /&gt;
&lt;br /&gt;
See https://support.google.com/firebase/answer/7015592?hl=en#zippy=%2Cin-this-article for more info but basically&lt;br /&gt;
&lt;br /&gt;
Edit the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
within platform name = andriod&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;google-services.json&amp;quot; target=&amp;quot;app/google-services.json&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
within platform name = ios&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;GoogleService-Info.plist&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Download the 2 config files from the firebase application and copy then into the App folder containing the cordova config&lt;br /&gt;
&lt;br /&gt;
Get a copy of firebase-messaging-sw.js and copy it into the UI/public folder&lt;br /&gt;
&lt;br /&gt;
=== Pull in the required javascript files ===&lt;br /&gt;
&lt;br /&gt;
Update the htmlservice config to include &lt;br /&gt;
&lt;br /&gt;
  &amp;quot;EndHeadScripts&amp;quot;: [&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot; : &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot;: &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-messaging.js&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ],&lt;br /&gt;
&lt;br /&gt;
=== Add a UI handler for the messaging ===&lt;br /&gt;
&lt;br /&gt;
Install the push notifications UI module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install UI/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module has the main job of registering for push notifications when the device is either in a native app or via the web. A device on the web will see the permission request for push notifications.&lt;br /&gt;
&lt;br /&gt;
You&amp;#039;ll need to edit the PushNotifications.js file to include the correct firebase config from the web application. This config is primarily used when your site is running from the web.&lt;br /&gt;
&lt;br /&gt;
=== Add a settings page to your local wep app ===&lt;br /&gt;
&lt;br /&gt;
This will allow you to test the token refresh process in a webapp, and quickly confirm that all looks ok&lt;br /&gt;
&lt;br /&gt;
You need to add a page with the following component&lt;br /&gt;
&lt;br /&gt;
UI/PushSettings&lt;br /&gt;
{\n\t\&amp;quot;c\&amp;quot;: {\n\t\t\&amp;quot;t\&amp;quot;: \&amp;quot;UI/PushSettings\&amp;quot;,\n\t\t\&amp;quot;d\&amp;quot;: {},\n\t\t\&amp;quot;i\&amp;quot;: 2\n\t},\n\t\&amp;quot;i\&amp;quot;: 3\n}&lt;br /&gt;
&lt;br /&gt;
Once added, when you visit the page it should try and get a token from firebase and then send an update to the community via /api.ashx/v2/mwa/user/device/add.json&lt;br /&gt;
&lt;br /&gt;
This will add a device entry to the community and allow notifications to be passed back to the web app&lt;br /&gt;
&lt;br /&gt;
There are some console log entries to confirm it has been added&lt;br /&gt;
&lt;br /&gt;
=== First Test Time To Get The Client Key ===&lt;br /&gt;
&lt;br /&gt;
If all goes well, you should now be able to get a key in PushNotifications.js&lt;br /&gt;
&lt;br /&gt;
see section&lt;br /&gt;
&lt;br /&gt;
.then((currentToken)&lt;br /&gt;
&lt;br /&gt;
You should get some data looking like this, the key can then be used in your backend to send notifications to the web app&lt;br /&gt;
&lt;br /&gt;
notificationKey&lt;br /&gt;
&lt;br /&gt;
xxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&lt;br /&gt;
&lt;br /&gt;
notificationKeyType&lt;br /&gt;
&lt;br /&gt;
web&lt;br /&gt;
&lt;br /&gt;
=== Backend Authentication ===&lt;br /&gt;
&lt;br /&gt;
Create a service account via https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project&lt;br /&gt;
&lt;br /&gt;
Choose the application&lt;br /&gt;
&lt;br /&gt;
Click on the service account (will initial say no keys under key id)&lt;br /&gt;
&lt;br /&gt;
Click on keys&lt;br /&gt;
&lt;br /&gt;
Create a new key (export as json) and copy the contents into the mwa core config -&amp;gt; messaging&lt;br /&gt;
&lt;br /&gt;
The key should look something like &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;service_account&amp;quot;,&lt;br /&gt;
  &amp;quot;project_id&amp;quot;: &amp;quot;com-theintelligence-app&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key_id&amp;quot;: &amp;quot;31dc7cc70581a64dd2fdcb8df2d6179b018a9698&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key&amp;quot;: &amp;quot;-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n&amp;quot;,&lt;br /&gt;
  &amp;quot;client_email&amp;quot;: &amp;quot;firebase-adminsdk-jgnr7@com-theintelligence-app.iam.gserviceaccount.com&amp;quot;,&lt;br /&gt;
  &amp;quot;client_id&amp;quot;: &amp;quot;108818059171631247824&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_uri&amp;quot;: &amp;quot;https://accounts.google.com/o/oauth2/auth&amp;quot;,&lt;br /&gt;
  &amp;quot;token_uri&amp;quot;: &amp;quot;https://oauth2.googleapis.com/token&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_provider_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/oauth2/v1/certs&amp;quot;,&lt;br /&gt;
  &amp;quot;client_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-jgnr7%40com-theintelligence-app.iam.gserviceaccount.com&amp;quot;&lt;br /&gt;
?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Backend Integration ===&lt;br /&gt;
&lt;br /&gt;
Start by installing the push notifications module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates the UserDevice type and also provides the PushNotificationService which can be used to send push notifications to target devices.&lt;br /&gt;
&lt;br /&gt;
Internally the PushNotificationService batches notifications together so can be used for large amounts of push notification traffic too.&lt;br /&gt;
&lt;br /&gt;
=== IOS APNS Key Setup ===&lt;br /&gt;
&lt;br /&gt;
To access iOS clients you will need to get a key from the Apple developer site and upload it into firebase:&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/5760994-how-to-set-up-your-apple-push-notification-key-for-your-ios-firebase-certificate#:~:text=How%20do%20I%20upload%20my%20APNs%20key%20to,Step%203.%20Click%20on%20Certificates%2C%20IDs%20%26%20Profiles&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
see the devops projects THE-APP-MCP AND THE-APP-MAP&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=673</id>
		<title>Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=673"/>
		<updated>2024-07-04T12:59:06Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Setup a new project in firebase */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setting up firebase ==&lt;br /&gt;
=== Get the package details from your application ===&lt;br /&gt;
&lt;br /&gt;
Get the package name from the cordova config.xml&lt;br /&gt;
&lt;br /&gt;
for example &amp;lt;widget android-packageName=&amp;quot;com.thermomix.comapp&amp;quot; id=&amp;quot;com.thermomix.comapp&amp;quot; version=&amp;quot;1.0.0&amp;quot; xmlns=&amp;quot;http://www.w3.org/ns/widgets&amp;quot; xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; xmlns:cdv=&amp;quot;http://cordova.apache.org/ns/1.0&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So in this case &amp;quot;com.thermomix.comapp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Setup a new project in firebase ===&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/web/setup&lt;br /&gt;
https://firebase.google.com/docs/android/setup&lt;br /&gt;
&lt;br /&gt;
Create apps for ios, android and web as required&lt;br /&gt;
&lt;br /&gt;
=== Update your application ===&lt;br /&gt;
&lt;br /&gt;
Add the following plugin into the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;platform name=&amp;quot;android&amp;quot;&amp;gt;section you will need to reference the icon colours for notifications&lt;br /&gt;
&lt;br /&gt;
&amp;lt;config-file parent=&amp;quot;/manifest/application/&amp;quot; target=&amp;quot;app/src/main/AndroidManifest.xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_icon&amp;quot; android:resource=&amp;quot;@drawable/notification_icon&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_color&amp;quot; android:resource=&amp;quot;@color/defaultNotificationColor&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/config-file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;plugin name=&amp;quot;cordova-plugin-firebase-messaging&amp;quot; spec=&amp;quot;~4.3.1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;FIREBASE_MESSAGING_VERSION&amp;quot; value=&amp;quot;20.1.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;ANDROIDX_CORE_VERSION&amp;quot; value=&amp;quot;1.0.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/plugin&amp;gt;&lt;br /&gt;
&lt;br /&gt;
see the official plugin page for more info &lt;br /&gt;
&lt;br /&gt;
https://www.npmjs.com/package/cordova-plugin-firebase-messaging&lt;br /&gt;
&lt;br /&gt;
=== Get the firebase config files ===&lt;br /&gt;
&lt;br /&gt;
See https://support.google.com/firebase/answer/7015592?hl=en#zippy=%2Cin-this-article for more info but basically&lt;br /&gt;
&lt;br /&gt;
Edit the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
within platform name = andriod&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;google-services.json&amp;quot; target=&amp;quot;app/google-services.json&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
within platform name = ios&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;GoogleService-Info.plist&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Download the 2 config files from the firebase application and copy then into the App folder containing the cordova config&lt;br /&gt;
&lt;br /&gt;
Get a copy of firebase-messaging-sw.js and copy it into the UI/public folder&lt;br /&gt;
&lt;br /&gt;
=== Pull in the required javascript files ===&lt;br /&gt;
&lt;br /&gt;
Update the htmlservice config to include &lt;br /&gt;
&lt;br /&gt;
  &amp;quot;EndHeadScripts&amp;quot;: [&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot; : &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot;: &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-messaging.js&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ],&lt;br /&gt;
&lt;br /&gt;
=== Add a UI handler for the messaging ===&lt;br /&gt;
&lt;br /&gt;
Install the push notifications UI module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install UI/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module has the main job of registering for push notifications when the device is either in a native app or via the web. A device on the web will see the permission request for push notifications.&lt;br /&gt;
&lt;br /&gt;
You&amp;#039;ll need to edit the PushNotifications.js file to include the correct firebase config from the web application. This config is primarily used when your site is running from the web.&lt;br /&gt;
&lt;br /&gt;
=== Add a settings page to your local wep app ===&lt;br /&gt;
&lt;br /&gt;
This will allow you to test the token refresh process in a webapp, and quickly confirm that all looks ok&lt;br /&gt;
&lt;br /&gt;
You need to add a page with the following component&lt;br /&gt;
&lt;br /&gt;
UI/PushSettings&lt;br /&gt;
{\n\t\&amp;quot;c\&amp;quot;: {\n\t\t\&amp;quot;t\&amp;quot;: \&amp;quot;UI/PushSettings\&amp;quot;,\n\t\t\&amp;quot;d\&amp;quot;: {},\n\t\t\&amp;quot;i\&amp;quot;: 2\n\t},\n\t\&amp;quot;i\&amp;quot;: 3\n}&lt;br /&gt;
&lt;br /&gt;
Once added, when you visit the page it should try and get a token from firebase and then send an update to the community via /api.ashx/v2/mwa/user/device/add.json&lt;br /&gt;
&lt;br /&gt;
This will add a device entry to the community and allow notifications to be passed back to the web app&lt;br /&gt;
&lt;br /&gt;
There are some console log entries to confirm it has been added&lt;br /&gt;
&lt;br /&gt;
=== First Test Time To Get The Client Key ===&lt;br /&gt;
&lt;br /&gt;
If all goes well, you should now be able to get a key in PushNotifications.js&lt;br /&gt;
&lt;br /&gt;
see section&lt;br /&gt;
&lt;br /&gt;
.then((currentToken)&lt;br /&gt;
&lt;br /&gt;
You should get some data looking like this, the key can then be used in your backend to send notifications to the web app&lt;br /&gt;
&lt;br /&gt;
notificationKey&lt;br /&gt;
&lt;br /&gt;
xxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&lt;br /&gt;
&lt;br /&gt;
notificationKeyType&lt;br /&gt;
&lt;br /&gt;
web&lt;br /&gt;
&lt;br /&gt;
=== Backend Authentication ===&lt;br /&gt;
&lt;br /&gt;
Create a service account via https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project&lt;br /&gt;
&lt;br /&gt;
Choose the application&lt;br /&gt;
&lt;br /&gt;
Click on the service account (will initial say no keys under key id)&lt;br /&gt;
&lt;br /&gt;
Click on keys&lt;br /&gt;
&lt;br /&gt;
Create a new key (export as json) and copy the contents into the mwa core config -&amp;gt; messaging&lt;br /&gt;
&lt;br /&gt;
The key should look something like &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;service_account&amp;quot;,&lt;br /&gt;
  &amp;quot;project_id&amp;quot;: &amp;quot;com-theintelligence-app&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key_id&amp;quot;: &amp;quot;31dc7cc70581a64dd2fdcb8df2d6179b018a9698&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key&amp;quot;: &amp;quot;-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n&amp;quot;,&lt;br /&gt;
  &amp;quot;client_email&amp;quot;: &amp;quot;firebase-adminsdk-jgnr7@com-theintelligence-app.iam.gserviceaccount.com&amp;quot;,&lt;br /&gt;
  &amp;quot;client_id&amp;quot;: &amp;quot;108818059171631247824&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_uri&amp;quot;: &amp;quot;https://accounts.google.com/o/oauth2/auth&amp;quot;,&lt;br /&gt;
  &amp;quot;token_uri&amp;quot;: &amp;quot;https://oauth2.googleapis.com/token&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_provider_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/oauth2/v1/certs&amp;quot;,&lt;br /&gt;
  &amp;quot;client_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-jgnr7%40com-theintelligence-app.iam.gserviceaccount.com&amp;quot;&lt;br /&gt;
?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Backend Integration ===&lt;br /&gt;
&lt;br /&gt;
Start by installing the push notifications module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates the UserDevice type and also provides the PushNotificationService which can be used to send push notifications to target devices.&lt;br /&gt;
&lt;br /&gt;
Internally the PushNotificationService batches notifications together so can be used for large amounts of push notification traffic too.&lt;br /&gt;
&lt;br /&gt;
=== IOS APNS Key Setup ===&lt;br /&gt;
&lt;br /&gt;
To access iOS clients you will need to get a key from the Apple developer site and upload it into firebase:&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/5760994-how-to-set-up-your-apple-push-notification-key-for-your-ios-firebase-certificate#:~:text=How%20do%20I%20upload%20my%20APNs%20key%20to,Step%203.%20Click%20on%20Certificates%2C%20IDs%20%26%20Profiles&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
see the devops projects THE-APP-MCP AND THE-APP-MAP&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=672</id>
		<title>Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=672"/>
		<updated>2024-07-04T08:34:23Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Update your application */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setting up firebase ==&lt;br /&gt;
=== Get the package details from your application ===&lt;br /&gt;
&lt;br /&gt;
Get the package name from the cordova config.xml&lt;br /&gt;
&lt;br /&gt;
for example &amp;lt;widget android-packageName=&amp;quot;com.thermomix.comapp&amp;quot; id=&amp;quot;com.thermomix.comapp&amp;quot; version=&amp;quot;1.0.0&amp;quot; xmlns=&amp;quot;http://www.w3.org/ns/widgets&amp;quot; xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; xmlns:cdv=&amp;quot;http://cordova.apache.org/ns/1.0&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So in this case &amp;quot;com.thermomix.comapp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Setup a new project in firebase ===&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/android/setup&lt;br /&gt;
&lt;br /&gt;
Create apps for ios, android and web as required&lt;br /&gt;
&lt;br /&gt;
=== Update your application ===&lt;br /&gt;
&lt;br /&gt;
Add the following plugin into the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;platform name=&amp;quot;android&amp;quot;&amp;gt;section you will need to reference the icon colours for notifications&lt;br /&gt;
&lt;br /&gt;
&amp;lt;config-file parent=&amp;quot;/manifest/application/&amp;quot; target=&amp;quot;app/src/main/AndroidManifest.xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_icon&amp;quot; android:resource=&amp;quot;@drawable/notification_icon&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_color&amp;quot; android:resource=&amp;quot;@color/defaultNotificationColor&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/config-file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;plugin name=&amp;quot;cordova-plugin-firebase-messaging&amp;quot; spec=&amp;quot;~4.3.1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;FIREBASE_MESSAGING_VERSION&amp;quot; value=&amp;quot;20.1.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;ANDROIDX_CORE_VERSION&amp;quot; value=&amp;quot;1.0.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/plugin&amp;gt;&lt;br /&gt;
&lt;br /&gt;
see the official plugin page for more info &lt;br /&gt;
&lt;br /&gt;
https://www.npmjs.com/package/cordova-plugin-firebase-messaging&lt;br /&gt;
&lt;br /&gt;
=== Get the firebase config files ===&lt;br /&gt;
&lt;br /&gt;
See https://support.google.com/firebase/answer/7015592?hl=en#zippy=%2Cin-this-article for more info but basically&lt;br /&gt;
&lt;br /&gt;
Edit the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
within platform name = andriod&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;google-services.json&amp;quot; target=&amp;quot;app/google-services.json&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
within platform name = ios&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;GoogleService-Info.plist&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Download the 2 config files from the firebase application and copy then into the App folder containing the cordova config&lt;br /&gt;
&lt;br /&gt;
Get a copy of firebase-messaging-sw.js and copy it into the UI/public folder&lt;br /&gt;
&lt;br /&gt;
=== Pull in the required javascript files ===&lt;br /&gt;
&lt;br /&gt;
Update the htmlservice config to include &lt;br /&gt;
&lt;br /&gt;
  &amp;quot;EndHeadScripts&amp;quot;: [&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot; : &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot;: &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-messaging.js&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ],&lt;br /&gt;
&lt;br /&gt;
=== Add a UI handler for the messaging ===&lt;br /&gt;
&lt;br /&gt;
Install the push notifications UI module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install UI/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module has the main job of registering for push notifications when the device is either in a native app or via the web. A device on the web will see the permission request for push notifications.&lt;br /&gt;
&lt;br /&gt;
You&amp;#039;ll need to edit the PushNotifications.js file to include the correct firebase config from the web application. This config is primarily used when your site is running from the web.&lt;br /&gt;
&lt;br /&gt;
=== Add a settings page to your local wep app ===&lt;br /&gt;
&lt;br /&gt;
This will allow you to test the token refresh process in a webapp, and quickly confirm that all looks ok&lt;br /&gt;
&lt;br /&gt;
You need to add a page with the following component&lt;br /&gt;
&lt;br /&gt;
UI/PushSettings&lt;br /&gt;
{\n\t\&amp;quot;c\&amp;quot;: {\n\t\t\&amp;quot;t\&amp;quot;: \&amp;quot;UI/PushSettings\&amp;quot;,\n\t\t\&amp;quot;d\&amp;quot;: {},\n\t\t\&amp;quot;i\&amp;quot;: 2\n\t},\n\t\&amp;quot;i\&amp;quot;: 3\n}&lt;br /&gt;
&lt;br /&gt;
Once added, when you visit the page it should try and get a token from firebase and then send an update to the community via /api.ashx/v2/mwa/user/device/add.json&lt;br /&gt;
&lt;br /&gt;
This will add a device entry to the community and allow notifications to be passed back to the web app&lt;br /&gt;
&lt;br /&gt;
There are some console log entries to confirm it has been added&lt;br /&gt;
&lt;br /&gt;
=== First Test Time To Get The Client Key ===&lt;br /&gt;
&lt;br /&gt;
If all goes well, you should now be able to get a key in PushNotifications.js&lt;br /&gt;
&lt;br /&gt;
see section&lt;br /&gt;
&lt;br /&gt;
.then((currentToken)&lt;br /&gt;
&lt;br /&gt;
You should get some data looking like this, the key can then be used in your backend to send notifications to the web app&lt;br /&gt;
&lt;br /&gt;
notificationKey&lt;br /&gt;
&lt;br /&gt;
xxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&lt;br /&gt;
&lt;br /&gt;
notificationKeyType&lt;br /&gt;
&lt;br /&gt;
web&lt;br /&gt;
&lt;br /&gt;
=== Backend Authentication ===&lt;br /&gt;
&lt;br /&gt;
Create a service account via https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project&lt;br /&gt;
&lt;br /&gt;
Choose the application&lt;br /&gt;
&lt;br /&gt;
Click on the service account (will initial say no keys under key id)&lt;br /&gt;
&lt;br /&gt;
Click on keys&lt;br /&gt;
&lt;br /&gt;
Create a new key (export as json) and copy the contents into the mwa core config -&amp;gt; messaging&lt;br /&gt;
&lt;br /&gt;
The key should look something like &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;service_account&amp;quot;,&lt;br /&gt;
  &amp;quot;project_id&amp;quot;: &amp;quot;com-theintelligence-app&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key_id&amp;quot;: &amp;quot;31dc7cc70581a64dd2fdcb8df2d6179b018a9698&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key&amp;quot;: &amp;quot;-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n&amp;quot;,&lt;br /&gt;
  &amp;quot;client_email&amp;quot;: &amp;quot;firebase-adminsdk-jgnr7@com-theintelligence-app.iam.gserviceaccount.com&amp;quot;,&lt;br /&gt;
  &amp;quot;client_id&amp;quot;: &amp;quot;108818059171631247824&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_uri&amp;quot;: &amp;quot;https://accounts.google.com/o/oauth2/auth&amp;quot;,&lt;br /&gt;
  &amp;quot;token_uri&amp;quot;: &amp;quot;https://oauth2.googleapis.com/token&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_provider_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/oauth2/v1/certs&amp;quot;,&lt;br /&gt;
  &amp;quot;client_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-jgnr7%40com-theintelligence-app.iam.gserviceaccount.com&amp;quot;&lt;br /&gt;
?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Backend Integration ===&lt;br /&gt;
&lt;br /&gt;
Start by installing the push notifications module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates the UserDevice type and also provides the PushNotificationService which can be used to send push notifications to target devices.&lt;br /&gt;
&lt;br /&gt;
Internally the PushNotificationService batches notifications together so can be used for large amounts of push notification traffic too.&lt;br /&gt;
&lt;br /&gt;
=== IOS APNS Key Setup ===&lt;br /&gt;
&lt;br /&gt;
To access iOS clients you will need to get a key from the Apple developer site and upload it into firebase:&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/5760994-how-to-set-up-your-apple-push-notification-key-for-your-ios-firebase-certificate#:~:text=How%20do%20I%20upload%20my%20APNs%20key%20to,Step%203.%20Click%20on%20Certificates%2C%20IDs%20%26%20Profiles&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
see the devops projects THE-APP-MCP AND THE-APP-MAP&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=671</id>
		<title>Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Push_Notifications&amp;diff=671"/>
		<updated>2024-07-04T08:31:00Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Update your application */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setting up firebase ==&lt;br /&gt;
=== Get the package details from your application ===&lt;br /&gt;
&lt;br /&gt;
Get the package name from the cordova config.xml&lt;br /&gt;
&lt;br /&gt;
for example &amp;lt;widget android-packageName=&amp;quot;com.thermomix.comapp&amp;quot; id=&amp;quot;com.thermomix.comapp&amp;quot; version=&amp;quot;1.0.0&amp;quot; xmlns=&amp;quot;http://www.w3.org/ns/widgets&amp;quot; xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; xmlns:cdv=&amp;quot;http://cordova.apache.org/ns/1.0&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So in this case &amp;quot;com.thermomix.comapp&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Setup a new project in firebase ===&lt;br /&gt;
&lt;br /&gt;
https://firebase.google.com/docs/android/setup&lt;br /&gt;
&lt;br /&gt;
Create apps for ios, android and web as required&lt;br /&gt;
&lt;br /&gt;
=== Update your application ===&lt;br /&gt;
&lt;br /&gt;
Add the following plugin into the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
In the &amp;lt;platform name=&amp;quot;android&amp;quot;&amp;gt;section you will need to reference the icon colours for notifications&lt;br /&gt;
&lt;br /&gt;
&amp;lt;config-file parent=&amp;quot;/manifest/application/&amp;quot; target=&amp;quot;app/src/main/AndroidManifest.xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_icon&amp;quot; android:resource=&amp;quot;@drawable/notification_icon&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;meta-data android:name=&amp;quot;com.google.firebase.messaging.default_notification_color&amp;quot; android:resource=&amp;quot;@color/defaultNotificationColor&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/config-file&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;plugin name=&amp;quot;cordova-plugin-firebase-messaging&amp;quot; spec=&amp;quot;~4.3.1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;FIREBASE_MESSAGING_VERSION&amp;quot; value=&amp;quot;20.1.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;variable name=&amp;quot;ANDROIDX_CORE_VERSION&amp;quot; value=&amp;quot;1.0.+&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/plugin&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Get the firebase config files ===&lt;br /&gt;
&lt;br /&gt;
See https://support.google.com/firebase/answer/7015592?hl=en#zippy=%2Cin-this-article for more info but basically&lt;br /&gt;
&lt;br /&gt;
Edit the config.xml in the App folder&lt;br /&gt;
&lt;br /&gt;
within platform name = andriod&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;google-services.json&amp;quot; target=&amp;quot;app/google-services.json&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
within platform name = ios&lt;br /&gt;
&lt;br /&gt;
&amp;lt;resource-file src=&amp;quot;GoogleService-Info.plist&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Download the 2 config files from the firebase application and copy then into the App folder containing the cordova config&lt;br /&gt;
&lt;br /&gt;
Get a copy of firebase-messaging-sw.js and copy it into the UI/public folder&lt;br /&gt;
&lt;br /&gt;
=== Pull in the required javascript files ===&lt;br /&gt;
&lt;br /&gt;
Update the htmlservice config to include &lt;br /&gt;
&lt;br /&gt;
  &amp;quot;EndHeadScripts&amp;quot;: [&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot; : &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
          &amp;quot;type&amp;quot; : &amp;quot;text/javascript&amp;quot;,&lt;br /&gt;
          &amp;quot;src&amp;quot;: &amp;quot;https://www.gstatic.com/firebasejs/7.14.0/firebase-messaging.js&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ],&lt;br /&gt;
&lt;br /&gt;
=== Add a UI handler for the messaging ===&lt;br /&gt;
&lt;br /&gt;
Install the push notifications UI module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install UI/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module has the main job of registering for push notifications when the device is either in a native app or via the web. A device on the web will see the permission request for push notifications.&lt;br /&gt;
&lt;br /&gt;
You&amp;#039;ll need to edit the PushNotifications.js file to include the correct firebase config from the web application. This config is primarily used when your site is running from the web.&lt;br /&gt;
&lt;br /&gt;
=== Add a settings page to your local wep app ===&lt;br /&gt;
&lt;br /&gt;
This will allow you to test the token refresh process in a webapp, and quickly confirm that all looks ok&lt;br /&gt;
&lt;br /&gt;
You need to add a page with the following component&lt;br /&gt;
&lt;br /&gt;
UI/PushSettings&lt;br /&gt;
{\n\t\&amp;quot;c\&amp;quot;: {\n\t\t\&amp;quot;t\&amp;quot;: \&amp;quot;UI/PushSettings\&amp;quot;,\n\t\t\&amp;quot;d\&amp;quot;: {},\n\t\t\&amp;quot;i\&amp;quot;: 2\n\t},\n\t\&amp;quot;i\&amp;quot;: 3\n}&lt;br /&gt;
&lt;br /&gt;
Once added, when you visit the page it should try and get a token from firebase and then send an update to the community via /api.ashx/v2/mwa/user/device/add.json&lt;br /&gt;
&lt;br /&gt;
This will add a device entry to the community and allow notifications to be passed back to the web app&lt;br /&gt;
&lt;br /&gt;
There are some console log entries to confirm it has been added&lt;br /&gt;
&lt;br /&gt;
=== First Test Time To Get The Client Key ===&lt;br /&gt;
&lt;br /&gt;
If all goes well, you should now be able to get a key in PushNotifications.js&lt;br /&gt;
&lt;br /&gt;
see section&lt;br /&gt;
&lt;br /&gt;
.then((currentToken)&lt;br /&gt;
&lt;br /&gt;
You should get some data looking like this, the key can then be used in your backend to send notifications to the web app&lt;br /&gt;
&lt;br /&gt;
notificationKey&lt;br /&gt;
&lt;br /&gt;
xxxxxxxxxxxxxxxxx:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&lt;br /&gt;
&lt;br /&gt;
notificationKeyType&lt;br /&gt;
&lt;br /&gt;
web&lt;br /&gt;
&lt;br /&gt;
=== Backend Authentication ===&lt;br /&gt;
&lt;br /&gt;
Create a service account via https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts?supportedpurview=project&lt;br /&gt;
&lt;br /&gt;
Choose the application&lt;br /&gt;
&lt;br /&gt;
Click on the service account (will initial say no keys under key id)&lt;br /&gt;
&lt;br /&gt;
Click on keys&lt;br /&gt;
&lt;br /&gt;
Create a new key (export as json) and copy the contents into the mwa core config -&amp;gt; messaging&lt;br /&gt;
&lt;br /&gt;
The key should look something like &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;type&amp;quot;: &amp;quot;service_account&amp;quot;,&lt;br /&gt;
  &amp;quot;project_id&amp;quot;: &amp;quot;com-theintelligence-app&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key_id&amp;quot;: &amp;quot;31dc7cc70581a64dd2fdcb8df2d6179b018a9698&amp;quot;,&lt;br /&gt;
  &amp;quot;private_key&amp;quot;: &amp;quot;-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n&amp;quot;,&lt;br /&gt;
  &amp;quot;client_email&amp;quot;: &amp;quot;firebase-adminsdk-jgnr7@com-theintelligence-app.iam.gserviceaccount.com&amp;quot;,&lt;br /&gt;
  &amp;quot;client_id&amp;quot;: &amp;quot;108818059171631247824&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_uri&amp;quot;: &amp;quot;https://accounts.google.com/o/oauth2/auth&amp;quot;,&lt;br /&gt;
  &amp;quot;token_uri&amp;quot;: &amp;quot;https://oauth2.googleapis.com/token&amp;quot;,&lt;br /&gt;
  &amp;quot;auth_provider_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/oauth2/v1/certs&amp;quot;,&lt;br /&gt;
  &amp;quot;client_x509_cert_url&amp;quot;: &amp;quot;https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-jgnr7%40com-theintelligence-app.iam.gserviceaccount.com&amp;quot;&lt;br /&gt;
?&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Backend Integration ===&lt;br /&gt;
&lt;br /&gt;
Start by installing the push notifications module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install Api/PushNotifications&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This module creates the UserDevice type and also provides the PushNotificationService which can be used to send push notifications to target devices.&lt;br /&gt;
&lt;br /&gt;
Internally the PushNotificationService batches notifications together so can be used for large amounts of push notification traffic too.&lt;br /&gt;
&lt;br /&gt;
=== IOS APNS Key Setup ===&lt;br /&gt;
&lt;br /&gt;
To access iOS clients you will need to get a key from the Apple developer site and upload it into firebase:&lt;br /&gt;
&lt;br /&gt;
https://learn.buildfire.com/en/articles/5760994-how-to-set-up-your-apple-push-notification-key-for-your-ios-firebase-certificate#:~:text=How%20do%20I%20upload%20my%20APNs%20key%20to,Step%203.%20Click%20on%20Certificates%2C%20IDs%20%26%20Profiles&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
see the devops projects THE-APP-MCP AND THE-APP-MAP&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Payments&amp;diff=670</id>
		<title>Payments</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Payments&amp;diff=670"/>
		<updated>2024-05-29T14:18:22Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Using built in UI&amp;#039;s */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack has an installable module for handling payments, shopping carts, products and [[subscriptions]] amongst other payment related functionality.&lt;br /&gt;
&lt;br /&gt;
= Getting Started =&lt;br /&gt;
&lt;br /&gt;
First, install the payments module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install payments&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will pull in multiple separate modules, e.g. Api/Payments. When you next start the project, you&amp;#039;ll then have a products and prices area of your admin panel.&lt;br /&gt;
&lt;br /&gt;
= Configuring the module =&lt;br /&gt;
&lt;br /&gt;
== Currency codes ==&lt;br /&gt;
&lt;br /&gt;
Prices on products can be localised and are localised by various components on the frontend such as the default UI/Payments/Checkout component. For this to work, it&amp;#039;s important that you go into the Locales part of the admin panel and set the &amp;quot;Currency code&amp;quot; on your locale(s). This currency code is, for example, &amp;quot;USD&amp;quot; or &amp;quot;GBP&amp;quot;, depending on what your primary audience is. You can also add additional locales to your site and set currency codes on each one.&lt;br /&gt;
&lt;br /&gt;
[[File:Payments_locale_currencycode.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;The currency code field on locales is used by the frontend to pick how to display prices&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
== Payment gateway ==&lt;br /&gt;
&lt;br /&gt;
Next, you&amp;#039;ll need to setup a payment gateway. By default Stripe is ready to go, so you&amp;#039;ll need a test Stripe publishable/ private key. Once you have this info, head into the Configuration part of the administration panel where you should see a Stripe entry, and drop in your details. At a minimum, only PublishableKey and SecretKey are required. If the Stripe entry is not there, you can create it too, or restart the API if you haven&amp;#039;t done so yet since installing the module. Config entries are created automatically if they don&amp;#039;t exist during startup.&lt;br /&gt;
&lt;br /&gt;
[[File:Stripe_gateway_config.png]]&lt;br /&gt;
&lt;br /&gt;
Once you have setup the payment gateway, it&amp;#039;s &amp;#039;&amp;#039;&amp;#039;important that you add a webhook to your site&amp;#039;&amp;#039;&amp;#039;. On the Stripe dashboard, create a webhook to https://yourSiteOrStageSite.com/v1/stripe-gateway/webhook.&lt;br /&gt;
&lt;br /&gt;
This of course won&amp;#039;t work when testing locally because Stripe can&amp;#039;t access your computer on a LAN (aside from using a Stripe webhook simulator) so the webhook is best set to point at a stage site. You can test your webhook setup is working by using one of Stripe&amp;#039;s [https://stripe.com/docs/testing#authentication-and-setup test payment cards with delayed authorisation such as 3DS]. The purchase request is created at Stripe and is marked as pending in your API. Stripe provides the 3DS URL to the frontend which you must then display - the default checkout component (UI/Payments/Checkout) will simply redirect to it. When the user successfully completes the 3DS challenge, the webhook is pinged by Stripe to indicate the payment completed successfully.&lt;br /&gt;
&lt;br /&gt;
= Adding products =&lt;br /&gt;
&lt;br /&gt;
In short, a product can have multiple tiers (for bulk pricing), and a product&amp;#039;s price can be localised (for international support). The UI for this will be specialised as a pricing grid in the future, but for now you&amp;#039;ll need to create separate prices and separate products per tier, if your product has both bulk prices and is localised.&lt;br /&gt;
&lt;br /&gt;
When adding prices, the Amount is in the smallest unit of a currency. In US Dollars and Euro&amp;#039;s for example, that is in cents. In Pound Sterling, it is in pence.&lt;br /&gt;
&lt;br /&gt;
A product also has [[Pricing Strategies|pricing strategies]] - see the separate page for more information on how those work.&lt;br /&gt;
&lt;br /&gt;
= Using built in UI&amp;#039;s =&lt;br /&gt;
&lt;br /&gt;
* Typically sites use the shopping cart checkout flow. This uses a checkout page which is suggested to use /checkout as the URL and contain UI/Payments/Checkout on it. You must also add a page with default URL set to &amp;quot;/complete&amp;quot; with UI/Payments/Complete on it.&lt;br /&gt;
&lt;br /&gt;
* Display what is currently in the cart with the UI/Payments/Cart component.&lt;br /&gt;
&lt;br /&gt;
* You can add to the cart via the useCart hook:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import { useCart } from &amp;#039;UI/Payments/CartSession&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
var { addToCart } = useCart();&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
addToCart({product: 14, quantity: 20}); // Add 20 of product #14 to the cart.&lt;br /&gt;
&lt;br /&gt;
addToCart({product: 19, isSubscribing: true}); // Adds the subscription product #19 to the cart.&lt;br /&gt;
&lt;br /&gt;
addToCart({product: 14, quantity: 1, ref: &amp;#039;test&amp;#039;}); // Add 1 of product #14 to the cart with a reference of test.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Or alternatively to add to the cart, the UI/Payments/ProductQuantity component. This has a +/- quantity UI and an add to cart button:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import { useCart } from &amp;#039;UI/Payments/CartSession&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
var { getCartQuantity } = useCart();&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProductQuantity product={productObject} quantity={getCartQuantity(productObject.id)}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* If you would just like a payment method input/selector, which includes selecting previously saved payment methods, use type=&amp;#039;payment&amp;#039;. Note that the saved payment methods are saved as a token - your site will only store the non-sensitive details such as the last 4 digits and the expiry date. Because of this the full requirements of PCI DSS are avoidable as by default the payment card details never touch your site&amp;#039;s servers.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Input type=&amp;#039;payment&amp;#039; name=&amp;#039;paymentMethod&amp;#039; label=&amp;#039;Payment method&amp;#039; validate={[&amp;#039;Required&amp;#039;]} /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Working inside the API =&lt;br /&gt;
&lt;br /&gt;
In a nutshell, the payments module will ultimately create a Purchase with ProductQuantity entries attached to it, then attempt to execute the purchase. For example if a Subscription is created, each time the subscription is billed it will raise a new Purchase and copy over the Subscriptions ProductQuantity entries. This allows historical purchases to list out the items even if the current subscription has been modified in some way.&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Payments&amp;diff=669</id>
		<title>Payments</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Payments&amp;diff=669"/>
		<updated>2024-05-28T14:06:31Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Using built in UI&amp;#039;s */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack has an installable module for handling payments, shopping carts, products and [[subscriptions]] amongst other payment related functionality.&lt;br /&gt;
&lt;br /&gt;
= Getting Started =&lt;br /&gt;
&lt;br /&gt;
First, install the payments module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack install payments&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will pull in multiple separate modules, e.g. Api/Payments. When you next start the project, you&amp;#039;ll then have a products and prices area of your admin panel.&lt;br /&gt;
&lt;br /&gt;
= Configuring the module =&lt;br /&gt;
&lt;br /&gt;
== Currency codes ==&lt;br /&gt;
&lt;br /&gt;
Prices on products can be localised and are localised by various components on the frontend such as the default UI/Payments/Checkout component. For this to work, it&amp;#039;s important that you go into the Locales part of the admin panel and set the &amp;quot;Currency code&amp;quot; on your locale(s). This currency code is, for example, &amp;quot;USD&amp;quot; or &amp;quot;GBP&amp;quot;, depending on what your primary audience is. You can also add additional locales to your site and set currency codes on each one.&lt;br /&gt;
&lt;br /&gt;
[[File:Payments_locale_currencycode.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;The currency code field on locales is used by the frontend to pick how to display prices&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
== Payment gateway ==&lt;br /&gt;
&lt;br /&gt;
Next, you&amp;#039;ll need to setup a payment gateway. By default Stripe is ready to go, so you&amp;#039;ll need a test Stripe publishable/ private key. Once you have this info, head into the Configuration part of the administration panel where you should see a Stripe entry, and drop in your details. At a minimum, only PublishableKey and SecretKey are required. If the Stripe entry is not there, you can create it too, or restart the API if you haven&amp;#039;t done so yet since installing the module. Config entries are created automatically if they don&amp;#039;t exist during startup.&lt;br /&gt;
&lt;br /&gt;
[[File:Stripe_gateway_config.png]]&lt;br /&gt;
&lt;br /&gt;
Once you have setup the payment gateway, it&amp;#039;s &amp;#039;&amp;#039;&amp;#039;important that you add a webhook to your site&amp;#039;&amp;#039;&amp;#039;. On the Stripe dashboard, create a webhook to https://yourSiteOrStageSite.com/v1/stripe-gateway/webhook.&lt;br /&gt;
&lt;br /&gt;
This of course won&amp;#039;t work when testing locally because Stripe can&amp;#039;t access your computer on a LAN (aside from using a Stripe webhook simulator) so the webhook is best set to point at a stage site. You can test your webhook setup is working by using one of Stripe&amp;#039;s [https://stripe.com/docs/testing#authentication-and-setup test payment cards with delayed authorisation such as 3DS]. The purchase request is created at Stripe and is marked as pending in your API. Stripe provides the 3DS URL to the frontend which you must then display - the default checkout component (UI/Payments/Checkout) will simply redirect to it. When the user successfully completes the 3DS challenge, the webhook is pinged by Stripe to indicate the payment completed successfully.&lt;br /&gt;
&lt;br /&gt;
= Adding products =&lt;br /&gt;
&lt;br /&gt;
In short, a product can have multiple tiers (for bulk pricing), and a product&amp;#039;s price can be localised (for international support). The UI for this will be specialised as a pricing grid in the future, but for now you&amp;#039;ll need to create separate prices and separate products per tier, if your product has both bulk prices and is localised.&lt;br /&gt;
&lt;br /&gt;
When adding prices, the Amount is in the smallest unit of a currency. In US Dollars and Euro&amp;#039;s for example, that is in cents. In Pound Sterling, it is in pence.&lt;br /&gt;
&lt;br /&gt;
A product also has [[Pricing Strategies|pricing strategies]] - see the separate page for more information on how those work.&lt;br /&gt;
&lt;br /&gt;
= Using built in UI&amp;#039;s =&lt;br /&gt;
&lt;br /&gt;
* Typically sites use the shopping cart checkout flow. This uses a checkout page which is suggested to use /checkout as the URL and contain UI/Payments/Checkout on it. You must also add a page with default URL set to &amp;quot;/complete&amp;quot; with UI/Payments/Complete on it.&lt;br /&gt;
&lt;br /&gt;
* Display what is currently in the cart with the UI/Payments/Cart component.&lt;br /&gt;
&lt;br /&gt;
* You can add to the cart via the useCart hook:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import { useCart } from &amp;#039;UI/Payments/CartSession&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
var { addToCart } = useCart();&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
addToCart({product: 14, quantity: 20}); // Add 20 of product #14 to the cart.&lt;br /&gt;
&lt;br /&gt;
addToCart({product: 19, isSubscribing: true}); // Adds the subscription product #19 to the cart.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Or alternatively to add to the cart, the UI/Payments/ProductQuantity component. This has a +/- quantity UI and an add to cart button:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
import { useCart } from &amp;#039;UI/Payments/CartSession&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
var { getCartQuantity } = useCart();&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProductQuantity product={productObject} quantity={getCartQuantity(productObject.id)}/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* If you would just like a payment method input/selector, which includes selecting previously saved payment methods, use type=&amp;#039;payment&amp;#039;. Note that the saved payment methods are saved as a token - your site will only store the non-sensitive details such as the last 4 digits and the expiry date. Because of this the full requirements of PCI DSS are avoidable as by default the payment card details never touch your site&amp;#039;s servers.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;jsx&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;Input type=&amp;#039;payment&amp;#039; name=&amp;#039;paymentMethod&amp;#039; label=&amp;#039;Payment method&amp;#039; validate={[&amp;#039;Required&amp;#039;]} /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Working inside the API =&lt;br /&gt;
&lt;br /&gt;
In a nutshell, the payments module will ultimately create a Purchase with ProductQuantity entries attached to it, then attempt to execute the purchase. For example if a Subscription is created, each time the subscription is billed it will raise a new Purchase and copy over the Subscriptions ProductQuantity entries. This allows historical purchases to list out the items even if the current subscription has been modified in some way.&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=663</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=663"/>
		<updated>2024-05-07T10:29:03Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates (Manual Steps) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps)==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates, adding your domain names as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /etc/nginx/sites-enabled/*.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssl_certificate /etc/ssl/cert/fullchain-1.pem;&lt;br /&gt;
ssl_certificate_key /etc/ssl/cert/privkey-1.pem;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next you need to use our new files to replace the existing files&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
rm *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem /etc/ssl/cert/fullchain-1.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem /etc/ssl/cert/privkey-1.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain-1.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
-rwxrwxrwx 1 root root  3677 Mar 19 14:58 fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey-1.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
-rwxrwxrwx 1 root root  1650 Mar 19 14:58 privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=PropTypes&amp;diff=662</id>
		<title>PropTypes</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=PropTypes&amp;diff=662"/>
		<updated>2024-04-30T11:54:14Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Valid values */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;PropTypes are the React mechanism for defining what values a React component prop will accept. Because Socialstack&amp;#039;s admin panel has a visual mechanism for adding components and there are a variety of convenience features for selecting values, Socialstack extends PropTypes and typically uses them a little differently. Fundamentally though, the propType value is a valid type accepted by &amp;lt;Input&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= PropTypes example =&lt;br /&gt;
&lt;br /&gt;
When you [[Generate|generate a UI module]], you&amp;#039;ll get the following as a commented out example (but with Entities swapped out with the name of your component):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
Entities.propTypes = {&lt;br /&gt;
	title: &amp;#039;string&amp;#039;, // text input&lt;br /&gt;
	size: [1,2,3,4], // dropdowns&lt;br /&gt;
	width: [ // dropdown with separate labels/values&lt;br /&gt;
		{ name: &amp;#039;1/12&amp;#039;, value: 1 },&lt;br /&gt;
		{ name: &amp;#039;2/12&amp;#039;, value: 2 },&lt;br /&gt;
		{ name: &amp;#039;3/12 (25%)&amp;#039;, value: 3 },&lt;br /&gt;
		{ name: &amp;#039;4/12 (33%)&amp;#039;, value: 4 },&lt;br /&gt;
		{ name: &amp;#039;5/12&amp;#039;, value: 5 },&lt;br /&gt;
		{ name: &amp;#039;6/12 (50%)&amp;#039;, value: 6 },&lt;br /&gt;
		{ name: &amp;#039;7/12&amp;#039;, value: 7 },&lt;br /&gt;
		{ name: &amp;#039;8/12 (66%)&amp;#039;, value: 8 },&lt;br /&gt;
		{ name: &amp;#039;9/12 (75%)&amp;#039;, value: 9 },&lt;br /&gt;
		{ name: &amp;#039;10/12&amp;#039;, value: 10 },&lt;br /&gt;
		{ name: &amp;#039;11/12&amp;#039;, value: 11 },&lt;br /&gt;
		{ name: &amp;#039;12/12 (100%)&amp;#039;, value: 12 }&lt;br /&gt;
	],&lt;br /&gt;
&lt;br /&gt;
	// alternatively, specify property parameters on a more granular level&lt;br /&gt;
	scrolledBackgroundColor: {&lt;br /&gt;
        type: &amp;#039;color&amp;#039;,&lt;br /&gt;
        label: &amp;#039;Background colour (when scrolled)&amp;#039;, // optional replacement label&lt;br /&gt;
        help: &amp;#039;Default background colour when page has been scrolled&amp;#039;, // optional help text to be shown along with the field (default position is between the label and field)&lt;br /&gt;
        helpPosition: &amp;#039;icon&amp;#039;, // help text position can be set on a per-field basis by including helpPosition (above, below or icon - with icon, the help text is hidden as alt text on an info icon displayed alongside the field)&lt;br /&gt;
        placeholder: &amp;#039;Select a colour&amp;#039; // by default, placeholder inherits the default value (if any) - set here to override this if necessary&lt;br /&gt;
    },	&lt;br /&gt;
&lt;br /&gt;
	// All &amp;lt;Input type=&amp;#039;x&amp;#039; /&amp;gt; values are supported - checkbox, color etc.&lt;br /&gt;
	// Also the special id type which can be used to select some other piece of content (by entity name), like this:&lt;br /&gt;
	templateToUse: {type: &amp;#039;id&amp;#039;, content: &amp;#039;Template&amp;#039;}&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So long as propTypes simply exists - it can be an empty object - the admin panel will believe your component is addable via the visual UI.&lt;br /&gt;
&lt;br /&gt;
= Valid values =&lt;br /&gt;
&lt;br /&gt;
* Anything that &amp;lt;Input&amp;gt; accepts as a type. This can vary from project to project because it&amp;#039;ll depend if you have installed custom input types or not, such as [[Html Fields|type=&amp;quot;html&amp;quot;]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
Entities.propTypes = {&lt;br /&gt;
    description: &amp;#039;text&amp;#039;, // or, more commonly, string (alias)&lt;br /&gt;
    background: &amp;#039;color&amp;#039;,&lt;br /&gt;
    isActive: &amp;#039;checkbox&amp;#039; // or bool (alias)&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Specifying an object will allow you to set all prop values on &amp;lt;Input&amp;gt; including any custom ones used by custom input types. Everything in the object is passed as a prop and in turn &amp;lt;Input&amp;gt; will forward it to your custom input type. Here we&amp;#039;re setting some of the other default props on &amp;lt;Input&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
Entities.propTypes = {&lt;br /&gt;
    scrolledBackgroundColor: {&lt;br /&gt;
        type: &amp;#039;color&amp;#039;,&lt;br /&gt;
        label: &amp;#039;Background colour (when scrolled)&amp;#039;, // optional replacement label&lt;br /&gt;
        help: &amp;#039;Default background colour when page has been scrolled&amp;#039;, // optional help text to be shown along with the field (default position is between the label and field)&lt;br /&gt;
        helpPosition: &amp;#039;icon&amp;#039;, // help text position can be set on a per-field basis by including helpPosition (above, below or icon - with icon, the help text is hidden as alt text on an info icon displayed alongside the field)&lt;br /&gt;
        placeholder: &amp;#039;Select a colour&amp;#039; // by default, placeholder inherits the default value (if any) - set here to override this if necessary&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* An array of values, which will generate a dropdown.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
Entities.propTypes = {&lt;br /&gt;
    size: [1,2,3,4], // dropdowns&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* An array of objects, which will generate a dropdown with custom labels.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
Entities.propTypes = {&lt;br /&gt;
    width: [ // dropdown with separate labels/values&lt;br /&gt;
		{ name: &amp;#039;1/12&amp;#039;, value: 1 },&lt;br /&gt;
		{ name: &amp;#039;2/12&amp;#039;, value: 2 },&lt;br /&gt;
		{ name: &amp;#039;3/12 (25%)&amp;#039;, value: 3 },&lt;br /&gt;
		{ name: &amp;#039;4/12 (33%)&amp;#039;, value: 4 },&lt;br /&gt;
		{ name: &amp;#039;5/12&amp;#039;, value: 5 },&lt;br /&gt;
		{ name: &amp;#039;6/12 (50%)&amp;#039;, value: 6 },&lt;br /&gt;
		{ name: &amp;#039;7/12&amp;#039;, value: 7 },&lt;br /&gt;
		{ name: &amp;#039;8/12 (66%)&amp;#039;, value: 8 },&lt;br /&gt;
		{ name: &amp;#039;9/12 (75%)&amp;#039;, value: 9 },&lt;br /&gt;
		{ name: &amp;#039;10/12&amp;#039;, value: 10 },&lt;br /&gt;
		{ name: &amp;#039;11/12&amp;#039;, value: 11 },&lt;br /&gt;
		{ name: &amp;#039;12/12 (100%)&amp;#039;, value: 12 }&lt;br /&gt;
	]&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* An object indicating a type and id will create a selector for that [[Entities|content type]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
Entities.propTypes = {&lt;br /&gt;
    templateToUse: {type: &amp;#039;id&amp;#039;, content: &amp;#039;Template&amp;#039;}&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* An object indicating an array type and entity will create a multi selector for that [[Entities|content type]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;&lt;br /&gt;
Entities.propTypes = {&lt;br /&gt;
    tags: {type: &amp;#039;array&amp;#039;, content: &amp;#039;tag&amp;#039;}&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Native_app_builds&amp;diff=658</id>
		<title>Native app builds</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Native_app_builds&amp;diff=658"/>
		<updated>2024-04-24T16:07:37Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Cordova example */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack supports creating native app builds using the UI&amp;#039;s from your project primarily for iOS and Android with experimental desktop support through platforms like Electron. Native app builds are usually created for the purpose of making use of lower level API&amp;#039;s of devices. Note that doing so is required on iOS as an app will be rejected if its features are something that could have been made via the web instead. Because of this, SocialStack tries to stay out of the way of your actual native project and essentially provides tooling which generates a set of standalone web files for you such that you can use them in any project. You then only need to display these inside some kind of web UI. This web UI could be loaded by any native framework like Cordova, Electron or something like Coherent UI in Unreal Engine too. &lt;br /&gt;
&lt;br /&gt;
= Creating a web bundle =&lt;br /&gt;
&lt;br /&gt;
The [https://www.npmjs.com/package/socialstack socialstack command line tools] have a build command which will bundle any images into the pack folder ready for the app build &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack build&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The [https://www.npmjs.com/package/socialstack socialstack command line tools] have a buildapp command which will convert a running socialstack instance in to a web bundle for you.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack buildapp -apiUrl https://api.example.com/ -instanceUrl http://localhost:5050/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;apiUrl&amp;#039;&amp;#039;&amp;#039; flag indicates where your live API will be that the app will use. For example, if users login or you use [[Loop]], the requests will be sent to this URL.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;instanceUrl&amp;#039;&amp;#039;&amp;#039; flag indicates where a currently running instance is with the latest version of your page layouts and localisations. These are baked in to the app such that offline users can still navigate around your UI. You must always of course pay special consideration for the increased likelihood of an offline user using your project when you are providing it as a native app.&lt;br /&gt;
&lt;br /&gt;
= Cordova example =&lt;br /&gt;
&lt;br /&gt;
SocialStack aims to be unopinionated in terms of which platform you choose, however cordova tends to be fairly straight forward to get going, so here is an example of how to do it.&lt;br /&gt;
&lt;br /&gt;
* Create a new cordova project in a directory called &amp;#039;App&amp;#039; located alongside your UI and Api folders. A folder called &amp;#039;App&amp;#039; is recommended - or one entirely outside your SocialStack project tree - as a default SocialStack project understands what this folder represents and will ignore parts of it accordingly.&lt;br /&gt;
&lt;br /&gt;
* Configure the cordova xml to use mobile.en.html as its index (entry point) page.&lt;br /&gt;
&lt;br /&gt;
* Add a symlink from UI/public to App/www (including on Windows). At the time of writing, Cordova does not have a way of configuring this location and instead a symlink is very effective. Here&amp;#039;s an example of adding a link on Windows via the command prompt in your project directory (don&amp;#039;t run this in powershell):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
mklink /d &amp;quot;App/www&amp;quot; &amp;quot;../UI/public&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Run the buildapp command described above. This outputs a UI bundle in to the UI/public directory.&lt;br /&gt;
&lt;br /&gt;
* Run the cordova build command, and you should see your UI inside the native app which is communicating with the configured API URL.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
D:\Dev\project&lt;br /&gt;
 &lt;br /&gt;
socialstack build &lt;br /&gt;
socialstack buildapp -apiUrl http://127.0.0.1:5050/ -instanceUrl http://127.0.0.1:5050/&lt;br /&gt;
 &lt;br /&gt;
cordova platform rm android&lt;br /&gt;
cordova platform add android@12.0.1&lt;br /&gt;
cordova build  &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Native_app_builds&amp;diff=657</id>
		<title>Native app builds</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Native_app_builds&amp;diff=657"/>
		<updated>2024-04-24T16:07:28Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Creating a web bundle */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack supports creating native app builds using the UI&amp;#039;s from your project primarily for iOS and Android with experimental desktop support through platforms like Electron. Native app builds are usually created for the purpose of making use of lower level API&amp;#039;s of devices. Note that doing so is required on iOS as an app will be rejected if its features are something that could have been made via the web instead. Because of this, SocialStack tries to stay out of the way of your actual native project and essentially provides tooling which generates a set of standalone web files for you such that you can use them in any project. You then only need to display these inside some kind of web UI. This web UI could be loaded by any native framework like Cordova, Electron or something like Coherent UI in Unreal Engine too. &lt;br /&gt;
&lt;br /&gt;
= Creating a web bundle =&lt;br /&gt;
&lt;br /&gt;
The [https://www.npmjs.com/package/socialstack socialstack command line tools] have a build command which will bundle any images into the pack folder ready for the app build &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack build&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The [https://www.npmjs.com/package/socialstack socialstack command line tools] have a buildapp command which will convert a running socialstack instance in to a web bundle for you.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack buildapp -apiUrl https://api.example.com/ -instanceUrl http://localhost:5050/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;apiUrl&amp;#039;&amp;#039;&amp;#039; flag indicates where your live API will be that the app will use. For example, if users login or you use [[Loop]], the requests will be sent to this URL.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;instanceUrl&amp;#039;&amp;#039;&amp;#039; flag indicates where a currently running instance is with the latest version of your page layouts and localisations. These are baked in to the app such that offline users can still navigate around your UI. You must always of course pay special consideration for the increased likelihood of an offline user using your project when you are providing it as a native app.&lt;br /&gt;
&lt;br /&gt;
= Cordova example =&lt;br /&gt;
&lt;br /&gt;
SocialStack aims to be unopinionated in terms of which platform you choose, however cordova tends to be fairly straight forward to get going, so here is an example of how to do it.&lt;br /&gt;
&lt;br /&gt;
* Create a new cordova project in a directory called &amp;#039;App&amp;#039; located alongside your UI and Api folders. A folder called &amp;#039;App&amp;#039; is recommended - or one entirely outside your SocialStack project tree - as a default SocialStack project understands what this folder represents and will ignore parts of it accordingly.&lt;br /&gt;
&lt;br /&gt;
* Configure the cordova xml to use mobile.en.html as its index (entry point) page.&lt;br /&gt;
&lt;br /&gt;
* Add a symlink from UI/public to App/www (including on Windows). At the time of writing, Cordova does not have a way of configuring this location and instead a symlink is very effective. Here&amp;#039;s an example of adding a link on Windows via the command prompt in your project directory (don&amp;#039;t run this in powershell):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
mklink /d &amp;quot;App/www&amp;quot; &amp;quot;../UI/public&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Run the buildapp command described above. This outputs a UI bundle in to the UI/public directory.&lt;br /&gt;
&lt;br /&gt;
* Run the cordova build command, and you should see your UI inside the native app which is communicating with the configured API URL.&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Native_app_builds&amp;diff=656</id>
		<title>Native app builds</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Native_app_builds&amp;diff=656"/>
		<updated>2024-04-24T16:06:53Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Creating a web bundle */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack supports creating native app builds using the UI&amp;#039;s from your project primarily for iOS and Android with experimental desktop support through platforms like Electron. Native app builds are usually created for the purpose of making use of lower level API&amp;#039;s of devices. Note that doing so is required on iOS as an app will be rejected if its features are something that could have been made via the web instead. Because of this, SocialStack tries to stay out of the way of your actual native project and essentially provides tooling which generates a set of standalone web files for you such that you can use them in any project. You then only need to display these inside some kind of web UI. This web UI could be loaded by any native framework like Cordova, Electron or something like Coherent UI in Unreal Engine too. &lt;br /&gt;
&lt;br /&gt;
= Creating a web bundle =&lt;br /&gt;
&lt;br /&gt;
The [https://www.npmjs.com/package/socialstack socialstack command line tools] have a build command which will bundle any images into the pack folder ready for the app build &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack build&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The [https://www.npmjs.com/package/socialstack socialstack command line tools] have a buildapp command which will convert a running socialstack instance in to a web bundle for you.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack buildapp -apiUrl https://api.example.com/ -instanceUrl http://localhost:5050/&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;apiUrl&amp;#039;&amp;#039;&amp;#039; flag indicates where your live API will be that the app will use. For example, if users login or you use [[Loop]], the requests will be sent to this URL.&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;instanceUrl&amp;#039;&amp;#039;&amp;#039; flag indicates where a currently running instance is with the latest version of your page layouts and localisations. These are baked in to the app such that offline users can still navigate around your UI. You must always of course pay special consideration for the increased likelihood of an offline user using your project when you are providing it as a native app.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
D:\Dev\project&lt;br /&gt;
 &lt;br /&gt;
socialstack build &lt;br /&gt;
socialstack buildapp -apiUrl http://127.0.0.1:5050/ -instanceUrl http://127.0.0.1:5050/&lt;br /&gt;
 &lt;br /&gt;
cordova platform rm android&lt;br /&gt;
cordova platform add android@12.0.1&lt;br /&gt;
cordova build  &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cordova example =&lt;br /&gt;
&lt;br /&gt;
SocialStack aims to be unopinionated in terms of which platform you choose, however cordova tends to be fairly straight forward to get going, so here is an example of how to do it.&lt;br /&gt;
&lt;br /&gt;
* Create a new cordova project in a directory called &amp;#039;App&amp;#039; located alongside your UI and Api folders. A folder called &amp;#039;App&amp;#039; is recommended - or one entirely outside your SocialStack project tree - as a default SocialStack project understands what this folder represents and will ignore parts of it accordingly.&lt;br /&gt;
&lt;br /&gt;
* Configure the cordova xml to use mobile.en.html as its index (entry point) page.&lt;br /&gt;
&lt;br /&gt;
* Add a symlink from UI/public to App/www (including on Windows). At the time of writing, Cordova does not have a way of configuring this location and instead a symlink is very effective. Here&amp;#039;s an example of adding a link on Windows via the command prompt in your project directory (don&amp;#039;t run this in powershell):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
mklink /d &amp;quot;App/www&amp;quot; &amp;quot;../UI/public&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Run the buildapp command described above. This outputs a UI bundle in to the UI/public directory.&lt;br /&gt;
&lt;br /&gt;
* Run the cordova build command, and you should see your UI inside the native app which is communicating with the configured API URL.&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=655</id>
		<title>Search Elastic</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=655"/>
		<updated>2024-04-09T16:27:24Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Search Brand Service */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The elastic search module works alongside the [[Search Crawler]] to extract pages from the site into an elastic search instance to enable site wide search&lt;br /&gt;
&lt;br /&gt;
The module registers itself to listen for the search crawler events and creates locale based indexes in the elastic repo for any pages it receives&lt;br /&gt;
&lt;br /&gt;
When indexing the pages, if there is a primary data type then this will also be indexed against the page in &amp;#039;&amp;#039;&amp;#039;primaryObject&amp;#039;&amp;#039;&amp;#039;, this can then be used in query values as explained below&lt;br /&gt;
&lt;br /&gt;
The indexing process is controlled by execution of the search crawler via the site automations&lt;br /&gt;
&lt;br /&gt;
The module contains a Readme.txt file which gives help for the initial setup of elastic but the creation of the indexes/content are all automated.&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&lt;br /&gt;
Social stack cloud can now deploy a standalone elastic node to Digital Ocean, but the manual steps for local deployment are detailed below &lt;br /&gt;
&lt;br /&gt;
The basic setup steps are &lt;br /&gt;
&lt;br /&gt;
#Install the following socialstack modules&lt;br /&gt;
#*Api/SearchCrawler&lt;br /&gt;
#*Api/SearchElastic&lt;br /&gt;
#*UI/SiteSearch&lt;br /&gt;
#Mark any pages not to be indexed&lt;br /&gt;
#Install Elastic Search (see &amp;#039;&amp;#039;&amp;#039;[https://github.com/socialstack-core/modules/tree/master/Api/SearchElastic Readme.txt]&amp;#039;&amp;#039;&amp;#039; in the repo for help and guidance)&lt;br /&gt;
#Configure the sizing/scale in the &amp;#039;&amp;#039;&amp;#039;elasticsearch.yml&amp;#039;&amp;#039;&amp;#039; file (mainly vm memory usage)&lt;br /&gt;
#Start Elastic Search for the first time and get the master password and certificate fingerprint&lt;br /&gt;
#Add the url, credentials and fingerprint values to the module configuration via the ss admin panel&lt;br /&gt;
#Run the automation to index the site, the indexing progress is shown via the console&lt;br /&gt;
&lt;br /&gt;
The module also contains a postman collection which can be used to quick debug/verify the indexed content&lt;br /&gt;
&lt;br /&gt;
Once indexed there is a UI/SiteSearch module which can be used as the basis of the site search&lt;br /&gt;
&lt;br /&gt;
== Social Stack Cloud Install Steps == &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install apt-transport-https&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;echo &amp;quot;deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main&amp;quot; | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install elasticsearch&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Remember to get the elastic user password from the output and update the ss configuration&lt;br /&gt;
&lt;br /&gt;
&amp;quot;The generated password for the elastic built-in superuser is : xxxxxx&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Next get the ssl certificate fingerprint value for config&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The section you need looks like this (the base64encoded values)&lt;br /&gt;
SHA256 Fingerprint=85:AA:9E:19:2F:4C:72:01:01:66:41:87:28:03:52:B7:88:61:9C:93:E8:18:D2:9C:C7:60:92:F9:E0:84:EE:AA&lt;br /&gt;
&lt;br /&gt;
Now elasticsearch is installed, before starting look at the memory and diskspace section below&lt;br /&gt;
&lt;br /&gt;
Finnaly you can now start the elasticsearch service by executing&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These commands provide no feedback as to whether Elasticsearch was started successfully or not. &lt;br /&gt;
Instead, this information will be written in the log files located in /var/log/elasticsearch/ or you can run the command below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;systemctl status elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To configure Elasticsearch to start automatically when the system boots up, run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo /bin/systemctl daemon-reload&lt;br /&gt;
&lt;br /&gt;
sudo /bin/systemctl enable elasticsearch.service&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring memory and diskspace ==&lt;br /&gt;
&lt;br /&gt;
It is recommended to setup the vm with at least 4gb ram, when running 2gb we have run into issues &lt;br /&gt;
&lt;br /&gt;
edit the config file to set diskspace limits &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/elasticsearch.yml&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cluster.routing.allocation.disk.threshold_enabled: false&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.flood_stage: 250mb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.low: 2gb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.high: 1gb&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To disable geo location, if the project is not using the geolocation extensionsit can be disabled as otherwise an additional index is created and the contents automatically downloaded &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
ingest.geoip.downloader.enabled: false&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the memory usage (default 4gb) but for dev we have set it to the minimum as below &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/jvm.options&lt;br /&gt;
&lt;br /&gt;
Uncomment and set these values &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
-Xms128m&lt;br /&gt;
&lt;br /&gt;
-Xmx128m&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To check the heap size &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo cat /var/log/elasticsearch/elasticsearch.log | grep &amp;quot;heap size&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Admin == &lt;br /&gt;
&lt;br /&gt;
linux&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl stop elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the fingerprint &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To reset the user password&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== GET /v1/sitesearch/reset ===&lt;br /&gt;
&lt;br /&gt;
This removes any indexes from elastic to allow a clean start&lt;br /&gt;
&lt;br /&gt;
=== POST /v1/sitesearch/query ===&lt;br /&gt;
&lt;br /&gt;
This is the core search endpoint which currently accepts the following values &lt;br /&gt;
&lt;br /&gt;
;queryString&lt;br /&gt;
:The query value entered by the user and/or the &amp;#039;&amp;#039;&amp;#039;UI/SiteSearch&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; components, elastic search uses the Lucene query language so you can build complex queries &lt;br /&gt;
:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html&lt;br /&gt;
:e.g.&lt;br /&gt;
:&amp;lt;code&amp;gt;Africa AND primaryObject.name:Botswana&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;primaryObject.price:(&amp;gt;=1000 AND &amp;lt;5000)&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;((quick AND fox) OR (brown AND fox) OR fox) AND NOT dog&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;tags&lt;br /&gt;
: This expects a list of comma separated tags, if any are supplied the query will only return results which contain all the passed tag values&lt;br /&gt;
&lt;br /&gt;
;contentTypes&lt;br /&gt;
: This expects a list of comma separated content types, if any are supplied the query will only return results which contain all the passed content types, these are normally the primary data object for the page&lt;br /&gt;
&lt;br /&gt;
;pageSize&lt;br /&gt;
: This expects the page size and will default to 10 if not populated&lt;br /&gt;
&lt;br /&gt;
;pageIndex&lt;br /&gt;
: This expects the starting page number and will default to 1&lt;br /&gt;
&lt;br /&gt;
;aggregationFields&lt;br /&gt;
: This expects a list of comma separated field names, if any are supplied then aggregations/facets are returned for the fields, a good example of this can be be found in &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; component&lt;br /&gt;
&lt;br /&gt;
;allFields&lt;br /&gt;
: If this is set to true then any indexed content will be searchable, this includes any &amp;#039;include&amp;#039; files and metadata from the primary object, if you want to restrict the search to the managed text fields then set this to false (the default)&lt;br /&gt;
&lt;br /&gt;
== Search taxonomy and filters/facets ==&lt;br /&gt;
You can filter or define facets(aggregations in ElasticSerach terminology) on any field exposed to Elastic from the social stack application, but they work best on metadata type fields such as tags, price etc &lt;br /&gt;
&lt;br /&gt;
A basic example would be the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; field.&lt;br /&gt;
&lt;br /&gt;
But a better solution is to setup contextual tags using the &amp;#039;&amp;#039;&amp;#039;Api/Categories&amp;#039;&amp;#039;&amp;#039; component and tags.&lt;br /&gt;
&lt;br /&gt;
This allows you to give some context to a tag, so for example you could create an &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category, then when adding a new tag for &amp;#039;&amp;#039;&amp;#039;Cow&amp;#039;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; you would set their category to be &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Any tags linked to a category can be referenced as &amp;#039;&amp;#039;&amp;#039;taxonomy.xxxxx&amp;#039;&amp;#039;&amp;#039; so in this example &amp;#039;&amp;#039;&amp;#039;taxonomy.animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
These can then be referenced to show a contextual filter in the UI, the example below will pass back an aggregation of any &amp;#039;&amp;#039;&amp;#039;animals&amp;#039;&amp;#039;&amp;#039; tags in the result set, &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; then uses these render an auto generated filter.&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		 {&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.animals&amp;quot;,&lt;br /&gt;
				&amp;quot;contentTypes&amp;quot;: &amp;quot;ItineraryPageDataType&amp;quot;&lt;br /&gt;
            }   &lt;br /&gt;
         } &lt;br /&gt;
         ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When tags are assigned to category and indexed, you will expose the core tag values and also some auto generated taxonomy fields.&lt;br /&gt;
&lt;br /&gt;
An example of the indexed data in Elastic looks something like the example below where &amp;#039;&amp;#039;&amp;#039;Africa&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;Botswana&amp;#039;&amp;#039;&amp;#039; are plain tags but &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; has been linked to the &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; value is still exposed in the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; element as that is used for free text search ranking. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;tags&amp;quot;: [&lt;br /&gt;
           &amp;quot;Africa&amp;quot;,&lt;br /&gt;
           &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
           &amp;quot;Horse&amp;quot;&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;taxonomy&amp;quot;: {&lt;br /&gt;
      &amp;quot;animals&amp;quot;: [&lt;br /&gt;
         &amp;quot;Horse&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== UI Components  ==&lt;br /&gt;
;UI/ContentSearch&lt;br /&gt;
&lt;br /&gt;
This provides a good example with filtering and paging see the amba repo &lt;br /&gt;
&lt;br /&gt;
The component will also allow you to override the default values/titles for any aggregation using the aggregationOverrides property, this can be useful to add grouping to values such as regions/countries etc&lt;br /&gt;
&lt;br /&gt;
The buckets property is optional and by default would normally be an empty array [] but can be used to override labels and the order of options&lt;br /&gt;
&lt;br /&gt;
Some basic examples of how overrides can be used to control the search filters&lt;br /&gt;
&lt;br /&gt;
=== 2 Column example with set items for destination ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.destination&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Destination&amp;quot;,&lt;br /&gt;
	&amp;quot;columns&amp;quot;: 2,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Madagascar&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Scotland&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Bonny Scotland&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Svalbard&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Swapping the default select boxes for radio buttons ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to show as a single checkbox ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isFeatured&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Only show featured events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to return both checked and unchecked values ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isCompleted&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Show completed events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;returnAllValues&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a simple select dropdown ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.country&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Location&amp;quot;,&lt;br /&gt;
	&amp;quot;all&amp;quot;: &amp;quot;Any location&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Allow multiple unrelated values to be chosen ===&lt;br /&gt;
&lt;br /&gt;
Override the default functionality which disables any original filter options if the search results no longer contain results containing the values, so a drill down work flow &lt;br /&gt;
dog -&amp;gt; spaniel -&amp;gt; english&lt;br /&gt;
&lt;br /&gt;
With &amp;#039;allowAllOptions&amp;#039; other unrelated documents can be brought into the results so for example when filtering events you could include multiple unrelated types such as &amp;quot;gala&amp;quot; OR &amp;quot;conference&amp;quot; when by default if you choose &amp;quot;gala&amp;quot;, conferences would be disabled&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.event type&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A complete example is shown below using separate components for the filters and results (2 column layout)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/SearchContext&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;comment&amp;quot;: &amp;quot;SearchContext used to link search engine, results and filters&amp;quot;&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
			    &lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/SiteContainer&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
				&amp;quot;t&amp;quot;: &amp;quot;UI/SiteRow&amp;quot;,&lt;br /&gt;
				&amp;quot;d&amp;quot;: {},&lt;br /&gt;
				&amp;quot;c&amp;quot;: [&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;sidebar&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: {&lt;br /&gt;
							&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
							&amp;quot;d&amp;quot;: {&lt;br /&gt;
								&amp;quot;title&amp;quot;: &amp;quot;Search insights&amp;quot;,&lt;br /&gt;
								&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.brand,taxonomy.audience,taxonomy.insight category,tags&amp;quot;,&lt;br /&gt;
								&amp;quot;contentTypes&amp;quot;: &amp;quot;Insight&amp;quot;,&lt;br /&gt;
								&amp;quot;resultLayout&amp;quot;: &amp;quot;list&amp;quot;,&lt;br /&gt;
								&amp;quot;itemRenderer&amp;quot;: &amp;quot;UI/InsightResultItem&amp;quot;,&lt;br /&gt;
								&amp;quot;sortOptions&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Ranking&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;_score&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;aggregationOverrides&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.brand&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View insights by brand&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Both&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any audience type&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View by insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any category&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;tags&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Topics&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any topics&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;hideResults&amp;quot;: true,&lt;br /&gt;
								&amp;quot;hideTitle&amp;quot;: true,&lt;br /&gt;
								&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
							},&lt;br /&gt;
							&amp;quot;i&amp;quot;: 4&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;i&amp;quot;: 6&lt;br /&gt;
					},&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;content&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: [&lt;br /&gt;
							{&lt;br /&gt;
								&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/ContentSearchResults&amp;quot;,&lt;br /&gt;
								&amp;quot;d&amp;quot;: {&lt;br /&gt;
									&amp;quot;hideTotal&amp;quot;: true,&lt;br /&gt;
									&amp;quot;searchType&amp;quot;: &amp;quot;insights&amp;quot;,&lt;br /&gt;
									&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
								},&lt;br /&gt;
								&amp;quot;i&amp;quot;: 14&lt;br /&gt;
							}&lt;br /&gt;
						],&lt;br /&gt;
						&amp;quot;i&amp;quot;: 11&lt;br /&gt;
					}&lt;br /&gt;
				],&lt;br /&gt;
				&amp;quot;i&amp;quot;: 12&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;i&amp;quot;: 13&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;i&amp;quot;: 3&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Multi domain sites  ==&lt;br /&gt;
&lt;br /&gt;
There are a couple of services which can be used to extend/manage how content is indexed for multi domain sites &lt;br /&gt;
&lt;br /&gt;
=== Search Domain Service ===&lt;br /&gt;
&lt;br /&gt;
This service interacts with events during the indexing cycle to determine which indexes are used for queries and storage based on the SiteDomains service&lt;br /&gt;
&lt;br /&gt;
The basic implementation stores each sub-site into a separate index based on the domain code&lt;br /&gt;
&lt;br /&gt;
=== Search Brand Service ===&lt;br /&gt;
&lt;br /&gt;
This service interacts with events during the indexing cycle to expose additional meta data to the indexed documents based on domains and taxonomy&lt;br /&gt;
&lt;br /&gt;
This can be used when you wish to keep a single search index for the instance but want to be able to filter based on the domain/taxonomy&lt;br /&gt;
&lt;br /&gt;
The basic implementation looks for tags with a category as defined in config, in this example &amp;#039;brand&amp;#039; alongside the page domain to add additional meta data of metaData.brand.xxx or metadata.brand.yyy&lt;br /&gt;
&lt;br /&gt;
You can also define mappings which check the individual fields within the primaryObject and if they match expose a defined value &lt;br /&gt;
&lt;br /&gt;
In the example below we check for a boolean for Bigcorp and/or the city being London.&lt;br /&gt;
&lt;br /&gt;
Sample config &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;IsEnabled&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Category&amp;quot;:&amp;quot;brand&amp;quot;,&lt;br /&gt;
  &amp;quot;MetaDataKey&amp;quot;:&amp;quot;brand&amp;quot;,&lt;br /&gt;
  &amp;quot;Mappings&amp;quot;: [&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Field&amp;quot;:&amp;quot;IsBigCorp&amp;quot;,&lt;br /&gt;
            &amp;quot;Value&amp;quot;:&amp;quot;true&amp;quot;,&lt;br /&gt;
            &amp;quot;Brand&amp;quot;:&amp;quot;bigcorp&amp;quot;&lt;br /&gt;
        },&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Field&amp;quot;:&amp;quot;City&amp;quot;,&lt;br /&gt;
            &amp;quot;Value&amp;quot;:&amp;quot;london&amp;quot;,&lt;br /&gt;
            &amp;quot;Brand&amp;quot;:&amp;quot;UK&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=654</id>
		<title>Search Elastic</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=654"/>
		<updated>2024-04-09T16:12:37Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Search Brand Service */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The elastic search module works alongside the [[Search Crawler]] to extract pages from the site into an elastic search instance to enable site wide search&lt;br /&gt;
&lt;br /&gt;
The module registers itself to listen for the search crawler events and creates locale based indexes in the elastic repo for any pages it receives&lt;br /&gt;
&lt;br /&gt;
When indexing the pages, if there is a primary data type then this will also be indexed against the page in &amp;#039;&amp;#039;&amp;#039;primaryObject&amp;#039;&amp;#039;&amp;#039;, this can then be used in query values as explained below&lt;br /&gt;
&lt;br /&gt;
The indexing process is controlled by execution of the search crawler via the site automations&lt;br /&gt;
&lt;br /&gt;
The module contains a Readme.txt file which gives help for the initial setup of elastic but the creation of the indexes/content are all automated.&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&lt;br /&gt;
Social stack cloud can now deploy a standalone elastic node to Digital Ocean, but the manual steps for local deployment are detailed below &lt;br /&gt;
&lt;br /&gt;
The basic setup steps are &lt;br /&gt;
&lt;br /&gt;
#Install the following socialstack modules&lt;br /&gt;
#*Api/SearchCrawler&lt;br /&gt;
#*Api/SearchElastic&lt;br /&gt;
#*UI/SiteSearch&lt;br /&gt;
#Mark any pages not to be indexed&lt;br /&gt;
#Install Elastic Search (see &amp;#039;&amp;#039;&amp;#039;[https://github.com/socialstack-core/modules/tree/master/Api/SearchElastic Readme.txt]&amp;#039;&amp;#039;&amp;#039; in the repo for help and guidance)&lt;br /&gt;
#Configure the sizing/scale in the &amp;#039;&amp;#039;&amp;#039;elasticsearch.yml&amp;#039;&amp;#039;&amp;#039; file (mainly vm memory usage)&lt;br /&gt;
#Start Elastic Search for the first time and get the master password and certificate fingerprint&lt;br /&gt;
#Add the url, credentials and fingerprint values to the module configuration via the ss admin panel&lt;br /&gt;
#Run the automation to index the site, the indexing progress is shown via the console&lt;br /&gt;
&lt;br /&gt;
The module also contains a postman collection which can be used to quick debug/verify the indexed content&lt;br /&gt;
&lt;br /&gt;
Once indexed there is a UI/SiteSearch module which can be used as the basis of the site search&lt;br /&gt;
&lt;br /&gt;
== Social Stack Cloud Install Steps == &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install apt-transport-https&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;echo &amp;quot;deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main&amp;quot; | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install elasticsearch&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Remember to get the elastic user password from the output and update the ss configuration&lt;br /&gt;
&lt;br /&gt;
&amp;quot;The generated password for the elastic built-in superuser is : xxxxxx&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Next get the ssl certificate fingerprint value for config&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The section you need looks like this (the base64encoded values)&lt;br /&gt;
SHA256 Fingerprint=85:AA:9E:19:2F:4C:72:01:01:66:41:87:28:03:52:B7:88:61:9C:93:E8:18:D2:9C:C7:60:92:F9:E0:84:EE:AA&lt;br /&gt;
&lt;br /&gt;
Now elasticsearch is installed, before starting look at the memory and diskspace section below&lt;br /&gt;
&lt;br /&gt;
Finnaly you can now start the elasticsearch service by executing&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These commands provide no feedback as to whether Elasticsearch was started successfully or not. &lt;br /&gt;
Instead, this information will be written in the log files located in /var/log/elasticsearch/ or you can run the command below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;systemctl status elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To configure Elasticsearch to start automatically when the system boots up, run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo /bin/systemctl daemon-reload&lt;br /&gt;
&lt;br /&gt;
sudo /bin/systemctl enable elasticsearch.service&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring memory and diskspace ==&lt;br /&gt;
&lt;br /&gt;
It is recommended to setup the vm with at least 4gb ram, when running 2gb we have run into issues &lt;br /&gt;
&lt;br /&gt;
edit the config file to set diskspace limits &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/elasticsearch.yml&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cluster.routing.allocation.disk.threshold_enabled: false&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.flood_stage: 250mb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.low: 2gb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.high: 1gb&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To disable geo location, if the project is not using the geolocation extensionsit can be disabled as otherwise an additional index is created and the contents automatically downloaded &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
ingest.geoip.downloader.enabled: false&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the memory usage (default 4gb) but for dev we have set it to the minimum as below &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/jvm.options&lt;br /&gt;
&lt;br /&gt;
Uncomment and set these values &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
-Xms128m&lt;br /&gt;
&lt;br /&gt;
-Xmx128m&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To check the heap size &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo cat /var/log/elasticsearch/elasticsearch.log | grep &amp;quot;heap size&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Admin == &lt;br /&gt;
&lt;br /&gt;
linux&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl stop elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the fingerprint &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To reset the user password&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== GET /v1/sitesearch/reset ===&lt;br /&gt;
&lt;br /&gt;
This removes any indexes from elastic to allow a clean start&lt;br /&gt;
&lt;br /&gt;
=== POST /v1/sitesearch/query ===&lt;br /&gt;
&lt;br /&gt;
This is the core search endpoint which currently accepts the following values &lt;br /&gt;
&lt;br /&gt;
;queryString&lt;br /&gt;
:The query value entered by the user and/or the &amp;#039;&amp;#039;&amp;#039;UI/SiteSearch&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; components, elastic search uses the Lucene query language so you can build complex queries &lt;br /&gt;
:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html&lt;br /&gt;
:e.g.&lt;br /&gt;
:&amp;lt;code&amp;gt;Africa AND primaryObject.name:Botswana&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;primaryObject.price:(&amp;gt;=1000 AND &amp;lt;5000)&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;((quick AND fox) OR (brown AND fox) OR fox) AND NOT dog&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;tags&lt;br /&gt;
: This expects a list of comma separated tags, if any are supplied the query will only return results which contain all the passed tag values&lt;br /&gt;
&lt;br /&gt;
;contentTypes&lt;br /&gt;
: This expects a list of comma separated content types, if any are supplied the query will only return results which contain all the passed content types, these are normally the primary data object for the page&lt;br /&gt;
&lt;br /&gt;
;pageSize&lt;br /&gt;
: This expects the page size and will default to 10 if not populated&lt;br /&gt;
&lt;br /&gt;
;pageIndex&lt;br /&gt;
: This expects the starting page number and will default to 1&lt;br /&gt;
&lt;br /&gt;
;aggregationFields&lt;br /&gt;
: This expects a list of comma separated field names, if any are supplied then aggregations/facets are returned for the fields, a good example of this can be be found in &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; component&lt;br /&gt;
&lt;br /&gt;
;allFields&lt;br /&gt;
: If this is set to true then any indexed content will be searchable, this includes any &amp;#039;include&amp;#039; files and metadata from the primary object, if you want to restrict the search to the managed text fields then set this to false (the default)&lt;br /&gt;
&lt;br /&gt;
== Search taxonomy and filters/facets ==&lt;br /&gt;
You can filter or define facets(aggregations in ElasticSerach terminology) on any field exposed to Elastic from the social stack application, but they work best on metadata type fields such as tags, price etc &lt;br /&gt;
&lt;br /&gt;
A basic example would be the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; field.&lt;br /&gt;
&lt;br /&gt;
But a better solution is to setup contextual tags using the &amp;#039;&amp;#039;&amp;#039;Api/Categories&amp;#039;&amp;#039;&amp;#039; component and tags.&lt;br /&gt;
&lt;br /&gt;
This allows you to give some context to a tag, so for example you could create an &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category, then when adding a new tag for &amp;#039;&amp;#039;&amp;#039;Cow&amp;#039;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; you would set their category to be &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Any tags linked to a category can be referenced as &amp;#039;&amp;#039;&amp;#039;taxonomy.xxxxx&amp;#039;&amp;#039;&amp;#039; so in this example &amp;#039;&amp;#039;&amp;#039;taxonomy.animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
These can then be referenced to show a contextual filter in the UI, the example below will pass back an aggregation of any &amp;#039;&amp;#039;&amp;#039;animals&amp;#039;&amp;#039;&amp;#039; tags in the result set, &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; then uses these render an auto generated filter.&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		 {&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.animals&amp;quot;,&lt;br /&gt;
				&amp;quot;contentTypes&amp;quot;: &amp;quot;ItineraryPageDataType&amp;quot;&lt;br /&gt;
            }   &lt;br /&gt;
         } &lt;br /&gt;
         ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When tags are assigned to category and indexed, you will expose the core tag values and also some auto generated taxonomy fields.&lt;br /&gt;
&lt;br /&gt;
An example of the indexed data in Elastic looks something like the example below where &amp;#039;&amp;#039;&amp;#039;Africa&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;Botswana&amp;#039;&amp;#039;&amp;#039; are plain tags but &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; has been linked to the &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; value is still exposed in the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; element as that is used for free text search ranking. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;tags&amp;quot;: [&lt;br /&gt;
           &amp;quot;Africa&amp;quot;,&lt;br /&gt;
           &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
           &amp;quot;Horse&amp;quot;&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;taxonomy&amp;quot;: {&lt;br /&gt;
      &amp;quot;animals&amp;quot;: [&lt;br /&gt;
         &amp;quot;Horse&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== UI Components  ==&lt;br /&gt;
;UI/ContentSearch&lt;br /&gt;
&lt;br /&gt;
This provides a good example with filtering and paging see the amba repo &lt;br /&gt;
&lt;br /&gt;
The component will also allow you to override the default values/titles for any aggregation using the aggregationOverrides property, this can be useful to add grouping to values such as regions/countries etc&lt;br /&gt;
&lt;br /&gt;
The buckets property is optional and by default would normally be an empty array [] but can be used to override labels and the order of options&lt;br /&gt;
&lt;br /&gt;
Some basic examples of how overrides can be used to control the search filters&lt;br /&gt;
&lt;br /&gt;
=== 2 Column example with set items for destination ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.destination&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Destination&amp;quot;,&lt;br /&gt;
	&amp;quot;columns&amp;quot;: 2,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Madagascar&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Scotland&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Bonny Scotland&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Svalbard&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Swapping the default select boxes for radio buttons ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to show as a single checkbox ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isFeatured&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Only show featured events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to return both checked and unchecked values ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isCompleted&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Show completed events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;returnAllValues&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a simple select dropdown ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.country&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Location&amp;quot;,&lt;br /&gt;
	&amp;quot;all&amp;quot;: &amp;quot;Any location&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Allow multiple unrelated values to be chosen ===&lt;br /&gt;
&lt;br /&gt;
Override the default functionality which disables any original filter options if the search results no longer contain results containing the values, so a drill down work flow &lt;br /&gt;
dog -&amp;gt; spaniel -&amp;gt; english&lt;br /&gt;
&lt;br /&gt;
With &amp;#039;allowAllOptions&amp;#039; other unrelated documents can be brought into the results so for example when filtering events you could include multiple unrelated types such as &amp;quot;gala&amp;quot; OR &amp;quot;conference&amp;quot; when by default if you choose &amp;quot;gala&amp;quot;, conferences would be disabled&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.event type&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A complete example is shown below using separate components for the filters and results (2 column layout)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/SearchContext&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;comment&amp;quot;: &amp;quot;SearchContext used to link search engine, results and filters&amp;quot;&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
			    &lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/SiteContainer&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
				&amp;quot;t&amp;quot;: &amp;quot;UI/SiteRow&amp;quot;,&lt;br /&gt;
				&amp;quot;d&amp;quot;: {},&lt;br /&gt;
				&amp;quot;c&amp;quot;: [&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;sidebar&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: {&lt;br /&gt;
							&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
							&amp;quot;d&amp;quot;: {&lt;br /&gt;
								&amp;quot;title&amp;quot;: &amp;quot;Search insights&amp;quot;,&lt;br /&gt;
								&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.brand,taxonomy.audience,taxonomy.insight category,tags&amp;quot;,&lt;br /&gt;
								&amp;quot;contentTypes&amp;quot;: &amp;quot;Insight&amp;quot;,&lt;br /&gt;
								&amp;quot;resultLayout&amp;quot;: &amp;quot;list&amp;quot;,&lt;br /&gt;
								&amp;quot;itemRenderer&amp;quot;: &amp;quot;UI/InsightResultItem&amp;quot;,&lt;br /&gt;
								&amp;quot;sortOptions&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Ranking&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;_score&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;aggregationOverrides&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.brand&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View insights by brand&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Both&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any audience type&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View by insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any category&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;tags&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Topics&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any topics&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;hideResults&amp;quot;: true,&lt;br /&gt;
								&amp;quot;hideTitle&amp;quot;: true,&lt;br /&gt;
								&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
							},&lt;br /&gt;
							&amp;quot;i&amp;quot;: 4&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;i&amp;quot;: 6&lt;br /&gt;
					},&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;content&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: [&lt;br /&gt;
							{&lt;br /&gt;
								&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/ContentSearchResults&amp;quot;,&lt;br /&gt;
								&amp;quot;d&amp;quot;: {&lt;br /&gt;
									&amp;quot;hideTotal&amp;quot;: true,&lt;br /&gt;
									&amp;quot;searchType&amp;quot;: &amp;quot;insights&amp;quot;,&lt;br /&gt;
									&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
								},&lt;br /&gt;
								&amp;quot;i&amp;quot;: 14&lt;br /&gt;
							}&lt;br /&gt;
						],&lt;br /&gt;
						&amp;quot;i&amp;quot;: 11&lt;br /&gt;
					}&lt;br /&gt;
				],&lt;br /&gt;
				&amp;quot;i&amp;quot;: 12&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;i&amp;quot;: 13&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;i&amp;quot;: 3&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Multi domain sites  ==&lt;br /&gt;
&lt;br /&gt;
There are a couple of services which can be used to extend/manage how content is indexed for multi domain sites &lt;br /&gt;
&lt;br /&gt;
=== Search Domain Service ===&lt;br /&gt;
&lt;br /&gt;
This service interacts with events during the indexing cycle to determine which indexes are used for queries and storage based on the SiteDomains service&lt;br /&gt;
&lt;br /&gt;
The basic implementation stores each sub-site into a separate index based on the domain code&lt;br /&gt;
&lt;br /&gt;
=== Search Brand Service ===&lt;br /&gt;
&lt;br /&gt;
This service interacts with events during the indexing cycle to expose additional meta data to the indexed documents based on domains and taxonomy&lt;br /&gt;
&lt;br /&gt;
This can be used when you wish to keep a single search index for the instance but want to be able to filter based on the domain/taxonomy&lt;br /&gt;
&lt;br /&gt;
The basic implementation looks for tags with a category of &amp;#039;brand&amp;#039; alongside the page domain to add additional meta data of metaData.brand.xxx or metadata.brand.yyy&lt;br /&gt;
&lt;br /&gt;
Sample config &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;IsEnabled&amp;quot;: true,&lt;br /&gt;
  &amp;quot;Category&amp;quot;:&amp;quot;brand&amp;quot;,&lt;br /&gt;
  &amp;quot;MetaDataKey&amp;quot;:&amp;quot;brand&amp;quot;,&lt;br /&gt;
  &amp;quot;Mappings&amp;quot;: [&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Field&amp;quot;:&amp;quot;IsBigCorp&amp;quot;,&lt;br /&gt;
            &amp;quot;Value&amp;quot;:&amp;quot;true&amp;quot;,&lt;br /&gt;
            &amp;quot;Brand&amp;quot;:&amp;quot;bigcorp&amp;quot;&lt;br /&gt;
        },&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Field&amp;quot;:&amp;quot;City&amp;quot;,&lt;br /&gt;
            &amp;quot;Value&amp;quot;:&amp;quot;london&amp;quot;,&lt;br /&gt;
            &amp;quot;Brand&amp;quot;:&amp;quot;UK&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=653</id>
		<title>Search Elastic</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=653"/>
		<updated>2024-04-09T15:47:40Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The elastic search module works alongside the [[Search Crawler]] to extract pages from the site into an elastic search instance to enable site wide search&lt;br /&gt;
&lt;br /&gt;
The module registers itself to listen for the search crawler events and creates locale based indexes in the elastic repo for any pages it receives&lt;br /&gt;
&lt;br /&gt;
When indexing the pages, if there is a primary data type then this will also be indexed against the page in &amp;#039;&amp;#039;&amp;#039;primaryObject&amp;#039;&amp;#039;&amp;#039;, this can then be used in query values as explained below&lt;br /&gt;
&lt;br /&gt;
The indexing process is controlled by execution of the search crawler via the site automations&lt;br /&gt;
&lt;br /&gt;
The module contains a Readme.txt file which gives help for the initial setup of elastic but the creation of the indexes/content are all automated.&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&lt;br /&gt;
Social stack cloud can now deploy a standalone elastic node to Digital Ocean, but the manual steps for local deployment are detailed below &lt;br /&gt;
&lt;br /&gt;
The basic setup steps are &lt;br /&gt;
&lt;br /&gt;
#Install the following socialstack modules&lt;br /&gt;
#*Api/SearchCrawler&lt;br /&gt;
#*Api/SearchElastic&lt;br /&gt;
#*UI/SiteSearch&lt;br /&gt;
#Mark any pages not to be indexed&lt;br /&gt;
#Install Elastic Search (see &amp;#039;&amp;#039;&amp;#039;[https://github.com/socialstack-core/modules/tree/master/Api/SearchElastic Readme.txt]&amp;#039;&amp;#039;&amp;#039; in the repo for help and guidance)&lt;br /&gt;
#Configure the sizing/scale in the &amp;#039;&amp;#039;&amp;#039;elasticsearch.yml&amp;#039;&amp;#039;&amp;#039; file (mainly vm memory usage)&lt;br /&gt;
#Start Elastic Search for the first time and get the master password and certificate fingerprint&lt;br /&gt;
#Add the url, credentials and fingerprint values to the module configuration via the ss admin panel&lt;br /&gt;
#Run the automation to index the site, the indexing progress is shown via the console&lt;br /&gt;
&lt;br /&gt;
The module also contains a postman collection which can be used to quick debug/verify the indexed content&lt;br /&gt;
&lt;br /&gt;
Once indexed there is a UI/SiteSearch module which can be used as the basis of the site search&lt;br /&gt;
&lt;br /&gt;
== Social Stack Cloud Install Steps == &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install apt-transport-https&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;echo &amp;quot;deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main&amp;quot; | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install elasticsearch&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Remember to get the elastic user password from the output and update the ss configuration&lt;br /&gt;
&lt;br /&gt;
&amp;quot;The generated password for the elastic built-in superuser is : xxxxxx&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Next get the ssl certificate fingerprint value for config&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The section you need looks like this (the base64encoded values)&lt;br /&gt;
SHA256 Fingerprint=85:AA:9E:19:2F:4C:72:01:01:66:41:87:28:03:52:B7:88:61:9C:93:E8:18:D2:9C:C7:60:92:F9:E0:84:EE:AA&lt;br /&gt;
&lt;br /&gt;
Now elasticsearch is installed, before starting look at the memory and diskspace section below&lt;br /&gt;
&lt;br /&gt;
Finnaly you can now start the elasticsearch service by executing&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These commands provide no feedback as to whether Elasticsearch was started successfully or not. &lt;br /&gt;
Instead, this information will be written in the log files located in /var/log/elasticsearch/ or you can run the command below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;systemctl status elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To configure Elasticsearch to start automatically when the system boots up, run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo /bin/systemctl daemon-reload&lt;br /&gt;
&lt;br /&gt;
sudo /bin/systemctl enable elasticsearch.service&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring memory and diskspace ==&lt;br /&gt;
&lt;br /&gt;
It is recommended to setup the vm with at least 4gb ram, when running 2gb we have run into issues &lt;br /&gt;
&lt;br /&gt;
edit the config file to set diskspace limits &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/elasticsearch.yml&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cluster.routing.allocation.disk.threshold_enabled: false&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.flood_stage: 250mb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.low: 2gb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.high: 1gb&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To disable geo location, if the project is not using the geolocation extensionsit can be disabled as otherwise an additional index is created and the contents automatically downloaded &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
ingest.geoip.downloader.enabled: false&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the memory usage (default 4gb) but for dev we have set it to the minimum as below &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/jvm.options&lt;br /&gt;
&lt;br /&gt;
Uncomment and set these values &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
-Xms128m&lt;br /&gt;
&lt;br /&gt;
-Xmx128m&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To check the heap size &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo cat /var/log/elasticsearch/elasticsearch.log | grep &amp;quot;heap size&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Admin == &lt;br /&gt;
&lt;br /&gt;
linux&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl stop elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the fingerprint &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To reset the user password&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== GET /v1/sitesearch/reset ===&lt;br /&gt;
&lt;br /&gt;
This removes any indexes from elastic to allow a clean start&lt;br /&gt;
&lt;br /&gt;
=== POST /v1/sitesearch/query ===&lt;br /&gt;
&lt;br /&gt;
This is the core search endpoint which currently accepts the following values &lt;br /&gt;
&lt;br /&gt;
;queryString&lt;br /&gt;
:The query value entered by the user and/or the &amp;#039;&amp;#039;&amp;#039;UI/SiteSearch&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; components, elastic search uses the Lucene query language so you can build complex queries &lt;br /&gt;
:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html&lt;br /&gt;
:e.g.&lt;br /&gt;
:&amp;lt;code&amp;gt;Africa AND primaryObject.name:Botswana&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;primaryObject.price:(&amp;gt;=1000 AND &amp;lt;5000)&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;((quick AND fox) OR (brown AND fox) OR fox) AND NOT dog&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;tags&lt;br /&gt;
: This expects a list of comma separated tags, if any are supplied the query will only return results which contain all the passed tag values&lt;br /&gt;
&lt;br /&gt;
;contentTypes&lt;br /&gt;
: This expects a list of comma separated content types, if any are supplied the query will only return results which contain all the passed content types, these are normally the primary data object for the page&lt;br /&gt;
&lt;br /&gt;
;pageSize&lt;br /&gt;
: This expects the page size and will default to 10 if not populated&lt;br /&gt;
&lt;br /&gt;
;pageIndex&lt;br /&gt;
: This expects the starting page number and will default to 1&lt;br /&gt;
&lt;br /&gt;
;aggregationFields&lt;br /&gt;
: This expects a list of comma separated field names, if any are supplied then aggregations/facets are returned for the fields, a good example of this can be be found in &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; component&lt;br /&gt;
&lt;br /&gt;
;allFields&lt;br /&gt;
: If this is set to true then any indexed content will be searchable, this includes any &amp;#039;include&amp;#039; files and metadata from the primary object, if you want to restrict the search to the managed text fields then set this to false (the default)&lt;br /&gt;
&lt;br /&gt;
== Search taxonomy and filters/facets ==&lt;br /&gt;
You can filter or define facets(aggregations in ElasticSerach terminology) on any field exposed to Elastic from the social stack application, but they work best on metadata type fields such as tags, price etc &lt;br /&gt;
&lt;br /&gt;
A basic example would be the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; field.&lt;br /&gt;
&lt;br /&gt;
But a better solution is to setup contextual tags using the &amp;#039;&amp;#039;&amp;#039;Api/Categories&amp;#039;&amp;#039;&amp;#039; component and tags.&lt;br /&gt;
&lt;br /&gt;
This allows you to give some context to a tag, so for example you could create an &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category, then when adding a new tag for &amp;#039;&amp;#039;&amp;#039;Cow&amp;#039;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; you would set their category to be &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Any tags linked to a category can be referenced as &amp;#039;&amp;#039;&amp;#039;taxonomy.xxxxx&amp;#039;&amp;#039;&amp;#039; so in this example &amp;#039;&amp;#039;&amp;#039;taxonomy.animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
These can then be referenced to show a contextual filter in the UI, the example below will pass back an aggregation of any &amp;#039;&amp;#039;&amp;#039;animals&amp;#039;&amp;#039;&amp;#039; tags in the result set, &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; then uses these render an auto generated filter.&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		 {&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.animals&amp;quot;,&lt;br /&gt;
				&amp;quot;contentTypes&amp;quot;: &amp;quot;ItineraryPageDataType&amp;quot;&lt;br /&gt;
            }   &lt;br /&gt;
         } &lt;br /&gt;
         ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When tags are assigned to category and indexed, you will expose the core tag values and also some auto generated taxonomy fields.&lt;br /&gt;
&lt;br /&gt;
An example of the indexed data in Elastic looks something like the example below where &amp;#039;&amp;#039;&amp;#039;Africa&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;Botswana&amp;#039;&amp;#039;&amp;#039; are plain tags but &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; has been linked to the &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; value is still exposed in the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; element as that is used for free text search ranking. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;tags&amp;quot;: [&lt;br /&gt;
           &amp;quot;Africa&amp;quot;,&lt;br /&gt;
           &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
           &amp;quot;Horse&amp;quot;&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;taxonomy&amp;quot;: {&lt;br /&gt;
      &amp;quot;animals&amp;quot;: [&lt;br /&gt;
         &amp;quot;Horse&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== UI Components  ==&lt;br /&gt;
;UI/ContentSearch&lt;br /&gt;
&lt;br /&gt;
This provides a good example with filtering and paging see the amba repo &lt;br /&gt;
&lt;br /&gt;
The component will also allow you to override the default values/titles for any aggregation using the aggregationOverrides property, this can be useful to add grouping to values such as regions/countries etc&lt;br /&gt;
&lt;br /&gt;
The buckets property is optional and by default would normally be an empty array [] but can be used to override labels and the order of options&lt;br /&gt;
&lt;br /&gt;
Some basic examples of how overrides can be used to control the search filters&lt;br /&gt;
&lt;br /&gt;
=== 2 Column example with set items for destination ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.destination&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Destination&amp;quot;,&lt;br /&gt;
	&amp;quot;columns&amp;quot;: 2,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Madagascar&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Scotland&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Bonny Scotland&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Svalbard&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Swapping the default select boxes for radio buttons ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to show as a single checkbox ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isFeatured&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Only show featured events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to return both checked and unchecked values ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isCompleted&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Show completed events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;returnAllValues&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a simple select dropdown ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.country&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Location&amp;quot;,&lt;br /&gt;
	&amp;quot;all&amp;quot;: &amp;quot;Any location&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Allow multiple unrelated values to be chosen ===&lt;br /&gt;
&lt;br /&gt;
Override the default functionality which disables any original filter options if the search results no longer contain results containing the values, so a drill down work flow &lt;br /&gt;
dog -&amp;gt; spaniel -&amp;gt; english&lt;br /&gt;
&lt;br /&gt;
With &amp;#039;allowAllOptions&amp;#039; other unrelated documents can be brought into the results so for example when filtering events you could include multiple unrelated types such as &amp;quot;gala&amp;quot; OR &amp;quot;conference&amp;quot; when by default if you choose &amp;quot;gala&amp;quot;, conferences would be disabled&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.event type&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A complete example is shown below using separate components for the filters and results (2 column layout)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/SearchContext&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;comment&amp;quot;: &amp;quot;SearchContext used to link search engine, results and filters&amp;quot;&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
			    &lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/SiteContainer&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
				&amp;quot;t&amp;quot;: &amp;quot;UI/SiteRow&amp;quot;,&lt;br /&gt;
				&amp;quot;d&amp;quot;: {},&lt;br /&gt;
				&amp;quot;c&amp;quot;: [&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;sidebar&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: {&lt;br /&gt;
							&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
							&amp;quot;d&amp;quot;: {&lt;br /&gt;
								&amp;quot;title&amp;quot;: &amp;quot;Search insights&amp;quot;,&lt;br /&gt;
								&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.brand,taxonomy.audience,taxonomy.insight category,tags&amp;quot;,&lt;br /&gt;
								&amp;quot;contentTypes&amp;quot;: &amp;quot;Insight&amp;quot;,&lt;br /&gt;
								&amp;quot;resultLayout&amp;quot;: &amp;quot;list&amp;quot;,&lt;br /&gt;
								&amp;quot;itemRenderer&amp;quot;: &amp;quot;UI/InsightResultItem&amp;quot;,&lt;br /&gt;
								&amp;quot;sortOptions&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Ranking&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;_score&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;aggregationOverrides&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.brand&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View insights by brand&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Both&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any audience type&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View by insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any category&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;tags&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Topics&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any topics&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;hideResults&amp;quot;: true,&lt;br /&gt;
								&amp;quot;hideTitle&amp;quot;: true,&lt;br /&gt;
								&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
							},&lt;br /&gt;
							&amp;quot;i&amp;quot;: 4&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;i&amp;quot;: 6&lt;br /&gt;
					},&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;content&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: [&lt;br /&gt;
							{&lt;br /&gt;
								&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/ContentSearchResults&amp;quot;,&lt;br /&gt;
								&amp;quot;d&amp;quot;: {&lt;br /&gt;
									&amp;quot;hideTotal&amp;quot;: true,&lt;br /&gt;
									&amp;quot;searchType&amp;quot;: &amp;quot;insights&amp;quot;,&lt;br /&gt;
									&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
								},&lt;br /&gt;
								&amp;quot;i&amp;quot;: 14&lt;br /&gt;
							}&lt;br /&gt;
						],&lt;br /&gt;
						&amp;quot;i&amp;quot;: 11&lt;br /&gt;
					}&lt;br /&gt;
				],&lt;br /&gt;
				&amp;quot;i&amp;quot;: 12&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;i&amp;quot;: 13&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;i&amp;quot;: 3&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Multi domain sites  ==&lt;br /&gt;
&lt;br /&gt;
There are a couple of services which can be used to extend/manage how content is indexed for multi domain sites &lt;br /&gt;
&lt;br /&gt;
=== Search Domain Service ===&lt;br /&gt;
&lt;br /&gt;
This service interacts with events during the indexing cycle to determine which indexes are used for queries and storage based on the SiteDomains service&lt;br /&gt;
&lt;br /&gt;
The basic implementation stores each sub-site into a separate index based on the domain code&lt;br /&gt;
&lt;br /&gt;
=== Search Brand Service ===&lt;br /&gt;
&lt;br /&gt;
This service interacts with events during the indexing cycle to expose additional meta data to the indexed documents based on domains and taxonomy&lt;br /&gt;
&lt;br /&gt;
This can be used when you wish to keep a single search index for the instance but want to be able to filter based on the domain/taxonomy&lt;br /&gt;
&lt;br /&gt;
The basic implementation looks for tags with a category of &amp;#039;brand&amp;#039; alongside the page domain to add additional meta data of metaData.brand.xxx or metadata.brand.yyy&lt;br /&gt;
&lt;br /&gt;
Sample config &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;IsEnabled&amp;quot;: true,&lt;br /&gt;
  &amp;quot;MetaDataKey&amp;quot;:&amp;quot;brand&amp;quot;,&lt;br /&gt;
  &amp;quot;Mappings&amp;quot;: [&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Field&amp;quot;:&amp;quot;IsBigCorp&amp;quot;,&lt;br /&gt;
            &amp;quot;Value&amp;quot;:&amp;quot;true&amp;quot;,&lt;br /&gt;
            &amp;quot;Brand&amp;quot;:&amp;quot;bigcorp&amp;quot;&lt;br /&gt;
        },&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Field&amp;quot;:&amp;quot;City&amp;quot;,&lt;br /&gt;
            &amp;quot;Value&amp;quot;:&amp;quot;london&amp;quot;,&lt;br /&gt;
            &amp;quot;Brand&amp;quot;:&amp;quot;UK&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=647</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=647"/>
		<updated>2024-04-08T08:53:04Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates (Manual Steps) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps)==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates, adding your domain names as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /etc/nginx/sites-enabled/*.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
ssl_certificate /etc/ssl/cert/fullchain-1.pem;&lt;br /&gt;
ssl_certificate_key /etc/ssl/cert/privkey-1.pem;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next you need to use our new files to replace the existing files&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem /etc/ssl/cert/fullchain-1.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem /etc/ssl/cert/privkey-1.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain-1.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
-rwxrwxrwx 1 root root  3677 Mar 19 14:58 fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey-1.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
-rwxrwxrwx 1 root root  1650 Mar 19 14:58 privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=646</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=646"/>
		<updated>2024-04-08T08:51:41Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates (Manual Steps) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps)==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates, adding your domain names as necessary&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The output from certbot should be something like - &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /etc/nginx/sites-enabled/*.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
	ssl_certificate /etc/ssl/cert/fullchain-1.pem;&lt;br /&gt;
	ssl_certificate_key /etc/ssl/cert/privkey-1.pem;&lt;br /&gt;
&lt;br /&gt;
So now need to use out new files to replace these&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem /etc/ssl/cert/fullchain-1.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem /etc/ssl/cert/privkey-1.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain-1.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
-rwxrwxrwx 1 root root  3677 Mar 19 14:58 fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey-1.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
-rwxrwxrwx 1 root root  1650 Mar 19 14:58 privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=645</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=645"/>
		<updated>2024-04-08T08:50:34Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates (Manual Steps) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps)==&lt;br /&gt;
&lt;br /&gt;
Here are the steps to manually create and install custom ssl certs, this may be required for multi-domain sites&lt;br /&gt;
&lt;br /&gt;
Onetime install/update certbot&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo apt install certbot python3-certbot-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the certificates &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
certbot certonly --nginx -d amb-sst.stage.socialstack.dev -d amb-amba-sst.socialstack.dev -d amb-bga-sst.socialstack.dev&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Putput will be something like &lt;br /&gt;
&lt;br /&gt;
	- Congratulations! Your certificate and chain have been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
	   Your key file has been saved at:&lt;br /&gt;
	   /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
	   Your cert will expire on 2024-07-07. To obtain a new or tweaked&lt;br /&gt;
	   version of this certificate in the future, simply run certbot&lt;br /&gt;
	   again. To non-interactively renew *all* of your certificates, run&lt;br /&gt;
	   &amp;quot;certbot renew&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	 - Your account credentials have been saved in your Certbot&lt;br /&gt;
	   configuration directory at /etc/letsencrypt. You should make a&lt;br /&gt;
	   secure backup of this folder now. This configuration directory will&lt;br /&gt;
	   also contain certificates and private keys obtained by Certbot so&lt;br /&gt;
	   making regular backups of this folder is ideal.&lt;br /&gt;
&lt;br /&gt;
So the two important files are &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
/etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now just need to link them back into the nginx config &lt;br /&gt;
&lt;br /&gt;
Check the site config to get the ssl config&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
more /etc/nginx/sites-enabled/*.conf&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
so usually something like &lt;br /&gt;
&lt;br /&gt;
	ssl_certificate /etc/ssl/cert/fullchain-1.pem;&lt;br /&gt;
	ssl_certificate_key /etc/ssl/cert/privkey-1.pem;&lt;br /&gt;
&lt;br /&gt;
So now need to use out new files to replace these&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /etc/ssl/cert&lt;br /&gt;
&lt;br /&gt;
tar -cvf certs.tar *.pem&lt;br /&gt;
&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem /etc/ssl/cert/fullchain-1.pem&lt;br /&gt;
ln -s /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem /etc/ssl/cert/privkey-1.pem&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So you should now have 2 symbolic link files to the new certs as below &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
-rw-r--r-- 1 root root 20480 Apr  8 08:33 certs.tar&lt;br /&gt;
lrwxrwxrwx 1 root root    65 Apr  8 08:34 fullchain-1.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/fullchain.pem&lt;br /&gt;
-rwxrwxrwx 1 root root  3677 Mar 19 14:58 fullchain.pem&lt;br /&gt;
lrwxrwxrwx 1 root root    63 Apr  8 08:34 privkey-1.pem -&amp;gt; /etc/letsencrypt/live/amb-sst.stage.socialstack.dev/privkey.pem&lt;br /&gt;
-rwxrwxrwx 1 root root  1650 Mar 19 14:58 privkey.pem&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally restart nginx&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nginx -s reload&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=644</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=644"/>
		<updated>2024-04-08T08:46:46Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps)== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=643</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=643"/>
		<updated>2024-04-08T08:46:13Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
To do it manually see here &lt;br /&gt;
&lt;br /&gt;
https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04&lt;br /&gt;
&lt;br /&gt;
== Certificates (Manual Steps)== &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=635</id>
		<title>SocialStack Cloud</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=SocialStack_Cloud&amp;diff=635"/>
		<updated>2024-02-27T14:41:26Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Certificates */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SocialStack Cloud is a service for simplifying the process of deploying and managing a cluster of servers running a dotnet project. It can create servers on a variety of cloud platforms (AWS, DigitalOcean, Azure) and then setup routing including DNS and loadbalancers. This is always in the form of &amp;quot;bring your own cloud platform account&amp;quot; to ensure that you have maximum access to your own sites and services.&lt;br /&gt;
&lt;br /&gt;
Currently SocialStack Cloud is a non-public service and requires an administrator account to use. As SocialStack Cloud is itself a SocialStack site, anyone who is already an administrator can grant access to a new user account by changing its role [https://cloud.socialstack.dev/en-admin/ once they have registered here].&lt;br /&gt;
&lt;br /&gt;
= Creating a project =&lt;br /&gt;
&lt;br /&gt;
To create a new project, first go to the [https://cloud.socialstack.dev/projects/ project list] and click the new project button. You&amp;#039;ll need to give your project a name and specify the cloud platform account to use. Cloud platform config is optional if you are only importing connection details for existing servers. You can also specify multiple platforms simultaneously which can be useful for large multicloud clusters. Here&amp;#039;s sample platform configuration for the 3 main cloud platforms:&lt;br /&gt;
&lt;br /&gt;
== AWS ==&lt;br /&gt;
&lt;br /&gt;
Create an access key [https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/net-dg-users-roles.html following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;AWS&amp;quot;: {&lt;br /&gt;
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;abcabc..&amp;quot;,&lt;br /&gt;
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;abcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DigitalOcean ==&lt;br /&gt;
&lt;br /&gt;
Create an [https://cloud.digitalocean.com/account/api/tokens access token for the account you would like to use]. It&amp;#039;s recommended to create a team, optionally per project, so you can better monitor individual project resources and their costs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;DigitalOcean&amp;quot;: {&lt;br /&gt;
        &amp;quot;ApiKey&amp;quot;: &amp;quot;abcabcabcabc..&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Azure ==&lt;br /&gt;
&lt;br /&gt;
Create an Azure active directory service principal [https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal following the guide here].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;Azure&amp;quot;: {&lt;br /&gt;
        &amp;quot;TenantId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientId&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;ClientSecret&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
        &amp;quot;SubscriptionId&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Cloud Build =&lt;br /&gt;
&lt;br /&gt;
Optionally during project creation, specify where your source is. Currently it must be a git repository and one which is accessible via classic git authentication (A personal access token over https rather than an SSH key).&lt;br /&gt;
&lt;br /&gt;
Cloud build works by using the stage server of the project. This way the code of a given project is always isolated into a dedicated cluster of servers and it can do a pull rather than a full checkout for often large gains in build speed. Because of this, there are a few important notes:&lt;br /&gt;
&lt;br /&gt;
* The stage server needs enough RAM to run your site and the build. The &amp;quot;medium&amp;quot; server option is usually appropriate. This is usually a battle between Babel (the javascript transpiler) which uses some 600MB on its own and MySQL which will take as much RAM as it can get. If a build is running very slowly or the stage site appears to have gone offline whilst a build is happening, it&amp;#039;s because the server has ran out of RAM. Connecting to the server and stopping MySQL is usually the easiest way to allow a build through on a small stage server.&lt;br /&gt;
&lt;br /&gt;
* The checkout goes in to /var/www/repo on the first stage server created.&lt;br /&gt;
&lt;br /&gt;
* If the checkout failed because the git credentials are wrong, the git credentials go in a git store located at ~/.git-credential of the root user.&lt;br /&gt;
&lt;br /&gt;
== Triggering builds ==&lt;br /&gt;
&lt;br /&gt;
Projects with cloud build config have a Build button. This build button is simply a link to a URL which can be used from any pipeline to trigger a build automatically, as the link does not require authentication as it contains a non-guessable randomly generated unique project key. As a build is performed on a stage server, updating the stage service itself is near instantaneous and happens as part of a build cycle.&lt;br /&gt;
&lt;br /&gt;
If you click the button manually, don&amp;#039;t close the page that opens as it will give you the build log in realtime.&lt;br /&gt;
&lt;br /&gt;
== Deploying builds ==&lt;br /&gt;
&lt;br /&gt;
When you&amp;#039;re ready to deploy, projects have a deploy button. Like the build button, this contains a link that can be used in external pipelines as well. This works by copying the build artifacts from the projects indicated build server (the first created stage server) and then uploading them to resources in the production environment via SSH. It waits for the upload to complete for every node before proceeding further. Once every node has a copy of the files, it then proceeds to one by one reload the services.&lt;br /&gt;
&lt;br /&gt;
=== Deployment rotation ===&lt;br /&gt;
&lt;br /&gt;
When Socialstack cloud deploys a project, it will move existing code out of the way by putting it into a directory called deploy which is usually found at either /var/www/stage/deploy or /var/www/prod/deploy. Note that it specifically does not do this with the Content directory as the Content directory is considered to be stateful. The previous build is put into a directory called &amp;#039;&amp;#039;prev&amp;#039;&amp;#039;. Like this:&lt;br /&gt;
&lt;br /&gt;
* If deploy/prev-2 exists, delete it.&lt;br /&gt;
* If deploy/prev-1 exists, rename it to prev-2.&lt;br /&gt;
* If deploy/prev exists, rename it to prev-1.&lt;br /&gt;
* Create deploy/prev and move the previous build into it.&lt;br /&gt;
&lt;br /&gt;
This means only the previous 3 deployments are retained and you can find them in the deploy folder should you need to recover them. Note that because this happens primarily using the move command rather than by copying it is usually an extremely fast switching operation.&lt;br /&gt;
&lt;br /&gt;
= Creating a server =&lt;br /&gt;
&lt;br /&gt;
Once you&amp;#039;ve created a project, you&amp;#039;ll be able to start creating resources. They can be created in any order but it is suggested to create a singular medium size stage server first:&lt;br /&gt;
&lt;br /&gt;
[[File:Creating-stage-server.png]]&lt;br /&gt;
&lt;br /&gt;
The available platform and region options are based on whichever platform(s) you have configured in your project.&lt;br /&gt;
&lt;br /&gt;
= Adding existing servers =&lt;br /&gt;
&lt;br /&gt;
This mechanism exists to add servers created manually/ externally. It is a way to use SocialStack Cloud features without providing an API key. It also works as a way to add things like managed MySQL instances via providing the username and password from the host.&lt;br /&gt;
&lt;br /&gt;
= How servers are deployed =&lt;br /&gt;
&lt;br /&gt;
This is a high level overview of the steps that SocialStack cloud goes through whilst deploying a resource. Because there is currently a very broad spectrum of combinations, it is suggested to either [https://source.socialstack.dev/infrastructure/socialstack-cloud/-/blob/master/Api/Servers/ServerService.cs check the source] or just deploy the combination you&amp;#039;re interested in and then check its deploy log as it will list everything that it ran aside from the contents of uploaded files. There is a button to view the creation log on [https://cloud.socialstack.dev/projects/ a server&amp;#039;s information panel].&lt;br /&gt;
&lt;br /&gt;
SocialStack Cloud will go through a series of phases when setting up a server. The current phase a server is in can be seen on a particular servers information screen via the [https://cloud.socialstack.dev/projects/ project&amp;#039;s page]. The setup process will vary depending on which resource is being setup, which cloud and which operating system it is on.&lt;br /&gt;
&lt;br /&gt;
== Server creation ==&lt;br /&gt;
&lt;br /&gt;
When the OS is unspecified, SocialStack cloud will deploy AL2 (Amazon Linux 2) on AWS and Ubuntu everywhere else. It will then wait for the server to exist and indicate that it is ready. It does this by connecting over SSH periodically as a user called &amp;quot;sscloud&amp;quot; to check the [https://cloudinit.readthedocs.io/en/latest/ cloudinit] status.&lt;br /&gt;
&lt;br /&gt;
== Installing software ==&lt;br /&gt;
&lt;br /&gt;
When the resource is ready for installation, SocialStack cloud then executes install commands over SSH. On most SocialStack servers, this installs dotnet, [https://www.nginx.com/ Nginx] and MySQL, then tunes the OS for typical SocialStack high volume throughput. The complete log of everything that is executed during server setup is available via the &amp;quot;view creation log&amp;quot; button on a particular resource.&lt;br /&gt;
&lt;br /&gt;
== DNS ==&lt;br /&gt;
&lt;br /&gt;
By default all SocialStack cloud projects are given an automatically generated DNS subdomain under .stage.socialstack.dev and the DNS records are added to the DNS zone for socialstack.dev which is currently hosted on rackspace.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Known issue with the rackspace DNS zone&amp;#039;&amp;#039;&amp;#039;: it is too large! Lately this has been surfacing as multiple failed certificate requests. You just have to be a little patient with it and retry.&lt;br /&gt;
&lt;br /&gt;
== Certificates ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud uses the DNS authorization technique to obtain a Let&amp;#039;s Encrypt certificate for the site too. DNS authorization is useful for multiple reasons: firstly it is a wildcard certificate so fewer are needed; larger deployments can typically all use the same wildcard certificate for example. Secondly http based authorization can be challenging when loadbalancers are involved as the response will be directed to a random server.&lt;br /&gt;
&lt;br /&gt;
It is able to perform the DNS authorization technique for any domain that it has record access to. Currently that is any domain on the rackspace account.&lt;br /&gt;
&lt;br /&gt;
Once a certificate has been obtained, SocialStack cloud then checks if it can apply it anywhere. It will apply it if a particular resource has a matching environment and address. The certificate is applied by connecting to the server over SSH and then uploading the certificate in to /etc/ssl/cert and then reloading nginx (the web server). This certificate location can also be seen in the nginx configuration which is usually located at /etc/nginx/sites-enabled/ (this can vary between OS&amp;#039;s but is very commonly here).&lt;br /&gt;
&lt;br /&gt;
To do it manually see here &lt;br /&gt;
&lt;br /&gt;
https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04&lt;br /&gt;
&lt;br /&gt;
= Managing Services =&lt;br /&gt;
&lt;br /&gt;
As the deployment can vary due to both the software being deployed and the OS it is being deployed on, this is just a rough guideline for common scenarios. As before it&amp;#039;s recommended to take a look at the creation log to get a better idea of what software is actually running, particularly if the &lt;br /&gt;
&lt;br /&gt;
== SocialStack API on Ubuntu ==&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud installs a service called either &amp;quot;stage&amp;quot; or &amp;quot;prod&amp;quot; which is managed by systemD on ubuntu. This means you can restart it etc like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# superuser only&lt;br /&gt;
sudo su&lt;br /&gt;
&lt;br /&gt;
service stage stop&lt;br /&gt;
service stage start&lt;br /&gt;
service stage restart&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And view its current log using journalctl like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
journalctl -u stage -n 100&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Accessing a server =&lt;br /&gt;
&lt;br /&gt;
Because servers are deployed in to a &amp;quot;bring your own cloud&amp;quot; environment, there are two ways to access a server. Either through the underlying cloud platform if you have access to that, or via the &amp;quot;Let me in&amp;quot; button on a particular server&amp;#039;s information panel. This button requires that your SocialStack cloud account has an SSH public key associated with it. This button tells SocialStack cloud to add your public key to the target server&amp;#039;s user list if it wasn&amp;#039;t already there, and SocialStack Cloud adds it via connecting over SSH to the target server.&lt;br /&gt;
&lt;br /&gt;
When you use the &amp;quot;Let me in&amp;quot; button it will also provide a useful summary of the connection details.&lt;br /&gt;
&lt;br /&gt;
= Server health reporting =&lt;br /&gt;
&lt;br /&gt;
All resources created on SocialStack Cloud are configured with a ReportTo URL. This tells the resource to frequently ping SocialStack Cloud with a health update. The current list of healthy resources is used to configure loadbalancers.&lt;br /&gt;
&lt;br /&gt;
SocialStack cloud retains historical health data in the form of (millions of) revisions of the &amp;quot;MonitoredHost&amp;quot; content type.&lt;br /&gt;
&lt;br /&gt;
= SocialStack Cloud Source =&lt;br /&gt;
&lt;br /&gt;
The SocialStack project is currently a private repository [https://source.socialstack.dev/infrastructure/socialstack-cloud located here].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Entities&amp;diff=630</id>
		<title>Entities</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Entities&amp;diff=630"/>
		<updated>2024-02-05T15:38:12Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Field Attributes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;An entity is an object which stores fields of information, usually in your database but can also be in memory only. Virtually everything in a SocialStack API is an entity of some form - roles, pages, menu items and so on.&lt;br /&gt;
&lt;br /&gt;
The quickest way to create an entity is to generate a template of one like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack generate Api/TodoItems&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Use a plural name here, otherwise you&amp;#039;ll be asked if you wanted to make a simple service instead. This is to avoid class naming collisions, i.e. the above ultimately creates an entity class called &amp;#039;&amp;#039;&amp;#039;Api.TodoItems.TodoItem&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=Fields=&lt;br /&gt;
&lt;br /&gt;
Edit your entity .cs file and you&amp;#039;ll see some sample fields. Simply adding a field will allow it to be stored in the database and it will also be editable via the admin panel and appear in your API requests including Loop. Use a C# property instead if you don&amp;#039;t want it to be stored in the database but did want it to appear everywhere else.&lt;br /&gt;
&lt;br /&gt;
=Field names=&lt;br /&gt;
&lt;br /&gt;
Some field names are treated specially for convenience. This is the list of all current patterns checked for by AutoFormService.&lt;br /&gt;
&lt;br /&gt;
== Refs ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;string {Name}Ref&amp;#039;&amp;#039;&amp;#039; - A string field ending with &amp;#039;&amp;#039;&amp;#039;Ref&amp;#039;&amp;#039;&amp;#039; indicates a file. You&amp;#039;ll get a file selector in the admin panel for these. They should be given to the getRef function at UI/Functions/GetRef to either obtain a url or display it directly.&lt;br /&gt;
&lt;br /&gt;
Whilst it&amp;#039;s always recommended to end a field with ref so it&amp;#039;s clear what data people are working with, you can get the same file selector on a field that doesn&amp;#039;t end in ref by setting the type to image or file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;file&amp;quot;)]&lt;br /&gt;
public string MyFileSelector;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== Dates ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;DateTime {Name}, DateTime? {Name}&amp;#039;&amp;#039;&amp;#039; - You should always postfix your date fields with &amp;#039;&amp;#039;&amp;#039;Utc&amp;#039;&amp;#039;&amp;#039; to make it obvious what the fields intended timezone is, however, any DateTime or DateTime? field will display a date selector. This is the same as setting the type to datetime:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;datetime&amp;quot;)]&lt;br /&gt;
public DateTime MyDateSelector;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== Booleans ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;bool {Name}, bool? {Name}&amp;#039;&amp;#039;&amp;#039; - Displays a checkbox on any bool field. This is the same as setting the type to checkbox:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;checkbox&amp;quot;)]&lt;br /&gt;
public int ZeroOrOne;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== Canvas editors ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;string {Name}Json&amp;#039;&amp;#039;&amp;#039; - A string field ending with &amp;#039;&amp;#039;&amp;#039;Json&amp;#039;&amp;#039;&amp;#039; indicates canvas JSON by default. You&amp;#039;ll get a wysiwig editor. If you want a regular Json editor instead, use a data attribute, like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;contentType&amp;quot;, &amp;quot;application/json&amp;quot;)]&lt;br /&gt;
public string ConfigurationJson;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get a canvas editor on a field that does not end in Json, set the type to canvas:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;canvas&amp;quot;)]&lt;br /&gt;
public string Configuration;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== Colour pickers ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;string {Name}Color, string {Name}Colour&amp;#039;&amp;#039;&amp;#039; - Displays a colour picker. To get a colour picker on any other field, set the type to color:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;color&amp;quot;)]&lt;br /&gt;
public string Background;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== User selectors ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;uint {Name}UserId&amp;#039;&amp;#039;&amp;#039;, &amp;#039;&amp;#039;&amp;#039;int {Name}UserId&amp;#039;&amp;#039;&amp;#039; - Displays a user selector. To display the same user selector on some other field, set the module like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;Admin/User/Select&amp;quot;)]&lt;br /&gt;
public uint ModeratedBy;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by telling it to render with a regular UI/Input:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;UI/Input&amp;quot;)]&lt;br /&gt;
public uint ModeratedByUserId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Entity selectors ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;uint {EntityName}Id&amp;#039;&amp;#039;&amp;#039; - a uint field ending with &amp;#039;&amp;#039;&amp;#039;Id&amp;#039;&amp;#039;&amp;#039; will be checked to see if you have an entity, i.e. any content type on your site, with the same name. If one exists, a selector for that entity will be used on the admin panel.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
public uint PageId; // Will display a dropdown of Page entries.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by telling it to render with a regular UI/Input:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;UI/Input&amp;quot;)]&lt;br /&gt;
public uint PageId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you would like the same content selector on a field which does not match this pattern, set the module and contentType like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;Admin/ContentSelect&amp;quot;)]&lt;br /&gt;
[Data(&amp;quot;contentType&amp;quot;, &amp;quot;page&amp;quot;)]&lt;br /&gt;
public uint ParentPageId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with all Data attributes, contentType here is just a prop of the Admin/ContentSelect component. I.e. we&amp;#039;re telling the form system to use a specific component to render this field and giving it some additional props to use as well.&lt;br /&gt;
&lt;br /&gt;
=== Search mode for large sets ===&lt;br /&gt;
&lt;br /&gt;
Because this is a generic content selector component, it makes some best guesses about what field to use as the display text in the dropdown. If you have a very large amount of content, a dropdown would be inappropriate and should display a searchable set instead to avoid listing thousands of things on an awkward dropdown. It has a search mode to help with this:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;title&amp;quot;)]&lt;br /&gt;
public uint PageId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This search Data attribute is a prop recognised by the Admin/ContentSelect component, so you can turn it on wherever you&amp;#039;re using that component. The attributes value is the name of the field that is used during the search - in this case, we&amp;#039;re searching pages based on if the search term matches anything in the page title.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;Admin/ContentSelect&amp;quot;)]&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;title&amp;quot;)]&lt;br /&gt;
[Data(&amp;quot;contentType&amp;quot;, &amp;quot;page&amp;quot;)]&lt;br /&gt;
public uint ParentPageId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Field Attributes=&lt;br /&gt;
&lt;br /&gt;
SocialStack conveniences above are often all that is necessary, however, there are certainly times where it is useful to have a field display with a custom editor component, or not display at all. You can add attributes to your fields to customise the field behaviour in a variety of ways:&lt;br /&gt;
&lt;br /&gt;
* [Module(&amp;quot;Admin/ModuleName&amp;quot;)] - This attribute tells the admin panel to render your field with the given module. The module will receive everything that UI/Input would have, so that&amp;#039;s props like label, name and value.&lt;br /&gt;
&lt;br /&gt;
* [Module(true)] - This attribute tells the admin panel to hide your field with the given module.&lt;br /&gt;
&lt;br /&gt;
* [Data(&amp;quot;propName&amp;quot;, &amp;quot;propValue&amp;quot;)] - This attribute passes additional props to the admin panel rendering of your component. Works best with custom modules, but can also be used to override certain default behaviours.&lt;br /&gt;
&lt;br /&gt;
* [Localized] - Often used on names and descriptions, this attribute indicates that the field varies by locale. Works with any field type meaning it can also be used to make, for example, a price vary by locale too. When you use localized fields and there are more than one locale on your site, you&amp;#039;ll get a dropdown on the admin panel to switch between editing different locales.&lt;br /&gt;
&lt;br /&gt;
* [Meta(&amp;quot;metaFieldName&amp;quot;)] - Marks this field as the source of a given named [[Content Type Meta Fields|meta field]].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Entities&amp;diff=629</id>
		<title>Entities</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Entities&amp;diff=629"/>
		<updated>2024-02-05T15:22:03Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Field Attributes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;An entity is an object which stores fields of information, usually in your database but can also be in memory only. Virtually everything in a SocialStack API is an entity of some form - roles, pages, menu items and so on.&lt;br /&gt;
&lt;br /&gt;
The quickest way to create an entity is to generate a template of one like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack generate Api/TodoItems&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Use a plural name here, otherwise you&amp;#039;ll be asked if you wanted to make a simple service instead. This is to avoid class naming collisions, i.e. the above ultimately creates an entity class called &amp;#039;&amp;#039;&amp;#039;Api.TodoItems.TodoItem&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
=Fields=&lt;br /&gt;
&lt;br /&gt;
Edit your entity .cs file and you&amp;#039;ll see some sample fields. Simply adding a field will allow it to be stored in the database and it will also be editable via the admin panel and appear in your API requests including Loop. Use a C# property instead if you don&amp;#039;t want it to be stored in the database but did want it to appear everywhere else.&lt;br /&gt;
&lt;br /&gt;
=Field names=&lt;br /&gt;
&lt;br /&gt;
Some field names are treated specially for convenience. This is the list of all current patterns checked for by AutoFormService.&lt;br /&gt;
&lt;br /&gt;
== Refs ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;string {Name}Ref&amp;#039;&amp;#039;&amp;#039; - A string field ending with &amp;#039;&amp;#039;&amp;#039;Ref&amp;#039;&amp;#039;&amp;#039; indicates a file. You&amp;#039;ll get a file selector in the admin panel for these. They should be given to the getRef function at UI/Functions/GetRef to either obtain a url or display it directly.&lt;br /&gt;
&lt;br /&gt;
Whilst it&amp;#039;s always recommended to end a field with ref so it&amp;#039;s clear what data people are working with, you can get the same file selector on a field that doesn&amp;#039;t end in ref by setting the type to image or file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;file&amp;quot;)]&lt;br /&gt;
public string MyFileSelector;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== Dates ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;DateTime {Name}, DateTime? {Name}&amp;#039;&amp;#039;&amp;#039; - You should always postfix your date fields with &amp;#039;&amp;#039;&amp;#039;Utc&amp;#039;&amp;#039;&amp;#039; to make it obvious what the fields intended timezone is, however, any DateTime or DateTime? field will display a date selector. This is the same as setting the type to datetime:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;datetime&amp;quot;)]&lt;br /&gt;
public DateTime MyDateSelector;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== Booleans ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;bool {Name}, bool? {Name}&amp;#039;&amp;#039;&amp;#039; - Displays a checkbox on any bool field. This is the same as setting the type to checkbox:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;checkbox&amp;quot;)]&lt;br /&gt;
public int ZeroOrOne;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== Canvas editors ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;string {Name}Json&amp;#039;&amp;#039;&amp;#039; - A string field ending with &amp;#039;&amp;#039;&amp;#039;Json&amp;#039;&amp;#039;&amp;#039; indicates canvas JSON by default. You&amp;#039;ll get a wysiwig editor. If you want a regular Json editor instead, use a data attribute, like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;contentType&amp;quot;, &amp;quot;application/json&amp;quot;)]&lt;br /&gt;
public string ConfigurationJson;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get a canvas editor on a field that does not end in Json, set the type to canvas:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;canvas&amp;quot;)]&lt;br /&gt;
public string Configuration;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== Colour pickers ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;string {Name}Color, string {Name}Colour&amp;#039;&amp;#039;&amp;#039; - Displays a colour picker. To get a colour picker on any other field, set the type to color:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;type&amp;quot;, &amp;quot;color&amp;quot;)]&lt;br /&gt;
public string Background;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by setting the type to something else (default: text).&lt;br /&gt;
&lt;br /&gt;
== User selectors ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;uint {Name}UserId&amp;#039;&amp;#039;&amp;#039;, &amp;#039;&amp;#039;&amp;#039;int {Name}UserId&amp;#039;&amp;#039;&amp;#039; - Displays a user selector. To display the same user selector on some other field, set the module like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;Admin/User/Select&amp;quot;)]&lt;br /&gt;
public uint ModeratedBy;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by telling it to render with a regular UI/Input:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;UI/Input&amp;quot;)]&lt;br /&gt;
public uint ModeratedByUserId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Entity selectors ==&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;uint {EntityName}Id&amp;#039;&amp;#039;&amp;#039; - a uint field ending with &amp;#039;&amp;#039;&amp;#039;Id&amp;#039;&amp;#039;&amp;#039; will be checked to see if you have an entity, i.e. any content type on your site, with the same name. If one exists, a selector for that entity will be used on the admin panel.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
public uint PageId; // Will display a dropdown of Page entries.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you don&amp;#039;t want this behaviour on a field that fits this pattern, override it by telling it to render with a regular UI/Input:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;UI/Input&amp;quot;)]&lt;br /&gt;
public uint PageId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you would like the same content selector on a field which does not match this pattern, set the module and contentType like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;Admin/ContentSelect&amp;quot;)]&lt;br /&gt;
[Data(&amp;quot;contentType&amp;quot;, &amp;quot;page&amp;quot;)]&lt;br /&gt;
public uint ParentPageId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with all Data attributes, contentType here is just a prop of the Admin/ContentSelect component. I.e. we&amp;#039;re telling the form system to use a specific component to render this field and giving it some additional props to use as well.&lt;br /&gt;
&lt;br /&gt;
=== Search mode for large sets ===&lt;br /&gt;
&lt;br /&gt;
Because this is a generic content selector component, it makes some best guesses about what field to use as the display text in the dropdown. If you have a very large amount of content, a dropdown would be inappropriate and should display a searchable set instead to avoid listing thousands of things on an awkward dropdown. It has a search mode to help with this:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;title&amp;quot;)]&lt;br /&gt;
public uint PageId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This search Data attribute is a prop recognised by the Admin/ContentSelect component, so you can turn it on wherever you&amp;#039;re using that component. The attributes value is the name of the field that is used during the search - in this case, we&amp;#039;re searching pages based on if the search term matches anything in the page title.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Module(&amp;quot;Admin/ContentSelect&amp;quot;)]&lt;br /&gt;
[Data(&amp;quot;search&amp;quot;, &amp;quot;title&amp;quot;)]&lt;br /&gt;
[Data(&amp;quot;contentType&amp;quot;, &amp;quot;page&amp;quot;)]&lt;br /&gt;
public uint ParentPageId;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Field Attributes=&lt;br /&gt;
&lt;br /&gt;
SocialStack conveniences above are often all that is necessary, however, there are certainly times where it is useful to have a field display with a custom editor component, or not display at all. You can add attributes to your fields to customise the field behaviour in a variety of ways:&lt;br /&gt;
&lt;br /&gt;
* [Module(&amp;quot;Admin/ModuleName&amp;quot;)] - This attribute tells the admin panel to render your field with the given module. The module will receive everything that UI/Input would have, so that&amp;#039;s props like label, name and value.&lt;br /&gt;
&lt;br /&gt;
* [Module(false)] - This attribute tells the admin panel to hide your field with the given module.&lt;br /&gt;
&lt;br /&gt;
* [Data(&amp;quot;propName&amp;quot;, &amp;quot;propValue&amp;quot;)] - This attribute passes additional props to the admin panel rendering of your component. Works best with custom modules, but can also be used to override certain default behaviours.&lt;br /&gt;
&lt;br /&gt;
* [Localized] - Often used on names and descriptions, this attribute indicates that the field varies by locale. Works with any field type meaning it can also be used to make, for example, a price vary by locale too. When you use localized fields and there are more than one locale on your site, you&amp;#039;ll get a dropdown on the admin panel to switch between editing different locales.&lt;br /&gt;
&lt;br /&gt;
* [Meta(&amp;quot;metaFieldName&amp;quot;)] - Marks this field as the source of a given named [[Content Type Meta Fields|meta field]].&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=620</id>
		<title>Search Elastic</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=620"/>
		<updated>2024-01-09T16:22:40Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Admin */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The elastic search module works alongside the [[Search Crawler]] to extract pages from the site into an elastic search instance to enable site wide search&lt;br /&gt;
&lt;br /&gt;
The module registers itself to listen for the search crawler events and creates locale based indexes in the elastic repo for any pages it receives&lt;br /&gt;
&lt;br /&gt;
When indexing the pages, if there is a primary data type then this will also be indexed against the page in &amp;#039;&amp;#039;&amp;#039;primaryObject&amp;#039;&amp;#039;&amp;#039;, this can then be used in query values as explained below&lt;br /&gt;
&lt;br /&gt;
The indexing process is controlled by execution of the search crawler via the site automations&lt;br /&gt;
&lt;br /&gt;
The module contains a Readme.txt file which gives help for the initial setup of elastic but the creation of the indexes/content are all automated.&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&lt;br /&gt;
Social stack cloud can now deploy a standalone elastic node to Digital Ocean, but the manual steps for local deployment are detailed below &lt;br /&gt;
&lt;br /&gt;
The basic setup steps are &lt;br /&gt;
&lt;br /&gt;
#Install the following socialstack modules&lt;br /&gt;
#*Api/SearchCrawler&lt;br /&gt;
#*Api/SearchElastic&lt;br /&gt;
#*UI/SiteSearch&lt;br /&gt;
#Mark any pages not to be indexed&lt;br /&gt;
#Install Elastic Search (see &amp;#039;&amp;#039;&amp;#039;[https://github.com/socialstack-core/modules/tree/master/Api/SearchElastic Readme.txt]&amp;#039;&amp;#039;&amp;#039; in the repo for help and guidance)&lt;br /&gt;
#Configure the sizing/scale in the &amp;#039;&amp;#039;&amp;#039;elasticsearch.yml&amp;#039;&amp;#039;&amp;#039; file (mainly vm memory usage)&lt;br /&gt;
#Start Elastic Search for the first time and get the master password and certificate fingerprint&lt;br /&gt;
#Add the url, credentials and fingerprint values to the module configuration via the ss admin panel&lt;br /&gt;
#Run the automation to index the site, the indexing progress is shown via the console&lt;br /&gt;
&lt;br /&gt;
The module also contains a postman collection which can be used to quick debug/verify the indexed content&lt;br /&gt;
&lt;br /&gt;
Once indexed there is a UI/SiteSearch module which can be used as the basis of the site search&lt;br /&gt;
&lt;br /&gt;
== Social Stack Cloud Install Steps == &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install apt-transport-https&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;echo &amp;quot;deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main&amp;quot; | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install elasticsearch&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Remember to get the elastic user password from the output and update the ss configuration&lt;br /&gt;
&lt;br /&gt;
&amp;quot;The generated password for the elastic built-in superuser is : xxxxxx&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Next get the ssl certificate fingerprint value for config&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The section you need looks like this (the base64encoded values)&lt;br /&gt;
SHA256 Fingerprint=85:AA:9E:19:2F:4C:72:01:01:66:41:87:28:03:52:B7:88:61:9C:93:E8:18:D2:9C:C7:60:92:F9:E0:84:EE:AA&lt;br /&gt;
&lt;br /&gt;
Now elasticsearch is installed, before starting look at the memory and diskspace section below&lt;br /&gt;
&lt;br /&gt;
Finnaly you can now start the elasticsearch service by executing&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These commands provide no feedback as to whether Elasticsearch was started successfully or not. &lt;br /&gt;
Instead, this information will be written in the log files located in /var/log/elasticsearch/ or you can run the command below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;systemctl status elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To configure Elasticsearch to start automatically when the system boots up, run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo /bin/systemctl daemon-reload&lt;br /&gt;
&lt;br /&gt;
sudo /bin/systemctl enable elasticsearch.service&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring memory and diskspace ==&lt;br /&gt;
&lt;br /&gt;
It is recommended to setup the vm with at least 4gb ram, when running 2gb we have run into issues &lt;br /&gt;
&lt;br /&gt;
edit the config file to set diskspace limits &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/elasticsearch.yml&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cluster.routing.allocation.disk.threshold_enabled: false&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.flood_stage: 250mb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.low: 2gb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.high: 1gb&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To disable geo location, if the project is not using the geolocation extensionsit can be disabled as otherwise an additional index is created and the contents automatically downloaded &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
ingest.geoip.downloader.enabled: false&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the memory usage (default 4gb) but for dev we have set it to the minimum as below &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/jvm.options&lt;br /&gt;
&lt;br /&gt;
Uncomment and set these values &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
-Xms128m&lt;br /&gt;
&lt;br /&gt;
-Xmx128m&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To check the heap size &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo cat /var/log/elasticsearch/elasticsearch.log | grep &amp;quot;heap size&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Admin == &lt;br /&gt;
&lt;br /&gt;
linux&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl stop elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the fingerprint &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To reset the user password&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== GET /v1/sitesearch/reset ===&lt;br /&gt;
&lt;br /&gt;
This removes any indexes from elastic to allow a clean start&lt;br /&gt;
&lt;br /&gt;
=== POST /v1/sitesearch/query ===&lt;br /&gt;
&lt;br /&gt;
This is the core search endpoint which currently accepts the following values &lt;br /&gt;
&lt;br /&gt;
;queryString&lt;br /&gt;
:The query value entered by the user and/or the &amp;#039;&amp;#039;&amp;#039;UI/SiteSearch&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; components, elastic search uses the Lucene query language so you can build complex queries &lt;br /&gt;
:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html&lt;br /&gt;
:e.g.&lt;br /&gt;
:&amp;lt;code&amp;gt;Africa AND primaryObject.name:Botswana&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;primaryObject.price:(&amp;gt;=1000 AND &amp;lt;5000)&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;((quick AND fox) OR (brown AND fox) OR fox) AND NOT dog&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;tags&lt;br /&gt;
: This expects a list of comma separated tags, if any are supplied the query will only return results which contain all the passed tag values&lt;br /&gt;
&lt;br /&gt;
;contentTypes&lt;br /&gt;
: This expects a list of comma separated content types, if any are supplied the query will only return results which contain all the passed content types, these are normally the primary data object for the page&lt;br /&gt;
&lt;br /&gt;
;pageSize&lt;br /&gt;
: This expects the page size and will default to 10 if not populated&lt;br /&gt;
&lt;br /&gt;
;pageIndex&lt;br /&gt;
: This expects the starting page number and will default to 1&lt;br /&gt;
&lt;br /&gt;
;aggregationFields&lt;br /&gt;
: This expects a list of comma separated field names, if any are supplied then aggregations/facets are returned for the fields, a good example of this can be be found in &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; component&lt;br /&gt;
&lt;br /&gt;
;allFields&lt;br /&gt;
: If this is set to true then any indexed content will be searchable, this includes any &amp;#039;include&amp;#039; files and metadata from the primary object, if you want to restrict the search to the managed text fields then set this to false (the default)&lt;br /&gt;
&lt;br /&gt;
== Search taxonomy and filters/facets ==&lt;br /&gt;
You can filter or define facets(aggregations in ElasticSerach terminology) on any field exposed to Elastic from the social stack application, but they work best on metadata type fields such as tags, price etc &lt;br /&gt;
&lt;br /&gt;
A basic example would be the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; field.&lt;br /&gt;
&lt;br /&gt;
But a better solution is to setup contextual tags using the &amp;#039;&amp;#039;&amp;#039;Api/Categories&amp;#039;&amp;#039;&amp;#039; component and tags.&lt;br /&gt;
&lt;br /&gt;
This allows you to give some context to a tag, so for example you could create an &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category, then when adding a new tag for &amp;#039;&amp;#039;&amp;#039;Cow&amp;#039;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; you would set their category to be &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Any tags linked to a category can be referenced as &amp;#039;&amp;#039;&amp;#039;taxonomy.xxxxx&amp;#039;&amp;#039;&amp;#039; so in this example &amp;#039;&amp;#039;&amp;#039;taxonomy.animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
These can then be referenced to show a contextual filter in the UI, the example below will pass back an aggregation of any &amp;#039;&amp;#039;&amp;#039;animals&amp;#039;&amp;#039;&amp;#039; tags in the result set, &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; then uses these render an auto generated filter.&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		 {&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.animals&amp;quot;,&lt;br /&gt;
				&amp;quot;contentTypes&amp;quot;: &amp;quot;ItineraryPageDataType&amp;quot;&lt;br /&gt;
            }   &lt;br /&gt;
         } &lt;br /&gt;
         ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When tags are assigned to category and indexed, you will expose the core tag values and also some auto generated taxonomy fields.&lt;br /&gt;
&lt;br /&gt;
An example of the indexed data in Elastic looks something like the example below where &amp;#039;&amp;#039;&amp;#039;Africa&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;Botswana&amp;#039;&amp;#039;&amp;#039; are plain tags but &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; has been linked to the &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; value is still exposed in the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; element as that is used for free text search ranking. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;tags&amp;quot;: [&lt;br /&gt;
           &amp;quot;Africa&amp;quot;,&lt;br /&gt;
           &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
           &amp;quot;Horse&amp;quot;&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;taxonomy&amp;quot;: {&lt;br /&gt;
      &amp;quot;animals&amp;quot;: [&lt;br /&gt;
         &amp;quot;Horse&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== UI Components  ==&lt;br /&gt;
;UI/ContentSearch&lt;br /&gt;
&lt;br /&gt;
This provides a good example with filtering and paging see the amba repo &lt;br /&gt;
&lt;br /&gt;
The component will also allow you to override the default values/titles for any aggregation using the aggregationOverrides property, this can be useful to add grouping to values such as regions/countries etc&lt;br /&gt;
&lt;br /&gt;
The buckets property is optional and by default would normally be an empty array [] but can be used to override labels and the order of options&lt;br /&gt;
&lt;br /&gt;
Some basic examples of how overrides can be used to control the search filters&lt;br /&gt;
&lt;br /&gt;
=== 2 Column example with set items for destination ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.destination&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Destination&amp;quot;,&lt;br /&gt;
	&amp;quot;columns&amp;quot;: 2,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Madagascar&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Scotland&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Bonny Scotland&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Svalbard&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Swapping the default select boxes for radio buttons ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to show as a single checkbox ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isFeatured&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Only show featured events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to return both checked and unchecked values ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isCompleted&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Show completed events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;returnAllValues&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a simple select dropdown ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.country&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Location&amp;quot;,&lt;br /&gt;
	&amp;quot;all&amp;quot;: &amp;quot;Any location&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Allow multiple unrelated values to be chosen ===&lt;br /&gt;
&lt;br /&gt;
Override the default functionality which disables any original filter options if the search results no longer contain results containing the values, so a drill down work flow &lt;br /&gt;
dog -&amp;gt; spaniel -&amp;gt; english&lt;br /&gt;
&lt;br /&gt;
With &amp;#039;allowAllOptions&amp;#039; other unrelated documents can be brought into the results so for example when filtering events you could include multiple unrelated types such as &amp;quot;gala&amp;quot; OR &amp;quot;conference&amp;quot; when by default if you choose &amp;quot;gala&amp;quot;, conferences would be disabled&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.event type&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A complete example is shown below using separate components for the filters and results (2 column layout)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/SearchContext&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;comment&amp;quot;: &amp;quot;SearchContext used to link search engine, results and filters&amp;quot;&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
			    &lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/SiteContainer&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
				&amp;quot;t&amp;quot;: &amp;quot;UI/SiteRow&amp;quot;,&lt;br /&gt;
				&amp;quot;d&amp;quot;: {},&lt;br /&gt;
				&amp;quot;c&amp;quot;: [&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;sidebar&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: {&lt;br /&gt;
							&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
							&amp;quot;d&amp;quot;: {&lt;br /&gt;
								&amp;quot;title&amp;quot;: &amp;quot;Search insights&amp;quot;,&lt;br /&gt;
								&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.brand,taxonomy.audience,taxonomy.insight category,tags&amp;quot;,&lt;br /&gt;
								&amp;quot;contentTypes&amp;quot;: &amp;quot;Insight&amp;quot;,&lt;br /&gt;
								&amp;quot;resultLayout&amp;quot;: &amp;quot;list&amp;quot;,&lt;br /&gt;
								&amp;quot;itemRenderer&amp;quot;: &amp;quot;UI/InsightResultItem&amp;quot;,&lt;br /&gt;
								&amp;quot;sortOptions&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Ranking&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;_score&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;aggregationOverrides&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.brand&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View insights by brand&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Both&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any audience type&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View by insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any category&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;tags&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Topics&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any topics&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;hideResults&amp;quot;: true,&lt;br /&gt;
								&amp;quot;hideTitle&amp;quot;: true,&lt;br /&gt;
								&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
							},&lt;br /&gt;
							&amp;quot;i&amp;quot;: 4&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;i&amp;quot;: 6&lt;br /&gt;
					},&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;content&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: [&lt;br /&gt;
							{&lt;br /&gt;
								&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/ContentSearchResults&amp;quot;,&lt;br /&gt;
								&amp;quot;d&amp;quot;: {&lt;br /&gt;
									&amp;quot;hideTotal&amp;quot;: true,&lt;br /&gt;
									&amp;quot;searchType&amp;quot;: &amp;quot;insights&amp;quot;,&lt;br /&gt;
									&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
								},&lt;br /&gt;
								&amp;quot;i&amp;quot;: 14&lt;br /&gt;
							}&lt;br /&gt;
						],&lt;br /&gt;
						&amp;quot;i&amp;quot;: 11&lt;br /&gt;
					}&lt;br /&gt;
				],&lt;br /&gt;
				&amp;quot;i&amp;quot;: 12&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;i&amp;quot;: 13&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;i&amp;quot;: 3&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=619</id>
		<title>Search Elastic</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=619"/>
		<updated>2024-01-09T16:22:11Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Admin */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The elastic search module works alongside the [[Search Crawler]] to extract pages from the site into an elastic search instance to enable site wide search&lt;br /&gt;
&lt;br /&gt;
The module registers itself to listen for the search crawler events and creates locale based indexes in the elastic repo for any pages it receives&lt;br /&gt;
&lt;br /&gt;
When indexing the pages, if there is a primary data type then this will also be indexed against the page in &amp;#039;&amp;#039;&amp;#039;primaryObject&amp;#039;&amp;#039;&amp;#039;, this can then be used in query values as explained below&lt;br /&gt;
&lt;br /&gt;
The indexing process is controlled by execution of the search crawler via the site automations&lt;br /&gt;
&lt;br /&gt;
The module contains a Readme.txt file which gives help for the initial setup of elastic but the creation of the indexes/content are all automated.&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&lt;br /&gt;
Social stack cloud can now deploy a standalone elastic node to Digital Ocean, but the manual steps for local deployment are detailed below &lt;br /&gt;
&lt;br /&gt;
The basic setup steps are &lt;br /&gt;
&lt;br /&gt;
#Install the following socialstack modules&lt;br /&gt;
#*Api/SearchCrawler&lt;br /&gt;
#*Api/SearchElastic&lt;br /&gt;
#*UI/SiteSearch&lt;br /&gt;
#Mark any pages not to be indexed&lt;br /&gt;
#Install Elastic Search (see &amp;#039;&amp;#039;&amp;#039;[https://github.com/socialstack-core/modules/tree/master/Api/SearchElastic Readme.txt]&amp;#039;&amp;#039;&amp;#039; in the repo for help and guidance)&lt;br /&gt;
#Configure the sizing/scale in the &amp;#039;&amp;#039;&amp;#039;elasticsearch.yml&amp;#039;&amp;#039;&amp;#039; file (mainly vm memory usage)&lt;br /&gt;
#Start Elastic Search for the first time and get the master password and certificate fingerprint&lt;br /&gt;
#Add the url, credentials and fingerprint values to the module configuration via the ss admin panel&lt;br /&gt;
#Run the automation to index the site, the indexing progress is shown via the console&lt;br /&gt;
&lt;br /&gt;
The module also contains a postman collection which can be used to quick debug/verify the indexed content&lt;br /&gt;
&lt;br /&gt;
Once indexed there is a UI/SiteSearch module which can be used as the basis of the site search&lt;br /&gt;
&lt;br /&gt;
== Social Stack Cloud Install Steps == &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install apt-transport-https&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;echo &amp;quot;deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main&amp;quot; | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install elasticsearch&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Remember to get the elastic user password from the output and update the ss configuration&lt;br /&gt;
&lt;br /&gt;
&amp;quot;The generated password for the elastic built-in superuser is : xxxxxx&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Next get the ssl certificate fingerprint value for config&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The section you need looks like this (the base64encoded values)&lt;br /&gt;
SHA256 Fingerprint=85:AA:9E:19:2F:4C:72:01:01:66:41:87:28:03:52:B7:88:61:9C:93:E8:18:D2:9C:C7:60:92:F9:E0:84:EE:AA&lt;br /&gt;
&lt;br /&gt;
Now elasticsearch is installed, before starting look at the memory and diskspace section below&lt;br /&gt;
&lt;br /&gt;
Finnaly you can now start the elasticsearch service by executing&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These commands provide no feedback as to whether Elasticsearch was started successfully or not. &lt;br /&gt;
Instead, this information will be written in the log files located in /var/log/elasticsearch/ or you can run the command below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;systemctl status elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To configure Elasticsearch to start automatically when the system boots up, run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo /bin/systemctl daemon-reload&lt;br /&gt;
&lt;br /&gt;
sudo /bin/systemctl enable elasticsearch.service&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring memory and diskspace ==&lt;br /&gt;
&lt;br /&gt;
It is recommended to setup the vm with at least 4gb ram, when running 2gb we have run into issues &lt;br /&gt;
&lt;br /&gt;
edit the config file to set diskspace limits &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/elasticsearch.yml&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cluster.routing.allocation.disk.threshold_enabled: false&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.flood_stage: 250mb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.low: 2gb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.high: 1gb&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To disable geo location, if the project is not using the geolocation extensionsit can be disabled as otherwise an additional index is created and the contents automatically downloaded &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
ingest.geoip.downloader.enabled: false&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the memory usage (default 4gb) but for dev we have set it to the minimum as below &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/jvm.options&lt;br /&gt;
&lt;br /&gt;
Uncomment and set these values &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
-Xms128m&lt;br /&gt;
&lt;br /&gt;
-Xmx128m&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To check the heap size &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo cat /var/log/elasticsearch/elasticsearch.log | grep &amp;quot;heap size&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Admin == &lt;br /&gt;
&lt;br /&gt;
linux&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl stop elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the fingerprint &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To reset the user password&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
bin/elasticsearch-reset-password -u elastic -i&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== GET /v1/sitesearch/reset ===&lt;br /&gt;
&lt;br /&gt;
This removes any indexes from elastic to allow a clean start&lt;br /&gt;
&lt;br /&gt;
=== POST /v1/sitesearch/query ===&lt;br /&gt;
&lt;br /&gt;
This is the core search endpoint which currently accepts the following values &lt;br /&gt;
&lt;br /&gt;
;queryString&lt;br /&gt;
:The query value entered by the user and/or the &amp;#039;&amp;#039;&amp;#039;UI/SiteSearch&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; components, elastic search uses the Lucene query language so you can build complex queries &lt;br /&gt;
:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html&lt;br /&gt;
:e.g.&lt;br /&gt;
:&amp;lt;code&amp;gt;Africa AND primaryObject.name:Botswana&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;primaryObject.price:(&amp;gt;=1000 AND &amp;lt;5000)&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;((quick AND fox) OR (brown AND fox) OR fox) AND NOT dog&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;tags&lt;br /&gt;
: This expects a list of comma separated tags, if any are supplied the query will only return results which contain all the passed tag values&lt;br /&gt;
&lt;br /&gt;
;contentTypes&lt;br /&gt;
: This expects a list of comma separated content types, if any are supplied the query will only return results which contain all the passed content types, these are normally the primary data object for the page&lt;br /&gt;
&lt;br /&gt;
;pageSize&lt;br /&gt;
: This expects the page size and will default to 10 if not populated&lt;br /&gt;
&lt;br /&gt;
;pageIndex&lt;br /&gt;
: This expects the starting page number and will default to 1&lt;br /&gt;
&lt;br /&gt;
;aggregationFields&lt;br /&gt;
: This expects a list of comma separated field names, if any are supplied then aggregations/facets are returned for the fields, a good example of this can be be found in &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; component&lt;br /&gt;
&lt;br /&gt;
;allFields&lt;br /&gt;
: If this is set to true then any indexed content will be searchable, this includes any &amp;#039;include&amp;#039; files and metadata from the primary object, if you want to restrict the search to the managed text fields then set this to false (the default)&lt;br /&gt;
&lt;br /&gt;
== Search taxonomy and filters/facets ==&lt;br /&gt;
You can filter or define facets(aggregations in ElasticSerach terminology) on any field exposed to Elastic from the social stack application, but they work best on metadata type fields such as tags, price etc &lt;br /&gt;
&lt;br /&gt;
A basic example would be the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; field.&lt;br /&gt;
&lt;br /&gt;
But a better solution is to setup contextual tags using the &amp;#039;&amp;#039;&amp;#039;Api/Categories&amp;#039;&amp;#039;&amp;#039; component and tags.&lt;br /&gt;
&lt;br /&gt;
This allows you to give some context to a tag, so for example you could create an &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category, then when adding a new tag for &amp;#039;&amp;#039;&amp;#039;Cow&amp;#039;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; you would set their category to be &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Any tags linked to a category can be referenced as &amp;#039;&amp;#039;&amp;#039;taxonomy.xxxxx&amp;#039;&amp;#039;&amp;#039; so in this example &amp;#039;&amp;#039;&amp;#039;taxonomy.animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
These can then be referenced to show a contextual filter in the UI, the example below will pass back an aggregation of any &amp;#039;&amp;#039;&amp;#039;animals&amp;#039;&amp;#039;&amp;#039; tags in the result set, &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; then uses these render an auto generated filter.&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		 {&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.animals&amp;quot;,&lt;br /&gt;
				&amp;quot;contentTypes&amp;quot;: &amp;quot;ItineraryPageDataType&amp;quot;&lt;br /&gt;
            }   &lt;br /&gt;
         } &lt;br /&gt;
         ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When tags are assigned to category and indexed, you will expose the core tag values and also some auto generated taxonomy fields.&lt;br /&gt;
&lt;br /&gt;
An example of the indexed data in Elastic looks something like the example below where &amp;#039;&amp;#039;&amp;#039;Africa&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;Botswana&amp;#039;&amp;#039;&amp;#039; are plain tags but &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; has been linked to the &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; value is still exposed in the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; element as that is used for free text search ranking. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;tags&amp;quot;: [&lt;br /&gt;
           &amp;quot;Africa&amp;quot;,&lt;br /&gt;
           &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
           &amp;quot;Horse&amp;quot;&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;taxonomy&amp;quot;: {&lt;br /&gt;
      &amp;quot;animals&amp;quot;: [&lt;br /&gt;
         &amp;quot;Horse&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== UI Components  ==&lt;br /&gt;
;UI/ContentSearch&lt;br /&gt;
&lt;br /&gt;
This provides a good example with filtering and paging see the amba repo &lt;br /&gt;
&lt;br /&gt;
The component will also allow you to override the default values/titles for any aggregation using the aggregationOverrides property, this can be useful to add grouping to values such as regions/countries etc&lt;br /&gt;
&lt;br /&gt;
The buckets property is optional and by default would normally be an empty array [] but can be used to override labels and the order of options&lt;br /&gt;
&lt;br /&gt;
Some basic examples of how overrides can be used to control the search filters&lt;br /&gt;
&lt;br /&gt;
=== 2 Column example with set items for destination ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.destination&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Destination&amp;quot;,&lt;br /&gt;
	&amp;quot;columns&amp;quot;: 2,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Madagascar&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Scotland&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Bonny Scotland&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Svalbard&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Swapping the default select boxes for radio buttons ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to show as a single checkbox ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isFeatured&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Only show featured events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to return both checked and unchecked values ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isCompleted&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Show completed events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;returnAllValues&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a simple select dropdown ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.country&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Location&amp;quot;,&lt;br /&gt;
	&amp;quot;all&amp;quot;: &amp;quot;Any location&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Allow multiple unrelated values to be chosen ===&lt;br /&gt;
&lt;br /&gt;
Override the default functionality which disables any original filter options if the search results no longer contain results containing the values, so a drill down work flow &lt;br /&gt;
dog -&amp;gt; spaniel -&amp;gt; english&lt;br /&gt;
&lt;br /&gt;
With &amp;#039;allowAllOptions&amp;#039; other unrelated documents can be brought into the results so for example when filtering events you could include multiple unrelated types such as &amp;quot;gala&amp;quot; OR &amp;quot;conference&amp;quot; when by default if you choose &amp;quot;gala&amp;quot;, conferences would be disabled&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.event type&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A complete example is shown below using separate components for the filters and results (2 column layout)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/SearchContext&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;comment&amp;quot;: &amp;quot;SearchContext used to link search engine, results and filters&amp;quot;&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
			    &lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/SiteContainer&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
				&amp;quot;t&amp;quot;: &amp;quot;UI/SiteRow&amp;quot;,&lt;br /&gt;
				&amp;quot;d&amp;quot;: {},&lt;br /&gt;
				&amp;quot;c&amp;quot;: [&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;sidebar&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: {&lt;br /&gt;
							&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
							&amp;quot;d&amp;quot;: {&lt;br /&gt;
								&amp;quot;title&amp;quot;: &amp;quot;Search insights&amp;quot;,&lt;br /&gt;
								&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.brand,taxonomy.audience,taxonomy.insight category,tags&amp;quot;,&lt;br /&gt;
								&amp;quot;contentTypes&amp;quot;: &amp;quot;Insight&amp;quot;,&lt;br /&gt;
								&amp;quot;resultLayout&amp;quot;: &amp;quot;list&amp;quot;,&lt;br /&gt;
								&amp;quot;itemRenderer&amp;quot;: &amp;quot;UI/InsightResultItem&amp;quot;,&lt;br /&gt;
								&amp;quot;sortOptions&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Ranking&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;_score&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;aggregationOverrides&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.brand&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View insights by brand&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Both&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any audience type&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View by insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any category&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;tags&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Topics&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any topics&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;hideResults&amp;quot;: true,&lt;br /&gt;
								&amp;quot;hideTitle&amp;quot;: true,&lt;br /&gt;
								&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
							},&lt;br /&gt;
							&amp;quot;i&amp;quot;: 4&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;i&amp;quot;: 6&lt;br /&gt;
					},&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;content&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: [&lt;br /&gt;
							{&lt;br /&gt;
								&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/ContentSearchResults&amp;quot;,&lt;br /&gt;
								&amp;quot;d&amp;quot;: {&lt;br /&gt;
									&amp;quot;hideTotal&amp;quot;: true,&lt;br /&gt;
									&amp;quot;searchType&amp;quot;: &amp;quot;insights&amp;quot;,&lt;br /&gt;
									&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
								},&lt;br /&gt;
								&amp;quot;i&amp;quot;: 14&lt;br /&gt;
							}&lt;br /&gt;
						],&lt;br /&gt;
						&amp;quot;i&amp;quot;: 11&lt;br /&gt;
					}&lt;br /&gt;
				],&lt;br /&gt;
				&amp;quot;i&amp;quot;: 12&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;i&amp;quot;: 13&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;i&amp;quot;: 3&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=618</id>
		<title>Search Elastic</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=618"/>
		<updated>2024-01-09T16:06:37Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Installation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The elastic search module works alongside the [[Search Crawler]] to extract pages from the site into an elastic search instance to enable site wide search&lt;br /&gt;
&lt;br /&gt;
The module registers itself to listen for the search crawler events and creates locale based indexes in the elastic repo for any pages it receives&lt;br /&gt;
&lt;br /&gt;
When indexing the pages, if there is a primary data type then this will also be indexed against the page in &amp;#039;&amp;#039;&amp;#039;primaryObject&amp;#039;&amp;#039;&amp;#039;, this can then be used in query values as explained below&lt;br /&gt;
&lt;br /&gt;
The indexing process is controlled by execution of the search crawler via the site automations&lt;br /&gt;
&lt;br /&gt;
The module contains a Readme.txt file which gives help for the initial setup of elastic but the creation of the indexes/content are all automated.&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&lt;br /&gt;
Social stack cloud can now deploy a standalone elastic node to Digital Ocean, but the manual steps for local deployment are detailed below &lt;br /&gt;
&lt;br /&gt;
The basic setup steps are &lt;br /&gt;
&lt;br /&gt;
#Install the following socialstack modules&lt;br /&gt;
#*Api/SearchCrawler&lt;br /&gt;
#*Api/SearchElastic&lt;br /&gt;
#*UI/SiteSearch&lt;br /&gt;
#Mark any pages not to be indexed&lt;br /&gt;
#Install Elastic Search (see &amp;#039;&amp;#039;&amp;#039;[https://github.com/socialstack-core/modules/tree/master/Api/SearchElastic Readme.txt]&amp;#039;&amp;#039;&amp;#039; in the repo for help and guidance)&lt;br /&gt;
#Configure the sizing/scale in the &amp;#039;&amp;#039;&amp;#039;elasticsearch.yml&amp;#039;&amp;#039;&amp;#039; file (mainly vm memory usage)&lt;br /&gt;
#Start Elastic Search for the first time and get the master password and certificate fingerprint&lt;br /&gt;
#Add the url, credentials and fingerprint values to the module configuration via the ss admin panel&lt;br /&gt;
#Run the automation to index the site, the indexing progress is shown via the console&lt;br /&gt;
&lt;br /&gt;
The module also contains a postman collection which can be used to quick debug/verify the indexed content&lt;br /&gt;
&lt;br /&gt;
Once indexed there is a UI/SiteSearch module which can be used as the basis of the site search&lt;br /&gt;
&lt;br /&gt;
== Social Stack Cloud Install Steps == &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install apt-transport-https&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;echo &amp;quot;deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main&amp;quot; | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install elasticsearch&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Remember to get the elastic user password from the output and update the ss configuration&lt;br /&gt;
&lt;br /&gt;
&amp;quot;The generated password for the elastic built-in superuser is : xxxxxx&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Next get the ssl certificate fingerprint value for config&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The section you need looks like this (the base64encoded values)&lt;br /&gt;
SHA256 Fingerprint=85:AA:9E:19:2F:4C:72:01:01:66:41:87:28:03:52:B7:88:61:9C:93:E8:18:D2:9C:C7:60:92:F9:E0:84:EE:AA&lt;br /&gt;
&lt;br /&gt;
Now elasticsearch is installed, before starting look at the memory and diskspace section below&lt;br /&gt;
&lt;br /&gt;
Finnaly you can now start the elasticsearch service by executing&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These commands provide no feedback as to whether Elasticsearch was started successfully or not. &lt;br /&gt;
Instead, this information will be written in the log files located in /var/log/elasticsearch/ or you can run the command below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;systemctl status elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To configure Elasticsearch to start automatically when the system boots up, run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo /bin/systemctl daemon-reload&lt;br /&gt;
&lt;br /&gt;
sudo /bin/systemctl enable elasticsearch.service&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring memory and diskspace ==&lt;br /&gt;
&lt;br /&gt;
It is recommended to setup the vm with at least 4gb ram, when running 2gb we have run into issues &lt;br /&gt;
&lt;br /&gt;
edit the config file to set diskspace limits &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/elasticsearch.yml&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cluster.routing.allocation.disk.threshold_enabled: false&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.flood_stage: 250mb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.low: 2gb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.high: 1gb&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To disable geo location, if the project is not using the geolocation extensionsit can be disabled as otherwise an additional index is created and the contents automatically downloaded &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
ingest.geoip.downloader.enabled: false&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the memory usage (default 4gb) but for dev we have set it to the minimum as below &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/jvm.options&lt;br /&gt;
&lt;br /&gt;
Uncomment and set these values &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
-Xms128m&lt;br /&gt;
&lt;br /&gt;
-Xmx128m&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To check the heap size &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo cat /var/log/elasticsearch/elasticsearch.log | grep &amp;quot;heap size&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Admin == &lt;br /&gt;
&lt;br /&gt;
linux&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl stop elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the fingerprint &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;which &lt;br /&gt;
&lt;br /&gt;
To reset the user password&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
bin/elasticsearch-reset-password -u elastic -i&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== GET /v1/sitesearch/reset ===&lt;br /&gt;
&lt;br /&gt;
This removes any indexes from elastic to allow a clean start&lt;br /&gt;
&lt;br /&gt;
=== POST /v1/sitesearch/query ===&lt;br /&gt;
&lt;br /&gt;
This is the core search endpoint which currently accepts the following values &lt;br /&gt;
&lt;br /&gt;
;queryString&lt;br /&gt;
:The query value entered by the user and/or the &amp;#039;&amp;#039;&amp;#039;UI/SiteSearch&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; components, elastic search uses the Lucene query language so you can build complex queries &lt;br /&gt;
:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html&lt;br /&gt;
:e.g.&lt;br /&gt;
:&amp;lt;code&amp;gt;Africa AND primaryObject.name:Botswana&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;primaryObject.price:(&amp;gt;=1000 AND &amp;lt;5000)&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;((quick AND fox) OR (brown AND fox) OR fox) AND NOT dog&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;tags&lt;br /&gt;
: This expects a list of comma separated tags, if any are supplied the query will only return results which contain all the passed tag values&lt;br /&gt;
&lt;br /&gt;
;contentTypes&lt;br /&gt;
: This expects a list of comma separated content types, if any are supplied the query will only return results which contain all the passed content types, these are normally the primary data object for the page&lt;br /&gt;
&lt;br /&gt;
;pageSize&lt;br /&gt;
: This expects the page size and will default to 10 if not populated&lt;br /&gt;
&lt;br /&gt;
;pageIndex&lt;br /&gt;
: This expects the starting page number and will default to 1&lt;br /&gt;
&lt;br /&gt;
;aggregationFields&lt;br /&gt;
: This expects a list of comma separated field names, if any are supplied then aggregations/facets are returned for the fields, a good example of this can be be found in &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; component&lt;br /&gt;
&lt;br /&gt;
;allFields&lt;br /&gt;
: If this is set to true then any indexed content will be searchable, this includes any &amp;#039;include&amp;#039; files and metadata from the primary object, if you want to restrict the search to the managed text fields then set this to false (the default)&lt;br /&gt;
&lt;br /&gt;
== Search taxonomy and filters/facets ==&lt;br /&gt;
You can filter or define facets(aggregations in ElasticSerach terminology) on any field exposed to Elastic from the social stack application, but they work best on metadata type fields such as tags, price etc &lt;br /&gt;
&lt;br /&gt;
A basic example would be the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; field.&lt;br /&gt;
&lt;br /&gt;
But a better solution is to setup contextual tags using the &amp;#039;&amp;#039;&amp;#039;Api/Categories&amp;#039;&amp;#039;&amp;#039; component and tags.&lt;br /&gt;
&lt;br /&gt;
This allows you to give some context to a tag, so for example you could create an &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category, then when adding a new tag for &amp;#039;&amp;#039;&amp;#039;Cow&amp;#039;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; you would set their category to be &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Any tags linked to a category can be referenced as &amp;#039;&amp;#039;&amp;#039;taxonomy.xxxxx&amp;#039;&amp;#039;&amp;#039; so in this example &amp;#039;&amp;#039;&amp;#039;taxonomy.animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
These can then be referenced to show a contextual filter in the UI, the example below will pass back an aggregation of any &amp;#039;&amp;#039;&amp;#039;animals&amp;#039;&amp;#039;&amp;#039; tags in the result set, &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; then uses these render an auto generated filter.&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		 {&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.animals&amp;quot;,&lt;br /&gt;
				&amp;quot;contentTypes&amp;quot;: &amp;quot;ItineraryPageDataType&amp;quot;&lt;br /&gt;
            }   &lt;br /&gt;
         } &lt;br /&gt;
         ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When tags are assigned to category and indexed, you will expose the core tag values and also some auto generated taxonomy fields.&lt;br /&gt;
&lt;br /&gt;
An example of the indexed data in Elastic looks something like the example below where &amp;#039;&amp;#039;&amp;#039;Africa&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;Botswana&amp;#039;&amp;#039;&amp;#039; are plain tags but &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; has been linked to the &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; value is still exposed in the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; element as that is used for free text search ranking. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;tags&amp;quot;: [&lt;br /&gt;
           &amp;quot;Africa&amp;quot;,&lt;br /&gt;
           &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
           &amp;quot;Horse&amp;quot;&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;taxonomy&amp;quot;: {&lt;br /&gt;
      &amp;quot;animals&amp;quot;: [&lt;br /&gt;
         &amp;quot;Horse&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== UI Components  ==&lt;br /&gt;
;UI/ContentSearch&lt;br /&gt;
&lt;br /&gt;
This provides a good example with filtering and paging see the amba repo &lt;br /&gt;
&lt;br /&gt;
The component will also allow you to override the default values/titles for any aggregation using the aggregationOverrides property, this can be useful to add grouping to values such as regions/countries etc&lt;br /&gt;
&lt;br /&gt;
The buckets property is optional and by default would normally be an empty array [] but can be used to override labels and the order of options&lt;br /&gt;
&lt;br /&gt;
Some basic examples of how overrides can be used to control the search filters&lt;br /&gt;
&lt;br /&gt;
=== 2 Column example with set items for destination ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.destination&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Destination&amp;quot;,&lt;br /&gt;
	&amp;quot;columns&amp;quot;: 2,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Madagascar&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Scotland&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Bonny Scotland&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Svalbard&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Swapping the default select boxes for radio buttons ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to show as a single checkbox ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isFeatured&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Only show featured events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to return both checked and unchecked values ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isCompleted&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Show completed events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;returnAllValues&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a simple select dropdown ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.country&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Location&amp;quot;,&lt;br /&gt;
	&amp;quot;all&amp;quot;: &amp;quot;Any location&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Allow multiple unrelated values to be chosen ===&lt;br /&gt;
&lt;br /&gt;
Override the default functionality which disables any original filter options if the search results no longer contain results containing the values, so a drill down work flow &lt;br /&gt;
dog -&amp;gt; spaniel -&amp;gt; english&lt;br /&gt;
&lt;br /&gt;
With &amp;#039;allowAllOptions&amp;#039; other unrelated documents can be brought into the results so for example when filtering events you could include multiple unrelated types such as &amp;quot;gala&amp;quot; OR &amp;quot;conference&amp;quot; when by default if you choose &amp;quot;gala&amp;quot;, conferences would be disabled&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.event type&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A complete example is shown below using separate components for the filters and results (2 column layout)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/SearchContext&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;comment&amp;quot;: &amp;quot;SearchContext used to link search engine, results and filters&amp;quot;&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
			    &lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/SiteContainer&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
				&amp;quot;t&amp;quot;: &amp;quot;UI/SiteRow&amp;quot;,&lt;br /&gt;
				&amp;quot;d&amp;quot;: {},&lt;br /&gt;
				&amp;quot;c&amp;quot;: [&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;sidebar&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: {&lt;br /&gt;
							&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
							&amp;quot;d&amp;quot;: {&lt;br /&gt;
								&amp;quot;title&amp;quot;: &amp;quot;Search insights&amp;quot;,&lt;br /&gt;
								&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.brand,taxonomy.audience,taxonomy.insight category,tags&amp;quot;,&lt;br /&gt;
								&amp;quot;contentTypes&amp;quot;: &amp;quot;Insight&amp;quot;,&lt;br /&gt;
								&amp;quot;resultLayout&amp;quot;: &amp;quot;list&amp;quot;,&lt;br /&gt;
								&amp;quot;itemRenderer&amp;quot;: &amp;quot;UI/InsightResultItem&amp;quot;,&lt;br /&gt;
								&amp;quot;sortOptions&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Ranking&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;_score&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;aggregationOverrides&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.brand&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View insights by brand&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Both&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any audience type&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View by insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any category&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;tags&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Topics&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any topics&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;hideResults&amp;quot;: true,&lt;br /&gt;
								&amp;quot;hideTitle&amp;quot;: true,&lt;br /&gt;
								&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
							},&lt;br /&gt;
							&amp;quot;i&amp;quot;: 4&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;i&amp;quot;: 6&lt;br /&gt;
					},&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;content&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: [&lt;br /&gt;
							{&lt;br /&gt;
								&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/ContentSearchResults&amp;quot;,&lt;br /&gt;
								&amp;quot;d&amp;quot;: {&lt;br /&gt;
									&amp;quot;hideTotal&amp;quot;: true,&lt;br /&gt;
									&amp;quot;searchType&amp;quot;: &amp;quot;insights&amp;quot;,&lt;br /&gt;
									&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
								},&lt;br /&gt;
								&amp;quot;i&amp;quot;: 14&lt;br /&gt;
							}&lt;br /&gt;
						],&lt;br /&gt;
						&amp;quot;i&amp;quot;: 11&lt;br /&gt;
					}&lt;br /&gt;
				],&lt;br /&gt;
				&amp;quot;i&amp;quot;: 12&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;i&amp;quot;: 13&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;i&amp;quot;: 3&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Content_Type_Meta_Fields&amp;diff=617</id>
		<title>Content Type Meta Fields</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Content_Type_Meta_Fields&amp;diff=617"/>
		<updated>2024-01-03T14:00:28Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Page Keyword Metadata */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Content types vary quite a lot, but it&amp;#039;s often useful to get a general use name, description or other field. For example, the &amp;quot;Title&amp;quot; of a Page makes sense as its title - as does the &amp;quot;Username&amp;quot; of a User. By default, Socialstack will guess at an appropriate field for use as a meta title field, and similarly for a description field, based on if a content type has any of a series of common field names.&lt;br /&gt;
&lt;br /&gt;
= Getting a meta value =&lt;br /&gt;
&lt;br /&gt;
Use the GetMetaFieldValue method on an autoService to read a named meta field. This is context aware, so it&amp;#039;s possible to return different values based on the role if necessary:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
// Get a service:&lt;br /&gt;
var service = Services.GetByContentType(anUnknownContentObject.Type);&lt;br /&gt;
&lt;br /&gt;
// Read the title:&lt;br /&gt;
var title = await service.GetMetaFieldValue(context, &amp;quot;title&amp;quot;, anUnknownContentObject);&lt;br /&gt;
&lt;br /&gt;
// If anUnknownContentObject was a page, the returned title would be the same as page.Title&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Automatic field values =&lt;br /&gt;
&lt;br /&gt;
If you have a field with any of the following names, it will be used as the meta field of the given name. It will favour fields closer to the start of the list if you have more than one. Case doesn&amp;#039;t matter.&lt;br /&gt;
&lt;br /&gt;
* title: &amp;quot;fullname&amp;quot;, &amp;quot;username&amp;quot;, &amp;quot;firstname&amp;quot;, &amp;quot;title&amp;quot;, &amp;quot;name&amp;quot;, &amp;quot;url&amp;quot;&lt;br /&gt;
* description: &amp;quot;description&amp;quot;, &amp;quot;shortdescription&amp;quot;, &amp;quot;bio&amp;quot;, &amp;quot;biography&amp;quot;, &amp;quot;about&amp;quot; &lt;br /&gt;
&lt;br /&gt;
= Specifying which fields to use =&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;re making a content type and you want a particular field to be the field used when the meta title is requested and it isn&amp;#039;t in the automatic list above, mark the field with the Meta attribute like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Meta(&amp;quot;title&amp;quot;)]&lt;br /&gt;
public string FancyTitle;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Open Graph =&lt;br /&gt;
&lt;br /&gt;
A common use case of metadata is the meta tags on a page, with some well defined ones used by search engines. The Api/OpenGraphTags module adds, for example, og:title to a page where the value originates from this content type meta API.&lt;br /&gt;
&lt;br /&gt;
= Keyword Metadata = &lt;br /&gt;
&lt;br /&gt;
The Api/PageMetadata module can also be added and will expose any page and &amp;#039;primary object&amp;#039; tags into the page header keywords metadata&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Content_Type_Meta_Fields&amp;diff=616</id>
		<title>Content Type Meta Fields</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Content_Type_Meta_Fields&amp;diff=616"/>
		<updated>2024-01-03T13:58:44Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Content types vary quite a lot, but it&amp;#039;s often useful to get a general use name, description or other field. For example, the &amp;quot;Title&amp;quot; of a Page makes sense as its title - as does the &amp;quot;Username&amp;quot; of a User. By default, Socialstack will guess at an appropriate field for use as a meta title field, and similarly for a description field, based on if a content type has any of a series of common field names.&lt;br /&gt;
&lt;br /&gt;
= Getting a meta value =&lt;br /&gt;
&lt;br /&gt;
Use the GetMetaFieldValue method on an autoService to read a named meta field. This is context aware, so it&amp;#039;s possible to return different values based on the role if necessary:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
// Get a service:&lt;br /&gt;
var service = Services.GetByContentType(anUnknownContentObject.Type);&lt;br /&gt;
&lt;br /&gt;
// Read the title:&lt;br /&gt;
var title = await service.GetMetaFieldValue(context, &amp;quot;title&amp;quot;, anUnknownContentObject);&lt;br /&gt;
&lt;br /&gt;
// If anUnknownContentObject was a page, the returned title would be the same as page.Title&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Automatic field values =&lt;br /&gt;
&lt;br /&gt;
If you have a field with any of the following names, it will be used as the meta field of the given name. It will favour fields closer to the start of the list if you have more than one. Case doesn&amp;#039;t matter.&lt;br /&gt;
&lt;br /&gt;
* title: &amp;quot;fullname&amp;quot;, &amp;quot;username&amp;quot;, &amp;quot;firstname&amp;quot;, &amp;quot;title&amp;quot;, &amp;quot;name&amp;quot;, &amp;quot;url&amp;quot;&lt;br /&gt;
* description: &amp;quot;description&amp;quot;, &amp;quot;shortdescription&amp;quot;, &amp;quot;bio&amp;quot;, &amp;quot;biography&amp;quot;, &amp;quot;about&amp;quot; &lt;br /&gt;
&lt;br /&gt;
= Specifying which fields to use =&lt;br /&gt;
&lt;br /&gt;
If you&amp;#039;re making a content type and you want a particular field to be the field used when the meta title is requested and it isn&amp;#039;t in the automatic list above, mark the field with the Meta attribute like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
[Meta(&amp;quot;title&amp;quot;)]&lt;br /&gt;
public string FancyTitle;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Open Graph =&lt;br /&gt;
&lt;br /&gt;
A common use case of metadata is the meta tags on a page, with some well defined ones used by search engines. The Api/OpenGraphTags module adds, for example, og:title to a page where the value originates from this content type meta API.&lt;br /&gt;
&lt;br /&gt;
= Page Keyword Metadata = &lt;br /&gt;
&lt;br /&gt;
The Api/PageMetadata module can also be added and will expose any page and &amp;#039;primary object&amp;#039; tags into the page header keywords metadata&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Dynamic_Content_Types&amp;diff=615</id>
		<title>Dynamic Content Types</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Dynamic_Content_Types&amp;diff=615"/>
		<updated>2023-12-20T17:01:29Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Working with one from code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In general it&amp;#039;s recommended to bake [[Entities|content types]] in to your codebase because it makes it much simpler for incoming developers to see what is available and use things like the events system with autocomplete to quickly link up what they need. However there is also some occasions where it would be useful to create a basic one via the admin panel at runtime. These are dynamic content types, and they are available via the Api/CustomContentTypes module:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
socialstack i Api/CustomContentTypes&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= How it works =&lt;br /&gt;
&lt;br /&gt;
Content types created via this module are first class citizens: they have the exact same performance characteristics as ones built in to the codebase. This is because the module generates a controller, service and a C# class to represent your content type at runtime. It then runs it through the regular service startup flow which allows things like the permission system and database engine to mount the content type automatically and in the same way it would if it was built in.&lt;br /&gt;
&lt;br /&gt;
= Hybrid types =&lt;br /&gt;
&lt;br /&gt;
If you create a dynamic content type which uses the same name as any of your native types - common ones being Page or User for example - then you will actually create a hybrid type. These have all the developer advantages of being a native type whilst still being extensible from the admin panel. Naturally you just can&amp;#039;t edit or remove the fields which originate from the underlying C#  type without editing the source.&lt;br /&gt;
&lt;br /&gt;
= Working with one from code =&lt;br /&gt;
&lt;br /&gt;
If you later need to add events to a dynamic content type, you have 2 options: either generate the source of the module so it becomes built in to the code, or alternatively, you can use Services.Get to find the service:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Get a weak reference to a service for a custom content type called &amp;quot;GalleryItem&amp;quot;:&lt;br /&gt;
var galleryItemService = Services.Get(&amp;quot;GalleryItemService&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// Get the event group, which is what would be the equiv of Events.GalleryItem if it existed:&lt;br /&gt;
var eventGroup = galleryItemService.GetEventGroup();&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These weak references are not particularly ideal in many scenario&amp;#039;s though, particularly if you did want to add an event handler to it. This is because event handlers like to know which types you&amp;#039;re working with so you can get good autocomplete and code correctness. At the least, consider a hybrid type instead. Here though we are working with &amp;quot;some runtime generated content type&amp;quot; with unknown fields, but it is not completely unknowable. We can use generic methods to be able to add events in a generic way. Note that this works for every content type, including the built in ones, and is how e.g. the permission system mounts every type in a generic way.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Get the service:&lt;br /&gt;
var service = Services.Get(&amp;quot;GalleryItemService&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// MethodToRun is a generic method on this class which will basically accept any AutoService.&lt;br /&gt;
var methodToRun = GetType().GetMethod(nameof(MethodToRun));&lt;br /&gt;
&lt;br /&gt;
// Create a specific flavour of that method:&lt;br /&gt;
var genericMethodToRun = methodToRun.MakeGenericMethod(new Type[] {&lt;br /&gt;
    service.ServicedType,&lt;br /&gt;
    service.IdType&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// And invoke it:&lt;br /&gt;
genericMethodToRun.Invoke(this, new object[] {&lt;br /&gt;
    service&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// .. some time later ..&lt;br /&gt;
// The method itself:&lt;br /&gt;
public void MethodToRun&amp;lt;T, ID&amp;gt;(AutoService&amp;lt;T, ID&amp;gt; service)&lt;br /&gt;
    where T : Content&amp;lt;ID&amp;gt;, new()&lt;br /&gt;
    where ID : struct, IConvertible, IEquatable&amp;lt;ID&amp;gt;, IComparable&amp;lt;ID&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
    /*&lt;br /&gt;
    service.EventGroup.AfterCreate.AddEventListener((Context context, T theThing) =&amp;gt; {&lt;br /&gt;
       // Adding an AfterCreate event handler to a totally generic content type&lt;br /&gt;
       // because of the where T : .. rule above, C# knows it is at least some sort of content type with Id etc on it.&lt;br /&gt;
       return new ValueTask&amp;lt;T&amp;gt;(theThing);&lt;br /&gt;
    });&lt;br /&gt;
    */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can be more specific with your where T: .. rule if you know the type you&amp;#039;re working with is something more specialised such as UserCreatedContent&amp;lt;ID&amp;gt;. This gives you extra fields such as the UserId and created/ edited times.&lt;br /&gt;
&lt;br /&gt;
Here&amp;#039;s an example of triggering an acknowledgement email when a dynamic form is populated&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;csharp&amp;quot;&amp;gt;&lt;br /&gt;
using System.Threading.Tasks;&lt;br /&gt;
using Api.Contexts;&lt;br /&gt;
using Api.Startup;&lt;br /&gt;
using Api.Emails;&lt;br /&gt;
using Api.Database;&lt;br /&gt;
using System;&lt;br /&gt;
using Api.Eventing;&lt;br /&gt;
using System.Collections.Generic;&lt;br /&gt;
&lt;br /&gt;
namespace Api.EventEmail&lt;br /&gt;
{&lt;br /&gt;
    [LoadPriority(101)]&lt;br /&gt;
    public partial class AutoFormEmailService : AutoService&lt;br /&gt;
    {&lt;br /&gt;
        private readonly EmailTemplateService _emails;&lt;br /&gt;
&lt;br /&gt;
        /// &amp;lt;summary&amp;gt;&lt;br /&gt;
        /// Service to locate booking forms for events and automatically send an acknowledgement email&lt;br /&gt;
        /// &amp;lt;/summary&amp;gt;&lt;br /&gt;
        public AutoFormEmailService(EmailTemplateService emails)&lt;br /&gt;
        {&lt;br /&gt;
            _emails = emails;&lt;br /&gt;
&lt;br /&gt;
            InstallEmails(&lt;br /&gt;
                new EmailTemplate()&lt;br /&gt;
                {&lt;br /&gt;
                    Name = &amp;quot;Event confirmation&amp;quot;,&lt;br /&gt;
                    Subject = &amp;quot;Event booking confirmation&amp;quot;,&lt;br /&gt;
                    Key = &amp;quot;event_confirmation&amp;quot;,&lt;br /&gt;
                    BodyJson = &amp;quot;{\&amp;quot;c\&amp;quot;: {\&amp;quot;t\&amp;quot;: \&amp;quot;Email/Default\&amp;quot;,\&amp;quot;d\&amp;quot;: {},\&amp;quot;r\&amp;quot;: {\&amp;quot;children\&amp;quot;: {\&amp;quot;t\&amp;quot;: \&amp;quot;Email/Centered\&amp;quot;,\&amp;quot;d\&amp;quot;: {},\&amp;quot;c\&amp;quot;: [{\&amp;quot;t\&amp;quot;: \&amp;quot;p\&amp;quot;,\&amp;quot;c\&amp;quot;: \&amp;quot;Thank you! Your event booking request has been receieved for\&amp;quot;,\&amp;quot;d\&amp;quot;: {},\&amp;quot;i\&amp;quot;: 2},{\&amp;quot;t\&amp;quot;: \&amp;quot;UI/Token\&amp;quot;,\&amp;quot;d\&amp;quot;: {\&amp;quot;mode\&amp;quot;: \&amp;quot;customdata\&amp;quot;,\&amp;quot;fields\&amp;quot;: [\&amp;quot;event\&amp;quot;]},\&amp;quot;c\&amp;quot;: {\&amp;quot;t\&amp;quot;: \&amp;quot;p\&amp;quot;,\&amp;quot;c\&amp;quot;: \&amp;quot;${customdata.event}\&amp;quot;,\&amp;quot;d\&amp;quot;: {},\&amp;quot;i\&amp;quot;: 6},\&amp;quot;i\&amp;quot;: 4}],\&amp;quot;i\&amp;quot;: 5},\&amp;quot;customLogo\&amp;quot;: {\&amp;quot;t\&amp;quot;: \&amp;quot;p\&amp;quot;,\&amp;quot;c\&amp;quot;: {\&amp;quot;t\&amp;quot;: \&amp;quot;br\&amp;quot;},\&amp;quot;d\&amp;quot;: {},\&amp;quot;i\&amp;quot;: 6}},\&amp;quot;i\&amp;quot;: 7},\&amp;quot;i\&amp;quot;: 8}&amp;quot;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
            // subscribe to events triggered by content types so we can locate dynamic forms &lt;br /&gt;
            Events.Service.AfterCreate.AddEventListener((Context ctx, AutoService service) =&amp;gt;&lt;br /&gt;
            {&lt;br /&gt;
                if (service == null)&lt;br /&gt;
                {&lt;br /&gt;
                    return new ValueTask&amp;lt;AutoService&amp;gt;(service);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // Get the content type for this service and event group:&lt;br /&gt;
                var servicedType = service.ServicedType;&lt;br /&gt;
                if (servicedType == null)&lt;br /&gt;
                {&lt;br /&gt;
                    // Things like the ffmpeg service.&lt;br /&gt;
                    return new ValueTask&amp;lt;AutoService&amp;gt;(service);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // If it&amp;#039;s a mapping type, ignore&lt;br /&gt;
                if (ContentTypes.IsAssignableToGenericType(servicedType, typeof(Mapping&amp;lt;,&amp;gt;)))&lt;br /&gt;
                {&lt;br /&gt;
                    return new ValueTask&amp;lt;AutoService&amp;gt;(service);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (!servicedType.Name.Contains(&amp;quot;Form&amp;quot;))&lt;br /&gt;
                {&lt;br /&gt;
                    // only interested in booking forms&lt;br /&gt;
                    return new ValueTask&amp;lt;AutoService&amp;gt;(service);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // setup the generic method on this class which will basically accept any AutoService.&lt;br /&gt;
                var setupEmail = GetType().GetMethod(nameof(SetupEmail));&lt;br /&gt;
&lt;br /&gt;
                // Create a specific flavour of that method:&lt;br /&gt;
                var setupEmailToRun = setupEmail.MakeGenericMethod(new Type[] {&lt;br /&gt;
                    service.ServicedType,&lt;br /&gt;
                    service.IdType&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                // And invoke it:&lt;br /&gt;
                setupEmailToRun.Invoke(this, new object[] { service });&lt;br /&gt;
&lt;br /&gt;
                return new ValueTask&amp;lt;AutoService&amp;gt;(service);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /// &amp;lt;summary&amp;gt;&lt;br /&gt;
        /// Generic method to check the &amp;#039;service&amp;#039; fields and if ok adds the create handler&lt;br /&gt;
        /// &amp;lt;/summary&amp;gt;&lt;br /&gt;
        /// &amp;lt;typeparam name=&amp;quot;T&amp;quot;&amp;gt;&amp;lt;/typeparam&amp;gt;&lt;br /&gt;
        /// &amp;lt;typeparam name=&amp;quot;ID&amp;quot;&amp;gt;&amp;lt;/typeparam&amp;gt;&lt;br /&gt;
        /// &amp;lt;param name=&amp;quot;service&amp;quot;&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;
        public void SetupEmail&amp;lt;T, ID&amp;gt;(AutoService&amp;lt;T, ID&amp;gt; service)&lt;br /&gt;
             where T : Content&amp;lt;ID&amp;gt;, new()&lt;br /&gt;
             where ID : struct, IConvertible, IEquatable&amp;lt;ID&amp;gt;, IComparable&amp;lt;ID&amp;gt;&lt;br /&gt;
        {&lt;br /&gt;
&lt;br /&gt;
            // check that the content type has the required fields&lt;br /&gt;
            JsonField emailAddressField = null;&lt;br /&gt;
            JsonField eventField = null;&lt;br /&gt;
            JsonField firstNameField = null;&lt;br /&gt;
&lt;br /&gt;
            // Get the field info for the current service type &lt;br /&gt;
            var context = new Context();&lt;br /&gt;
            var fieldInfo = service.GetJsonStructure(context).Result;&lt;br /&gt;
&lt;br /&gt;
            foreach (var fieldPair in fieldInfo.AllFields)&lt;br /&gt;
            {&lt;br /&gt;
                if (fieldPair.Key.ToLower() == &amp;quot;emailaddress&amp;quot;)&lt;br /&gt;
                {&lt;br /&gt;
                    emailAddressField = fieldPair.Value;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (fieldPair.Key.ToLower() == &amp;quot;event&amp;quot;)&lt;br /&gt;
                {&lt;br /&gt;
                    eventField = fieldPair.Value;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (fieldPair.Key.ToLower() == &amp;quot;firstname&amp;quot;)&lt;br /&gt;
                {&lt;br /&gt;
                    firstNameField = fieldPair.Value;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (emailAddressField == null || eventField == null || firstNameField == null)&lt;br /&gt;
            {&lt;br /&gt;
                // form doesn&amp;#039;t have the required fields&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // wire up the after create event for this form&lt;br /&gt;
            service.EventGroup.AfterCreate.AddEventListener((Context context, T formData) =&amp;gt;&lt;br /&gt;
            {&lt;br /&gt;
                // Adding an AfterCreate event handler to a totally generic content type&lt;br /&gt;
                // because of the where T : .. rule above, C# knows it is at least some sort of content type with Id etc on it.&lt;br /&gt;
&lt;br /&gt;
                var emailAddress = emailAddressField.FieldInfo.GetValue(formData) as string;&lt;br /&gt;
                var eventName = eventField.FieldInfo.GetValue(formData) as string;&lt;br /&gt;
                var firstName = firstNameField.FieldInfo.GetValue(formData) as string;&lt;br /&gt;
&lt;br /&gt;
                if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(eventName) || string.IsNullOrWhiteSpace(firstName))&lt;br /&gt;
                {&lt;br /&gt;
                    // form doesn&amp;#039;t have the required data&lt;br /&gt;
                    return new ValueTask&amp;lt;T&amp;gt;(formData);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // now have the data so send an email.....&lt;br /&gt;
&lt;br /&gt;
                var userRecipient = new Recipient(emailAddress, context);&lt;br /&gt;
                userRecipient.CustomData = new&lt;br /&gt;
                {&lt;br /&gt;
                    Event = eventName,&lt;br /&gt;
                    FirstName = firstName&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                var recipients = new List&amp;lt;Recipient&amp;gt;&lt;br /&gt;
                {&lt;br /&gt;
                    userRecipient&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                _emails.Send(recipients, &amp;quot;event_confirmation&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                return new ValueTask&amp;lt;T&amp;gt;(formData);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Working with them on the UI =&lt;br /&gt;
&lt;br /&gt;
Dynamic content types become a lot less useful if you have to hardcode field names into your javascript in order to use them. It is recommended to make use of the [[Graph Editor]] to instead pipe the dynamic content through to more generic components.&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=614</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=614"/>
		<updated>2023-12-13T17:36:08Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\distribution.key -out d:\\distribution.csr -subj &amp;quot;/emailAddress=you@domain.com, CN=Your Company, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the distribution.csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\distribution.key -in d:\\distribution.pem -out d:\\distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example yourapp.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier&lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 password $(p12passwd)&lt;br /&gt;
&lt;br /&gt;
You should now have 2 files in the relevant secure files area of the pipeline&lt;br /&gt;
&lt;br /&gt;
*distribution.p12 &lt;br /&gt;
*yourapp.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Ensure they are correctly referenced in the build pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;yourapp.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=613</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=613"/>
		<updated>2023-12-13T17:33:56Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Check the build pipeline */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example yourapp.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier&lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 password $(p12passwd)&lt;br /&gt;
&lt;br /&gt;
You should now have 2 files in the relevant secure files area of the pipeline&lt;br /&gt;
&lt;br /&gt;
*4Roads_Distribution.p12 &lt;br /&gt;
*yourapp.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Ensure they are correctly referenced in the build pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;yourapp.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=612</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=612"/>
		<updated>2023-12-13T17:33:30Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Provisioning File */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example yourapp.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier&lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 password $(p12passwd)&lt;br /&gt;
&lt;br /&gt;
You should now have 2 files in the relevant secure files area of the pipeline&lt;br /&gt;
&lt;br /&gt;
*{distribution}.p12 &lt;br /&gt;
*{yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Ensure they are correctly referenced in the build pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=611</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=611"/>
		<updated>2023-12-13T17:32:29Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Check the build pipeline */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 password $(p12passwd)&lt;br /&gt;
&lt;br /&gt;
You should now have 2 files in the relevant secure files area of the pipeline&lt;br /&gt;
&lt;br /&gt;
*{distribution}.p12 &lt;br /&gt;
*{yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Ensure they are correctly referenced in the build pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=610</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=610"/>
		<updated>2023-12-13T17:31:52Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Upload your distribution p12 file into devops */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 file pwd&lt;br /&gt;
&lt;br /&gt;
You should now have 2 files in the relevant secure files area of the pipeline&lt;br /&gt;
&lt;br /&gt;
*{distribution}.p12 &lt;br /&gt;
*{yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Ensure they are correctly referenced in the build pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=609</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=609"/>
		<updated>2023-12-13T17:31:11Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Check the build pipeline */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops to import the certificate, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 file pwd&lt;br /&gt;
&lt;br /&gt;
You should now have 2 files in the relevant secure files area of the pipeline&lt;br /&gt;
&lt;br /&gt;
*{distribution}.p12 &lt;br /&gt;
*{yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Ensure they are correctly referenced in the build pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=608</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=608"/>
		<updated>2023-12-13T17:30:43Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Check the build pipeline */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops to import the certificate, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 file pwd&lt;br /&gt;
&lt;br /&gt;
You should now have 2 files in the relevant secure files area of the pipeline&lt;br /&gt;
&lt;br /&gt;
{distribution}.p12 &lt;br /&gt;
{yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Ensure they are correctly referenced in the build pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=607</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=607"/>
		<updated>2023-12-13T17:28:43Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Upload your distribution p12 file into devops */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops to import the certificate, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE YAML BUILD FILE)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 file pwd&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=606</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=606"/>
		<updated>2023-12-13T17:28:12Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Convert into p12 file for devops */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Upload your distribution p12 file into devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Navigate to {project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops to import the certificate, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE yaml build file)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 file pwd&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=605</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=605"/>
		<updated>2023-12-13T17:26:56Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Convert into p12 file for devops */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Using the distribution.cer file you created and downloaded from the apple developer portal and your key file from the first step &lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
{project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops to import the certificate, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE yaml build file)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 file pwd&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=604</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=604"/>
		<updated>2023-12-13T17:24:34Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
{project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops to import the certificate, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE yaml build file)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If it&amp;#039;s an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
* Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
* Save the changes&lt;br /&gt;
&lt;br /&gt;
* Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
* Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 file pwd&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=603</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=603"/>
		<updated>2023-12-13T17:21:44Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Creating and using apple developer certificates for signing app in devops */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\\4Roads_Distribution.key -out d:\\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create a distribution certificate using Apple Developer ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Go to https://developer.apple.com/account/resources/certificates/list&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create New Certificate&lt;br /&gt;
**	Choose &amp;quot;Apple Distribution&amp;quot;&lt;br /&gt;
**	Upload the csr file created above&lt;br /&gt;
**	Download the distribution.cer file&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
You will need to set a password for the certificate (remember this for later.....)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl x509 -in d:\\distribution.cer -inform DER -out d:\\distribution.pem -outform PEM&lt;br /&gt;
openssl pkcs12 -export -inkey d:\\4Roads_Distribution.key -in d:\\distribution.pem -out d:\\4Roads_Distribution.p12&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Convert into p12 file for devops ===&lt;br /&gt;
&lt;br /&gt;
Take your shiny new .p12 file and upload it into devops for use by your pipeline&lt;br /&gt;
&lt;br /&gt;
{project} -&amp;gt; pipelines -&amp;gt; library -&amp;gt; secure files&lt;br /&gt;
&lt;br /&gt;
You may also need to update the password variable used by devops to import the certificate, usually this is called p12passwd and is stored in the build pipeline variables &lt;br /&gt;
&lt;br /&gt;
(DO NOT HARDCODE IT INTO THE yaml build file)&lt;br /&gt;
&lt;br /&gt;
=== Provisioning File ===&lt;br /&gt;
&lt;br /&gt;
You will also need to create/update the mobile provisioning file so that it links to your new certificate in the apple developer portal&lt;br /&gt;
&lt;br /&gt;
If you forget this stage you will most likely end up with a very unhelpful error from the build such as &lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Error: /usr/bin/xcodebuild failed with return code: 65&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
https://developer.apple.com/account/resources/profiles/list&lt;br /&gt;
&lt;br /&gt;
If its an existing app, then choose the entry from the list, there should be a single entry per application.&lt;br /&gt;
&lt;br /&gt;
Choose edit and at the bottom link the new certificate.&lt;br /&gt;
&lt;br /&gt;
Save the changes&lt;br /&gt;
&lt;br /&gt;
Download the mobile provision file so for example {yourapp}.mobileprovision&lt;br /&gt;
&lt;br /&gt;
Upload the file into the secure files area as per the p12 file earlier &lt;br /&gt;
&lt;br /&gt;
=== Check the build pipeline === &lt;br /&gt;
&lt;br /&gt;
Check the pipeline build file to ensure that the filenames are correct, note the variable for the p12 file pwd&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleCertificate@2&lt;br /&gt;
        displayName: &amp;#039;iOS certificate installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          certSecureFile: &amp;#039;4Roads_Distribution.p12&amp;#039;&lt;br /&gt;
          certPwd: &amp;#039;$(p12passwd)&amp;#039;&lt;br /&gt;
&lt;br /&gt;
      - task: InstallAppleProvisioningProfile@1&lt;br /&gt;
        displayName: &amp;#039;iOS provisioning profile installation&amp;#039; &lt;br /&gt;
        inputs:&lt;br /&gt;
          provisioningProfileLocation: &amp;#039;secureFiles&amp;#039;&lt;br /&gt;
          provProfileSecureFile: &amp;#039;{yourapp}.mobileprovision&amp;#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=602</id>
		<title>Apple Certificates</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Apple_Certificates&amp;diff=602"/>
		<updated>2023-12-13T16:53:31Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: Created page with &amp;quot;== Creating and using apple developer certificates for signing app in devops ==  === Get openssl ===  download open ssl 1.x (later versions are not compatible with mac ios see...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Creating and using apple developer certificates for signing app in devops ==&lt;br /&gt;
&lt;br /&gt;
=== Get openssl ===&lt;br /&gt;
&lt;br /&gt;
download open ssl 1.x (later versions are not compatible with mac ios see comment below)&lt;br /&gt;
&lt;br /&gt;
If you are using a build system like cordova and receive an error like this one:&lt;br /&gt;
&lt;br /&gt;
security: SecKeychainItemImport: MAC verification failed during PKCS12 import (wrong password?)&lt;br /&gt;
&lt;br /&gt;
It&amp;#039;s because &amp;quot;OpenSSL 3.x changed its default algorithm in pkcs12. Which is not compatible with embedded Security frameworks in macOS/iOS. You could alternatively use OpenSSL 1.x.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Create a certificate signing request (CSR) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
openssl genpkey -algorithm RSA -out d:\4Roads_Distribution.key&lt;br /&gt;
&lt;br /&gt;
openssl req -new -key d:\4Roads_Distribution.key -out d:\4Roads_Distribution.csr -subj &amp;quot;/emailAddress=Robert.Nash@4-roads.com, CN=4 Roads LTD, C=GB&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== References ===&lt;br /&gt;
&lt;br /&gt;
https://help.adobe.com/en_US/air/build/WS5b3ccc516d4fbf351e63e3d118666ade46-7ff0.html#WSfffb011ac560372f284e44b012cd1e700c0-8000&lt;br /&gt;
&lt;br /&gt;
https://gist.github.com/boodle/77436b2d9facb8e938ad&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
	<entry>
		<id>https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=601</id>
		<title>Search Elastic</title>
		<link rel="alternate" type="text/html" href="https://wiki.socialstack.dev/index.php?title=Search_Elastic&amp;diff=601"/>
		<updated>2023-12-12T15:34:38Z</updated>

		<summary type="html">&lt;p&gt;80.229.4.125: /* Forcing a boolean value to pass both checked and unchecked values */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The elastic search module works alongside the [[Search Crawler]] to extract pages from the site into an elastic search instance to enable site wide search&lt;br /&gt;
&lt;br /&gt;
The module registers itself to listen for the search crawler events and creates locale based indexes in the elastic repo for any pages it receives&lt;br /&gt;
&lt;br /&gt;
When indexing the pages, if there is a primary data type then this will also be indexed against the page in &amp;#039;&amp;#039;&amp;#039;primaryObject&amp;#039;&amp;#039;&amp;#039;, this can then be used in query values as explained below&lt;br /&gt;
&lt;br /&gt;
The indexing process is controlled by execution of the search crawler via the site automations&lt;br /&gt;
&lt;br /&gt;
The module contains a Readme.txt file which gives help for the initial setup of elastic but the creation of the indexes/content are all automated.&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&lt;br /&gt;
The basic setup steps are &lt;br /&gt;
&lt;br /&gt;
#Install the following socialstack modules&lt;br /&gt;
#*Api/SearchCrawler&lt;br /&gt;
#*Api/SearchElastic&lt;br /&gt;
#*UI/SiteSearch&lt;br /&gt;
#Mark any pages not to be indexed&lt;br /&gt;
#Install Elastic Search (see &amp;#039;&amp;#039;&amp;#039;[https://github.com/socialstack-core/modules/tree/master/Api/SearchElastic Readme.txt]&amp;#039;&amp;#039;&amp;#039; in the repo for help and guidance)&lt;br /&gt;
#Configure the sizing/scale in the &amp;#039;&amp;#039;&amp;#039;elasticsearch.yml&amp;#039;&amp;#039;&amp;#039; file (mainly vm memory usage)&lt;br /&gt;
#Start Elastic Search for the first time and get the master password and certificate fingerprint&lt;br /&gt;
#Add the url, credentials and fingerprint values to the module configuration via the ss admin panel&lt;br /&gt;
#Run the automation to index the site, the indexing progress is shown via the console&lt;br /&gt;
&lt;br /&gt;
The module also contains a postman collection which can be used to quick debug/verify the indexed content&lt;br /&gt;
&lt;br /&gt;
Once indexed there is a UI/SiteSearch module which can be used as the basis of the site search&lt;br /&gt;
&lt;br /&gt;
== Social Stack Cloud Install Steps == &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get install apt-transport-https&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;echo &amp;quot;deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main&amp;quot; | sudo tee /etc/apt/sources.list.d/elastic-8.x.list&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install elasticsearch&amp;lt;/code&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Remember to get the elastic user password from the output and update the ss configuration&lt;br /&gt;
&lt;br /&gt;
&amp;quot;The generated password for the elastic built-in superuser is : xxxxxx&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Next get the ssl certificate fingerprint value for config&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The section you need looks like this (the base64encoded values)&lt;br /&gt;
SHA256 Fingerprint=85:AA:9E:19:2F:4C:72:01:01:66:41:87:28:03:52:B7:88:61:9C:93:E8:18:D2:9C:C7:60:92:F9:E0:84:EE:AA&lt;br /&gt;
&lt;br /&gt;
Now elasticsearch is installed, before starting look at the memory and diskspace section below&lt;br /&gt;
&lt;br /&gt;
Finnaly you can now start the elasticsearch service by executing&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These commands provide no feedback as to whether Elasticsearch was started successfully or not. &lt;br /&gt;
Instead, this information will be written in the log files located in /var/log/elasticsearch/ or you can run the command below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;systemctl status elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To configure Elasticsearch to start automatically when the system boots up, run the following commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo /bin/systemctl daemon-reload&lt;br /&gt;
&lt;br /&gt;
sudo /bin/systemctl enable elasticsearch.service&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring memory and diskspace ==&lt;br /&gt;
&lt;br /&gt;
It is recommended to setup the vm with at least 4gb ram, when running 2gb we have run into issues &lt;br /&gt;
&lt;br /&gt;
edit the config file to set diskspace limits &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/elasticsearch.yml&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cluster.routing.allocation.disk.threshold_enabled: false&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.flood_stage: 250mb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.low: 2gb&lt;br /&gt;
&lt;br /&gt;
cluster.routing.allocation.disk.watermark.high: 1gb&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To disable geo location, if the project is not using the geolocation extensionsit can be disabled as otherwise an additional index is created and the contents automatically downloaded &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
ingest.geoip.downloader.enabled: false&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set the memory usage (default 4gb) but for dev we have set it to the minimum as below &lt;br /&gt;
&lt;br /&gt;
edit /etc/elasticsearch/jvm.options&lt;br /&gt;
&lt;br /&gt;
Uncomment and set these values &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
-Xms128m&lt;br /&gt;
&lt;br /&gt;
-Xmx128m&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To check the heap size &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
sudo cat /var/log/elasticsearch/elasticsearch.log | grep &amp;quot;heap size&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Admin == &lt;br /&gt;
&lt;br /&gt;
linux&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl start elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;sudo systemctl stop elasticsearch.service&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To get the fingerprint &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
cd /etc/elasticsearch/certs&lt;br /&gt;
&lt;br /&gt;
openssl x509 -fingerprint -sha256 -in http_ca.crt&lt;br /&gt;
&amp;lt;/code&amp;gt;which &lt;br /&gt;
&lt;br /&gt;
To reset the user password&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
bin/elasticsearch-reset-password -u elastic -i&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Endpoints ==&lt;br /&gt;
&lt;br /&gt;
=== GET /v1/sitesearch/reset ===&lt;br /&gt;
&lt;br /&gt;
This removes any indexes from elastic to allow a clean start&lt;br /&gt;
&lt;br /&gt;
=== POST /v1/sitesearch/query ===&lt;br /&gt;
&lt;br /&gt;
This is the core search endpoint which currently accepts the following values &lt;br /&gt;
&lt;br /&gt;
;queryString&lt;br /&gt;
:The query value entered by the user and/or the &amp;#039;&amp;#039;&amp;#039;UI/SiteSearch&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; components, elastic search uses the Lucene query language so you can build complex queries &lt;br /&gt;
:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html&lt;br /&gt;
:e.g.&lt;br /&gt;
:&amp;lt;code&amp;gt;Africa AND primaryObject.name:Botswana&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;primaryObject.price:(&amp;gt;=1000 AND &amp;lt;5000)&amp;lt;/code&amp;gt;&lt;br /&gt;
:&amp;lt;code&amp;gt;((quick AND fox) OR (brown AND fox) OR fox) AND NOT dog&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
;tags&lt;br /&gt;
: This expects a list of comma separated tags, if any are supplied the query will only return results which contain all the passed tag values&lt;br /&gt;
&lt;br /&gt;
;contentTypes&lt;br /&gt;
: This expects a list of comma separated content types, if any are supplied the query will only return results which contain all the passed content types, these are normally the primary data object for the page&lt;br /&gt;
&lt;br /&gt;
;pageSize&lt;br /&gt;
: This expects the page size and will default to 10 if not populated&lt;br /&gt;
&lt;br /&gt;
;pageIndex&lt;br /&gt;
: This expects the starting page number and will default to 1&lt;br /&gt;
&lt;br /&gt;
;aggregationFields&lt;br /&gt;
: This expects a list of comma separated field names, if any are supplied then aggregations/facets are returned for the fields, a good example of this can be be found in &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; component&lt;br /&gt;
&lt;br /&gt;
;allFields&lt;br /&gt;
: If this is set to true then any indexed content will be searchable, this includes any &amp;#039;include&amp;#039; files and metadata from the primary object, if you want to restrict the search to the managed text fields then set this to false (the default)&lt;br /&gt;
&lt;br /&gt;
== Search taxonomy and filters/facets ==&lt;br /&gt;
You can filter or define facets(aggregations in ElasticSerach terminology) on any field exposed to Elastic from the social stack application, but they work best on metadata type fields such as tags, price etc &lt;br /&gt;
&lt;br /&gt;
A basic example would be the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; field.&lt;br /&gt;
&lt;br /&gt;
But a better solution is to setup contextual tags using the &amp;#039;&amp;#039;&amp;#039;Api/Categories&amp;#039;&amp;#039;&amp;#039; component and tags.&lt;br /&gt;
&lt;br /&gt;
This allows you to give some context to a tag, so for example you could create an &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category, then when adding a new tag for &amp;#039;&amp;#039;&amp;#039;Cow&amp;#039;&amp;#039;&amp;#039; or &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; you would set their category to be &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Any tags linked to a category can be referenced as &amp;#039;&amp;#039;&amp;#039;taxonomy.xxxxx&amp;#039;&amp;#039;&amp;#039; so in this example &amp;#039;&amp;#039;&amp;#039;taxonomy.animals&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
These can then be referenced to show a contextual filter in the UI, the example below will pass back an aggregation of any &amp;#039;&amp;#039;&amp;#039;animals&amp;#039;&amp;#039;&amp;#039; tags in the result set, &amp;#039;&amp;#039;&amp;#039;UI/ContentSearch&amp;#039;&amp;#039;&amp;#039; then uses these render an auto generated filter.&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		 {&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.animals&amp;quot;,&lt;br /&gt;
				&amp;quot;contentTypes&amp;quot;: &amp;quot;ItineraryPageDataType&amp;quot;&lt;br /&gt;
            }   &lt;br /&gt;
         } &lt;br /&gt;
         ]&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When tags are assigned to category and indexed, you will expose the core tag values and also some auto generated taxonomy fields.&lt;br /&gt;
&lt;br /&gt;
An example of the indexed data in Elastic looks something like the example below where &amp;#039;&amp;#039;&amp;#039;Africa&amp;#039;&amp;#039;&amp;#039; and &amp;#039;&amp;#039;&amp;#039;Botswana&amp;#039;&amp;#039;&amp;#039; are plain tags but &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; has been linked to the &amp;#039;&amp;#039;&amp;#039;Animals&amp;#039;&amp;#039;&amp;#039; category&lt;br /&gt;
&lt;br /&gt;
The &amp;#039;&amp;#039;&amp;#039;Horse&amp;#039;&amp;#039;&amp;#039; value is still exposed in the &amp;#039;&amp;#039;&amp;#039;tags&amp;#039;&amp;#039;&amp;#039; element as that is used for free text search ranking. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
   &amp;quot;tags&amp;quot;: [&lt;br /&gt;
           &amp;quot;Africa&amp;quot;,&lt;br /&gt;
           &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
           &amp;quot;Horse&amp;quot;&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;taxonomy&amp;quot;: {&lt;br /&gt;
      &amp;quot;animals&amp;quot;: [&lt;br /&gt;
         &amp;quot;Horse&amp;quot;&lt;br /&gt;
      ]&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== UI Components  ==&lt;br /&gt;
;UI/ContentSearch&lt;br /&gt;
&lt;br /&gt;
This provides a good example with filtering and paging see the amba repo &lt;br /&gt;
&lt;br /&gt;
The component will also allow you to override the default values/titles for any aggregation using the aggregationOverrides property, this can be useful to add grouping to values such as regions/countries etc&lt;br /&gt;
&lt;br /&gt;
The buckets property is optional and by default would normally be an empty array [] but can be used to override labels and the order of options&lt;br /&gt;
&lt;br /&gt;
Some basic examples of how overrides can be used to control the search filters&lt;br /&gt;
&lt;br /&gt;
=== 2 Column example with set items for destination ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.destination&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Destination&amp;quot;,&lt;br /&gt;
	&amp;quot;columns&amp;quot;: 2,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Botswana&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Madagascar&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Scotland&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Bonny Scotland&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;key&amp;quot;: &amp;quot;Svalbard&amp;quot;,&lt;br /&gt;
			&amp;quot;label&amp;quot;: null&lt;br /&gt;
		}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Swapping the default select boxes for radio buttons ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to show as a single checkbox ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isFeatured&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Only show featured events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a boolean value to return both checked and unchecked values ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.isCompleted&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Show completed events&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;isBoolean&amp;quot;: true,&lt;br /&gt;
	&amp;quot;returnAllValues&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Forcing a simple select dropdown ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;primaryObject.country&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Location&amp;quot;,&lt;br /&gt;
	&amp;quot;all&amp;quot;: &amp;quot;Any location&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Allow multiple unrelated values to be chosen ===&lt;br /&gt;
&lt;br /&gt;
Override the default functionality which disables any original filter options if the search results no longer contain results containing the values, so a drill down work flow &lt;br /&gt;
dog -&amp;gt; spaniel -&amp;gt; english&lt;br /&gt;
&lt;br /&gt;
With &amp;#039;allowAllOptions&amp;#039; other unrelated documents can be brought into the results so for example when filtering events you could include multiple unrelated types such as &amp;quot;gala&amp;quot; OR &amp;quot;conference&amp;quot; when by default if you choose &amp;quot;gala&amp;quot;, conferences would be disabled&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;: &amp;quot;taxonomy.event type&amp;quot;,&lt;br /&gt;
	&amp;quot;label&amp;quot;: &amp;quot;Event Type&amp;quot;,&lt;br /&gt;
	&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
	&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
	&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
	&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A complete example is shown below using separate components for the filters and results (2 column layout)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;c&amp;quot;: [&lt;br /&gt;
		{&lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/SearchContext&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {&lt;br /&gt;
				&amp;quot;comment&amp;quot;: &amp;quot;SearchContext used to link search engine, results and filters&amp;quot;&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
			    &lt;br /&gt;
			&amp;quot;t&amp;quot;: &amp;quot;UI/SiteContainer&amp;quot;,&lt;br /&gt;
			&amp;quot;d&amp;quot;: {},&lt;br /&gt;
			&amp;quot;c&amp;quot;: {&lt;br /&gt;
				&amp;quot;t&amp;quot;: &amp;quot;UI/SiteRow&amp;quot;,&lt;br /&gt;
				&amp;quot;d&amp;quot;: {},&lt;br /&gt;
				&amp;quot;c&amp;quot;: [&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;sidebar&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: {&lt;br /&gt;
							&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch&amp;quot;,&lt;br /&gt;
							&amp;quot;d&amp;quot;: {&lt;br /&gt;
								&amp;quot;title&amp;quot;: &amp;quot;Search insights&amp;quot;,&lt;br /&gt;
								&amp;quot;aggregationFields&amp;quot;: &amp;quot;taxonomy.brand,taxonomy.audience,taxonomy.insight category,tags&amp;quot;,&lt;br /&gt;
								&amp;quot;contentTypes&amp;quot;: &amp;quot;Insight&amp;quot;,&lt;br /&gt;
								&amp;quot;resultLayout&amp;quot;: &amp;quot;list&amp;quot;,&lt;br /&gt;
								&amp;quot;itemRenderer&amp;quot;: &amp;quot;UI/InsightResultItem&amp;quot;,&lt;br /&gt;
								&amp;quot;sortOptions&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Ranking&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;_score&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Name (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;title.keyword&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (asc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;asc&amp;quot;&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;Date (desc)&amp;quot;,&lt;br /&gt;
										&amp;quot;field&amp;quot;: &amp;quot;primaryObject.editedUtc&amp;quot;,&lt;br /&gt;
										&amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;aggregationOverrides&amp;quot;: [&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.brand&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View insights by brand&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Both&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.audience&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Audience&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any audience type&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;radio&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;taxonomy.insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;View by insight category&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any category&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;checkbox&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;isExpanded&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									},&lt;br /&gt;
									{&lt;br /&gt;
										&amp;quot;name&amp;quot;: &amp;quot;tags&amp;quot;,&lt;br /&gt;
										&amp;quot;label&amp;quot;: &amp;quot;Topics&amp;quot;,&lt;br /&gt;
										&amp;quot;all&amp;quot;: &amp;quot;Any topics&amp;quot;,&lt;br /&gt;
										&amp;quot;prompt&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
										&amp;quot;inputType&amp;quot;: &amp;quot;select&amp;quot;,&lt;br /&gt;
										&amp;quot;allowAllOptions&amp;quot;: true,&lt;br /&gt;
										&amp;quot;buckets&amp;quot;: []&lt;br /&gt;
									}&lt;br /&gt;
								],&lt;br /&gt;
								&amp;quot;hideResults&amp;quot;: true,&lt;br /&gt;
								&amp;quot;hideTitle&amp;quot;: true,&lt;br /&gt;
								&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
							},&lt;br /&gt;
							&amp;quot;i&amp;quot;: 4&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;i&amp;quot;: 6&lt;br /&gt;
					},&lt;br /&gt;
					{&lt;br /&gt;
						&amp;quot;t&amp;quot;: &amp;quot;UI/SiteColumn&amp;quot;,&lt;br /&gt;
						&amp;quot;d&amp;quot;: {&lt;br /&gt;
							&amp;quot;width&amp;quot;: &amp;quot;content&amp;quot;&lt;br /&gt;
						},&lt;br /&gt;
						&amp;quot;c&amp;quot;: [&lt;br /&gt;
							{&lt;br /&gt;
								&amp;quot;t&amp;quot;: &amp;quot;UI/ContentSearch/ContentSearchResults&amp;quot;,&lt;br /&gt;
								&amp;quot;d&amp;quot;: {&lt;br /&gt;
									&amp;quot;hideTotal&amp;quot;: true,&lt;br /&gt;
									&amp;quot;searchType&amp;quot;: &amp;quot;insights&amp;quot;,&lt;br /&gt;
									&amp;quot;pageSize&amp;quot;: &amp;quot;4&amp;quot;&lt;br /&gt;
								},&lt;br /&gt;
								&amp;quot;i&amp;quot;: 14&lt;br /&gt;
							}&lt;br /&gt;
						],&lt;br /&gt;
						&amp;quot;i&amp;quot;: 11&lt;br /&gt;
					}&lt;br /&gt;
				],&lt;br /&gt;
				&amp;quot;i&amp;quot;: 12&lt;br /&gt;
			},&lt;br /&gt;
			&amp;quot;i&amp;quot;: 13&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;i&amp;quot;: 3&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>80.229.4.125</name></author>
	</entry>
</feed>